Skip to content

Commit

Permalink
chore(cloud): add cached state to events
Browse files Browse the repository at this point in the history
Added a `cached` state to status event payloads. This state is only used
when a "get status" action succeeds, and is intended to simplify the
API logic around deciding whether e.g. a build was cached or ran
successfully to completion.
  • Loading branch information
thsig committed Mar 16, 2023
1 parent 94287b1 commit df9d908
Show file tree
Hide file tree
Showing 12 changed files with 51 additions and 29 deletions.
2 changes: 1 addition & 1 deletion core/src/actions/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import { PickTypeByKind } from "../graph/config-graph"
import { DeployAction } from "./deploy"
import { TestAction } from "./test"
import { RunAction } from "./run"
import { uuidv4 } from "../util/util"
import { uuidv4 } from "../util/random"

// TODO-G2: split this file

Expand Down
38 changes: 30 additions & 8 deletions core/src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,23 @@ export interface ActionConfigTypes {
Test: TestActionConfig<string, any>
}

// See https://melvingeorge.me/blog/convert-array-into-string-literal-union-type-typescript
export const actionStateTypes = ["getting-status", "ready", "not-ready", "processing", "failed", "unknown"] as const
/**
* These are the states returned from actions.
*
* See https://melvingeorge.me/blog/convert-array-into-string-literal-union-type-typescript
*/
export const actionStateTypes = ["ready", "not-ready", "processing", "failed", "unknown"] as const
export type ActionState = typeof actionStateTypes[number]

/**
* These are the states emitted in status events. Here, we include additional states to help distinguish status event
* emitted around status/cache checks VS statuses emitted around the execution after a failed status check.
*/
export const actionStateTypesForEvent = [
...actionStateTypes,
"getting-status",
"cached",
] as const
/**
* This type represents the lifecycle of an individual action execution.
*
Expand All @@ -110,7 +125,7 @@ export const actionStateTypes = ["getting-status", "ready", "not-ready", "proces
* initial state: "getting-status"
* final state: "ready" or "failed"
*
* "getting-status" -> "ready" | "not-ready"
* "getting-status" -> "cached" | "not-ready"
* "not-ready" -> "processing"
* "processing" -> "ready" | "failed"
* ```
Expand All @@ -125,10 +140,13 @@ export const actionStateTypes = ["getting-status", "ready", "not-ready", "proces
* - Or for a Kubernetes deployment, this might involve checking if the requested resources are already live and up
* to date.
*
* - `"ready"`: An up-to-date result exists for the action.
* - This state can be reached by a status check that returned a successful cache hit (e.g. an up-to-date build
* artifact / deployed resource / test result / run result already exists), or by successfully processing the
* action after getting a `"not-ready"` state from the status check.
* - `"cached"`: This state indicates a cache hit. An up-to-date result exists for the action.
* - This state can be reached e.g. when a status check indicates that an up-to-date build artifact / deployed
* resource / test result / run result already exists.
*
* - `"ready"`: The action was executed successfully, and an up-to-date result exists for the action.
* - This state can be reached by successfully processing the action after getting a `"not-ready"` state from the
* status check.
*
* - `"not-ready"`: No result (or no healthy result) for the action exists with the requested version.
* - This state is reached by a status check that doesn't find an up-to-date result (e.g. no up-to-date container
Expand All @@ -140,7 +158,11 @@ export const actionStateTypes = ["getting-status", "ready", "not-ready", "proces
* - `"failed"`: The action was executed, but a failure or error occurred, so no up-to-date result was created for
* the action.
*/
export type ActionState = typeof actionStateTypes[number]
export type ActionStateForEvent = typeof actionStateTypesForEvent[number]

export const stateForCacheStatusEvent = (state: ActionState): ActionStateForEvent => {
return state === "ready" ? "cached" : state
}

export interface ActionStatus<
T extends BaseAction = BaseAction,
Expand Down
6 changes: 3 additions & 3 deletions core/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import type { CommandInfo } from "./plugin-context"
import type { ActionReference } from "./config/common"
import type { GraphResult } from "./graph/results"
import { NamespaceStatus } from "./types/namespace"
import { sanitizeValue } from "./util/logging"
import { ActionState } from "./actions/types"
import { BuildState } from "./plugin/handlers/Build/get-status"
import { ActionStateForEvent } from "./actions/types"
import { sanitizeValue } from "./util/logging"

export type GardenEventListener<T extends EventName> = (payload: Events[T]) => void

Expand Down Expand Up @@ -109,7 +109,7 @@ export interface ActionStatusPayload<S = {}> {
moduleName: string | null // DEPRECATED: Remove in 0.14
startedAt: string
completedAt?: string
state: ActionState
state: ActionStateForEvent
status: S
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/plugins/exec/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ExecLogsFollower } from "./logs"
import { PluginContext } from "../../plugin-context"
import { ExecDeploy } from "./config"
import { DeployActionHandler } from "../../plugin/action-types"
import { DeployStatus } from "../../plugin/handlers/Deploy/get-status"
import { deployStateToActionState, DeployStatus } from "../../plugin/handlers/Deploy/get-status"
import { Resolved } from "../../actions/types"
import { convertCommandSpec, execRun, getDefaultEnvVars } from "./common"
import { kill } from "process"
Expand All @@ -50,7 +50,7 @@ export const getExecDeployStatus: DeployActionHandler<"getStatus", ExecDeploy> =
const state = result.exitCode === 0 ? "ready" : "outdated"

return {
state,
state: deployStateToActionState(state),
detail: {
state,
version: action.versionString(),
Expand All @@ -64,7 +64,7 @@ export const getExecDeployStatus: DeployActionHandler<"getStatus", ExecDeploy> =
const state = "unknown"

return {
state,
state: deployStateToActionState(state),
detail: { state, version: action.versionString(), detail: {} },
outputs: {
log: "",
Expand Down
8 changes: 4 additions & 4 deletions core/src/router/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import chalk from "chalk"
import { renderOutputStream } from "../util/util"
import { PluginEventBroker } from "../plugin-context"
import { BaseRouterParams, createActionRouter } from "./base"
import { ActionState } from "../actions/types"
import { ActionState, stateForCacheStatusEvent } from "../actions/types"
import { PublishActionResult } from "../plugin/handlers/Build/publish"
import { uuidv4 } from "../util/random"

export const buildRouter = (baseParams: BaseRouterParams) =>
createActionRouter("Build", baseParams, {
Expand Down Expand Up @@ -42,14 +41,15 @@ export const buildRouter = (baseParams: BaseRouterParams) =>
handlerType: "getStatus",
defaultHandler: async () => ({ state: <ActionState>"unknown", detail: {}, outputs: {} }),
})
const { state } = status

// TODO-G2: only validate if state is ready?
await router.validateActionOutputs(action, "runtime", status.outputs)
garden.events.emit("buildStatus", {
...payloadAttrs,
completedAt: new Date().toISOString(),
state: status.state,
status: { state: status.state === "ready" ? "fetched" : "outdated" },
state: stateForCacheStatusEvent(state),
status: { state: state === "ready" ? "fetched" : "outdated" },
})
return status
},
Expand Down
4 changes: 2 additions & 2 deletions core/src/router/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import chalk from "chalk"
import { omit } from "lodash"
import { ActionState } from "../actions/types"
import { ActionState, stateForCacheStatusEvent } from "../actions/types"
import { PluginEventBroker } from "../plugin-context"
import { DeployState } from "../types/service"
import { renderOutputStream } from "../util/util"
Expand Down Expand Up @@ -159,7 +159,7 @@ export const deployRouter = (baseParams: BaseRouterParams) =>
garden.events.emit("deployStatus", {
...payloadAttrs,
completedAt: new Date().toISOString(),
state: result.state,
state: stateForCacheStatusEvent(result.state),
status: omit(result.detail, "detail")
})

Expand Down
4 changes: 2 additions & 2 deletions core/src/router/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { realpath } from "fs-extra"
import normalizePath from "normalize-path"
import tmp from "tmp-promise"
import { ActionState } from "../actions/types"
import { ActionState, stateForCacheStatusEvent } from "../actions/types"
import { PluginEventBroker } from "../plugin-context"
import { runStatusForEventPayload } from "../plugin/base"
import { copyArtifacts, getArtifactKey } from "../util/artifacts"
Expand Down Expand Up @@ -124,7 +124,7 @@ export const runRouter = (baseParams: BaseRouterParams) =>

garden.events.emit("runStatus", {
...payloadAttrs,
state: result.state,
state: stateForCacheStatusEvent(result.state),
completedAt: new Date().toISOString(),
status: runStatusForEventPayload(result.detail),
})
Expand Down
4 changes: 2 additions & 2 deletions core/src/router/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { realpath } from "fs-extra"
import normalizePath from "normalize-path"
import { ActionState } from "../actions/types"
import { ActionState, stateForCacheStatusEvent } from "../actions/types"
import { PluginEventBroker } from "../plugin-context"
import { runStatusForEventPayload } from "../plugin/base"
import { copyArtifacts, getArtifactKey } from "../util/artifacts"
Expand Down Expand Up @@ -122,7 +122,7 @@ export const testRouter = (baseParams: BaseRouterParams) =>

garden.events.emit("testStatus", {
...payloadAttrs,
state: result.state,
state: stateForCacheStatusEvent(result.state),
completedAt: new Date().toISOString(),
status: runStatusForEventPayload(result.detail),
})
Expand Down
2 changes: 1 addition & 1 deletion core/test/unit/src/router/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe("build actions", () => {
expect(event2.name).to.eql("buildStatus")
expect(event2.payload.moduleName).to.eql("module-a")
expect(event2.payload.actionUid).to.eql(event1.payload.actionUid)
expect(event2.payload.state).to.eql("ready")
expect(event2.payload.state).to.eql("cached")
expect(event2.payload.status.state).to.eql("fetched")
})
})
Expand Down
2 changes: 1 addition & 1 deletion core/test/unit/src/router/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("deploy actions", () => {
expect(event2.payload.actionVersion).to.eql(resolvedDeployAction.versionString())
expect(event2.payload.moduleName).to.eql("module-a")
expect(event2.payload.actionUid).to.eql(event1.payload.actionUid)
expect(event2.payload.state).to.eql("ready")
expect(event2.payload.state).to.eql("cached")
expect(event2.payload.status.state).to.eql("ready")
})

Expand Down
2 changes: 1 addition & 1 deletion core/test/unit/src/router/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe("run actions", () => {
expect(event2.name).to.eql("runStatus")
expect(event2.payload.moduleName).to.eql("module-a")
expect(event2.payload.actionUid).to.eql(event1.payload.actionUid)
expect(event2.payload.state).to.eql("ready")
expect(event2.payload.state).to.eql("cached")
expect(event2.payload.status.state).to.eql("succeeded")
})

Expand Down
2 changes: 1 addition & 1 deletion core/test/unit/src/router/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ describe("test actions", () => {
expect(event2.name).to.eql("testStatus")
expect(event1.payload.moduleName).to.eql("module-a")
expect(event2.payload.actionUid).to.eql(event1.payload.actionUid)
expect(event2.payload.state).to.eql("ready")
expect(event2.payload.state).to.eql("cached")
expect(event2.payload.status.state).to.eql("succeeded")
})
})

0 comments on commit df9d908

Please sign in to comment.