diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 502c082ea1..5c324881f6 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -388,7 +388,7 @@ Examples: ### garden get status -Outputs the status of your environment. +Outputs the full status of your environment. ##### Usage @@ -735,8 +735,8 @@ This can be useful for debugging tests, particularly integration/end-to-end test Examples: - garden run test my-module integ # run the test named 'integ' in my-module - garden run test my-module integ --interactive=false # do not attach to the test run, just output results when completed + garden run test my-module integ # run the test named 'integ' in my-module + garden run test my-module integ --interactive=false # do not attach to the test run, just output results when completed ##### Usage diff --git a/docs/reference/config.md b/docs/reference/config.md index 336be66032..08ac8c1320 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -961,25 +961,41 @@ limits: # The steps the workflow should run. At least one step is required. Steps are run sequentially. If a step fails, # subsequent steps are skipped. steps: - - # A Garden command this step should run. + - # An identifier to assign to this step. If none is specified, this defaults to "step-", where + # is the sequential number of the step (first step being number 1). + # + # This identifier is useful when referencing command outputs in following steps. For example, if you set this + # to "my-step", following steps can reference the \${steps.my-step.outputs.*} key in the `script` or `command` + # fields. + name: + + # A Garden command this step should run, followed by any required or optional arguments and flags. + # Arguments and options for the commands may be templated, including references to previous steps, but for now + # the commands themselves (as listed below) must be hard-coded. # # Supported commands: # + # `[build]` # `[delete, environment]` - # # `[delete, service]` - # # `[deploy]` - # + # `[exec]` + # `[get, config]` # `[get, outputs]` - # + # `[get, status]` + # `[get, task-result]` + # `[get, test-result]` + # `[link, module]` + # `[link, source]` # `[publish]` - # # `[run, task]` - # # `[run, test]` - # # `[test]` + # `[update-remote, all]` + # `[update-remote, modules]` + # `[update-remote, sources]` + # + # command: # A description of the workflow step. @@ -988,6 +1004,7 @@ steps: # A bash script to run. Note that the host running the workflow must have bash installed and on path. It is # considered to have run successfully if it returns an exit code of 0. Any other exit code signals an error, and # the remainder of the workflow is aborted. + # The script may include template strings, including references to previous steps. script: # A list of triggers that determine when the workflow should be run, and which environment should be used (Garden @@ -1145,34 +1162,67 @@ The steps the workflow should run. At least one step is required. Steps are run | --------------- | -------- | | `array[object]` | Yes | +### `steps[].name` + +[steps](#steps) > name + +An identifier to assign to this step. If none is specified, this defaults to "step-", where + is the sequential number of the step (first step being number 1). + +This identifier is useful when referencing command outputs in following steps. For example, if you set this +to "my-step", following steps can reference the \${steps.my-step.outputs.*} key in the `script` or `command` +fields. + +| Type | Required | +| -------- | -------- | +| `string` | No | + ### `steps[].command[]` [steps](#steps) > command -A Garden command this step should run. +A Garden command this step should run, followed by any required or optional arguments and flags. +Arguments and options for the commands may be templated, including references to previous steps, but for now +the commands themselves (as listed below) must be hard-coded. Supported commands: +`[build]` `[delete, environment]` - `[delete, service]` - `[deploy]` - +`[exec]` +`[get, config]` `[get, outputs]` - +`[get, status]` +`[get, task-result]` +`[get, test-result]` +`[link, module]` +`[link, source]` `[publish]` - `[run, task]` - `[run, test]` - `[test]` +`[update-remote, all]` +`[update-remote, modules]` +`[update-remote, sources]` + + | Type | Required | | --------------- | -------- | | `array[string]` | No | +Example: + +```yaml +steps: + - command: + - run + - task + - my-task +``` + ### `steps[].description` [steps](#steps) > description @@ -1188,6 +1238,7 @@ A description of the workflow step. [steps](#steps) > script A bash script to run. Note that the host running the workflow must have bash installed and on path. It is considered to have run successfully if it returns an exit code of 0. Any other exit code signals an error, and the remainder of the workflow is aborted. +The script may include template strings, including references to previous steps. | Type | Required | | -------- | -------- | diff --git a/docs/reference/template-strings.md b/docs/reference/template-strings.md index 8497ec7c3a..8a78660e20 100644 --- a/docs/reference/template-strings.md +++ b/docs/reference/template-strings.md @@ -232,25 +232,25 @@ Example: my-variable: ${environment.namespace} ``` -### `${providers.*}` +### `${variables.*}` -Retrieve information about providers that are defined in the project. +A map of all variables defined in the project configuration. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${variables.*}` +### `${var.*}` -A map of all variables defined in the project configuration. +Alias for the variables field. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${var.*}` +### `${providers.*}` -Alias for the variables field. +Retrieve information about providers that are defined in the project. | Type | Default | | -------- | ------- | @@ -397,25 +397,25 @@ Example: my-variable: ${environment.namespace} ``` -### `${providers.*}` +### `${variables.*}` -Retrieve information about providers that are defined in the project. +A map of all variables defined in the project configuration. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${variables.*}` +### `${var.*}` -A map of all variables defined in the project configuration. +Alias for the variables field. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${var.*}` +### `${providers.*}` -Alias for the variables field. +Retrieve information about providers that are defined in the project. | Type | Default | | -------- | ------- | @@ -594,25 +594,25 @@ Example: my-variable: ${environment.namespace} ``` -### `${providers.*}` +### `${variables.*}` -Retrieve information about providers that are defined in the project. +A map of all variables defined in the project configuration. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${variables.*}` +### `${var.*}` -A map of all variables defined in the project configuration. +Alias for the variables field. | Type | Default | | -------- | ------- | | `object` | `{}` | -### `${var.*}` +### `${providers.*}` -Alias for the variables field. +Retrieve information about providers that are defined in the project. | Type | Default | | -------- | ------- | @@ -650,3 +650,167 @@ Runtime information from the tasks that the service/task being run depends on. | -------- | ------- | | `object` | `{}` | + +## Workflow configuration context + +The below keys are available in template strings for Workflow configurations. + +Note that the `{steps.*}` key is only available for the `steps[].command` and `steps[].script` fields in Workflow configs, and may only reference previous steps in the same workflow. See below for more details. + + +### `${local.*}` + +Context variables that are specific to the currently running environment/machine. + +| Type | +| -------- | +| `object` | + +### `${local.artifactsPath}` + +The absolute path to the directory where exported artifacts from test and task runs are stored. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${local.artifactsPath} +``` + +### `${local.env.*}` + +A map of all local environment variables (see https://nodejs.org/api/process.html#process_process_env). + +| Type | +| -------- | +| `object` | + +### `${local.platform}` + +A string indicating the platform that the framework is running on (see https://nodejs.org/api/process.html#process_process_platform) + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${local.platform} +``` + +### `${local.username}` + +The current username (as resolved by https://github.com/sindresorhus/username) + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${local.username} +``` + +### `${project.*}` + +Information about the Garden project. + +| Type | +| -------- | +| `object` | + +### `${project.name}` + +The name of the Garden project. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${project.name} +``` + +### `${environment.*}` + +Information about the environment that Garden is running against. + +| Type | +| -------- | +| `object` | + +### `${environment.name}` + +The name of the environment Garden is running against, excluding the namespace. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${environment.name} +``` + +### `${environment.fullName}` + +The full name of the environment Garden is running against, including the namespace. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${environment.fullName} +``` + +### `${environment.namespace}` + +The currently active namespace (if any). + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${environment.namespace} +``` + +### `${variables.*}` + +A map of all variables defined in the project configuration. + +| Type | Default | +| -------- | ------- | +| `object` | `{}` | + +### `${var.*}` + +Alias for the variables field. + +| Type | Default | +| -------- | ------- | +| `object` | `{}` | + +### `${steps.*}` + +Reference previous steps in a workflow. Only available in the `steps[].command` and `steps[].script` fields. +The name of the step should be the explicitly set `name` of the other step, or if one is not set, use +`step-`, where is the sequential number of the step (starting from 1). + +| Type | Default | +| -------- | ------- | +| `object` | `{}` | + diff --git a/garden-service/src/actions.ts b/garden-service/src/actions.ts index c04bbb345c..383c90ed5d 100644 --- a/garden-service/src/actions.ts +++ b/garden-service/src/actions.ts @@ -798,8 +798,6 @@ export class ActionRouter implements TypeGuard { const configContext = new ModuleConfigContext({ garden: this.garden, resolvedProviders: providers, - variables: this.garden.variables, - secrets: this.garden.secrets, dependencyConfigs: modules, dependencyVersions: fromPairs(modules.map((m) => [m.name, m.version])), runtimeContext, @@ -860,8 +858,6 @@ export class ActionRouter implements TypeGuard { const configContext = new ModuleConfigContext({ garden: this.garden, resolvedProviders: providers, - variables: this.garden.variables, - secrets: this.garden.secrets, dependencyConfigs: modules, dependencyVersions: fromPairs(modules.map((m) => [m.name, m.version])), runtimeContext, diff --git a/garden-service/src/commands/run/workflow.ts b/garden-service/src/commands/run/workflow.ts index 6c06b94ce3..e87d8f6809 100644 --- a/garden-service/src/commands/run/workflow.ts +++ b/garden-service/src/commands/run/workflow.ts @@ -63,7 +63,7 @@ export class RunWorkflowCommand extends Command { const outerLog = log.placeholder() // Partially resolve the workflow config, and prepare any configured files before continuing const rawWorkflow = garden.getRawWorkflowConfig(args.workflow) - const templateContext = new WorkflowConfigContext(garden, garden.variables, garden.secrets) + const templateContext = new WorkflowConfigContext(garden) const files = resolveTemplateStrings(rawWorkflow.files || [], templateContext) // Write all the configured files for the workflow diff --git a/garden-service/src/config/config-context.ts b/garden-service/src/config/config-context.ts index 3f9d8c112a..330b381248 100644 --- a/garden-service/src/config/config-context.ts +++ b/garden-service/src/config/config-context.ts @@ -353,7 +353,7 @@ export class WorkflowConfigContext extends ProjectConfigContext { // We ignore step references here, and keep for later resolution public steps: Map | PassthroughContext - constructor(garden: Garden, variables: DeepPrimitiveMap, secrets: PrimitiveMap) { + constructor(garden: Garden) { super({ projectName: garden.projectName, artifactsPath: garden.artifactsPath, username: garden.username }) const fullEnvName = garden.namespace ? `${garden.namespace}.${garden.environmentName}` : garden.environmentName @@ -361,14 +361,14 @@ export class WorkflowConfigContext extends ProjectConfigContext { this.project = new ProjectContext(this, garden.projectName) - this.var = this.variables = variables - this.secrets = secrets + this.var = this.variables = garden.variables + this.secrets = garden.secrets this.steps = new PassthroughContext() } } -export class WorkflowStepContext extends ConfigContext { +class WorkflowStepContext extends ConfigContext { @schema(joi.string().description("The full output log from the step.")) public log: string @@ -425,7 +425,7 @@ export class WorkflowStepConfigContext extends WorkflowConfigContext { resolvedSteps: { [name: string]: WorkflowStepResult } stepName: string }) { - super(garden, garden.variables, garden.secrets) + super(garden) this.steps = new Map() @@ -498,8 +498,8 @@ export class ProviderConfigContext extends WorkflowConfigContext { ) public providers: Map - constructor(garden: Garden, resolvedProviders: ProviderMap, variables: DeepPrimitiveMap, secrets: PrimitiveMap) { - super(garden, variables, secrets) + constructor(garden: Garden, resolvedProviders: ProviderMap) { + super(garden) this.providers = new Map(Object.entries(mapValues(resolvedProviders, (p) => new ProviderContext(this, p)))) } @@ -704,8 +704,6 @@ export class ModuleConfigContext extends ProviderConfigContext { constructor({ garden, resolvedProviders, - variables, - secrets, moduleName, dependencyConfigs, dependencyVersions, @@ -713,8 +711,6 @@ export class ModuleConfigContext extends ProviderConfigContext { }: { garden: Garden resolvedProviders: ProviderMap - variables: DeepPrimitiveMap - secrets: PrimitiveMap moduleName?: string dependencyConfigs: ModuleConfig[] dependencyVersions: { [name: string]: ModuleVersion } @@ -722,7 +718,7 @@ export class ModuleConfigContext extends ProviderConfigContext { // Otherwise we pass `${runtime.*} template strings through for later resolution. runtimeContext?: RuntimeContext }) { - super(garden, resolvedProviders, variables, secrets) + super(garden, resolvedProviders) this.modules = new Map( dependencyConfigs.map( @@ -747,15 +743,11 @@ export class OutputConfigContext extends ModuleConfigContext { constructor({ garden, resolvedProviders, - variables, - secrets, modules, runtimeContext, }: { garden: Garden resolvedProviders: ProviderMap - variables: DeepPrimitiveMap - secrets: PrimitiveMap modules: Module[] runtimeContext: RuntimeContext }) { @@ -763,8 +755,6 @@ export class OutputConfigContext extends ModuleConfigContext { super({ garden, resolvedProviders, - variables, - secrets, dependencyConfigs: modules, dependencyVersions: versions, runtimeContext, diff --git a/garden-service/src/config/workflow.ts b/garden-service/src/config/workflow.ts index 61a16467b1..d7dbe0ec60 100644 --- a/garden-service/src/config/workflow.ts +++ b/garden-service/src/config/workflow.ts @@ -250,7 +250,7 @@ export interface WorkflowConfigMap { export function resolveWorkflowConfig(garden: Garden, config: WorkflowConfig) { const log = garden.log const { variables, secrets } = garden - const context = new WorkflowConfigContext(garden, variables, secrets) + const context = new WorkflowConfigContext(garden) log.silly(`Resolving template strings for workflow ${config.name}`) let resolvedConfig = resolveTemplateStrings(cloneDeep(config), context, { allowPartial: true }) log.silly(`Validating config for workflow ${config.name}`) diff --git a/garden-service/src/garden.ts b/garden-service/src/garden.ts index 664e3ade90..e19b2e4e93 100644 --- a/garden-service/src/garden.ts +++ b/garden-service/src/garden.ts @@ -697,8 +697,6 @@ export class Garden { return new OutputConfigContext({ garden: this, resolvedProviders: providers, - variables: this.variables, - secrets: this.secrets, modules, runtimeContext, }) @@ -792,8 +790,6 @@ export class Garden { const configContext = new ModuleConfigContext({ garden: this, resolvedProviders: keyBy(providers, "name"), - variables: this.variables, - secrets: this.secrets, dependencyConfigs: resolvedModules, dependencyVersions: fromPairs(resolvedModules.map((m) => [m.name, m.version])), runtimeContext, diff --git a/garden-service/src/outputs.ts b/garden-service/src/outputs.ts index 2b6a401a32..068c66b69f 100644 --- a/garden-service/src/outputs.ts +++ b/garden-service/src/outputs.ts @@ -65,8 +65,6 @@ export async function resolveProjectOutputs(garden: Garden, log: LogEntry): Prom new OutputConfigContext({ garden, resolvedProviders: {}, - variables: garden.variables, - secrets: garden.secrets, modules: [], runtimeContext: emptyRuntimeContext, }) diff --git a/garden-service/src/tasks/resolve-module.ts b/garden-service/src/tasks/resolve-module.ts index 2cafceb5aa..970697298a 100644 --- a/garden-service/src/tasks/resolve-module.ts +++ b/garden-service/src/tasks/resolve-module.ts @@ -108,8 +108,6 @@ export class ResolveModuleConfigTask extends BaseTask { const configContext = new ModuleConfigContext({ garden: this.garden, resolvedProviders: this.resolvedProviders, - variables: this.garden.variables, - secrets: this.garden.secrets, moduleName: this.moduleConfig.name, dependencyConfigs: [...dependencyConfigs, ...dependencyModules], dependencyVersions: fromPairs(dependencyModules.map((m) => [m.name, m.version])), diff --git a/garden-service/src/tasks/resolve-provider.ts b/garden-service/src/tasks/resolve-provider.ts index a9d89ee2e7..e847862a1c 100644 --- a/garden-service/src/tasks/resolve-provider.ts +++ b/garden-service/src/tasks/resolve-provider.ts @@ -144,12 +144,7 @@ export class ResolveProviderTask extends BaseTask { return alreadyResolvedProviders } - const context = new ProviderConfigContext( - this.garden, - resolvedProviders, - this.garden.variables, - this.garden.secrets - ) + const context = new ProviderConfigContext(this.garden, resolvedProviders) this.log.silly(`Resolving template strings for provider ${this.config.name}`) let resolvedConfig = resolveTemplateStrings(this.config, context) diff --git a/garden-service/src/types/plugin/command.ts b/garden-service/src/types/plugin/command.ts index 573d27fdb3..b95cc675af 100644 --- a/garden-service/src/types/plugin/command.ts +++ b/garden-service/src/types/plugin/command.ts @@ -20,16 +20,17 @@ export interface PluginCommandParams { modules: Module[] } -export const pluginParamsSchema = joi.object().keys({ - ctx: pluginContextSchema(), - args: joiArray(joi.string()).description( - "A list of arguments from the command line. This excludes any parsed global options, as well as the command name itself." - ), - log: logEntrySchema(), - modules: joiArray(moduleSchema()).description( - "If the command defnitions has `resolveModules` set to `true`, this is set to a list of all modules in the project/environment. Otherwise this is an empty list." - ), -}) +export const pluginParamsSchema = () => + joi.object().keys({ + ctx: pluginContextSchema(), + args: joiArray(joi.string()).description( + "A list of arguments from the command line. This excludes any parsed global options, as well as the command name itself." + ), + log: logEntrySchema(), + modules: joiArray(moduleSchema()).description( + "If the command defnitions has `resolveModules` set to `true`, this is set to a list of all modules in the project/environment. Otherwise this is an empty list." + ), + }) export interface PluginCommandResult { result: T diff --git a/garden-service/test/unit/src/config/config-context.ts b/garden-service/test/unit/src/config/config-context.ts index 132c68c0f8..5049909ea3 100644 --- a/garden-service/test/unit/src/config/config-context.ts +++ b/garden-service/test/unit/src/config/config-context.ts @@ -15,6 +15,8 @@ import { ProjectConfigContext, ModuleConfigContext, ProviderConfigContext, + WorkflowConfigContext, + WorkflowStepConfigContext, } from "../../../../src/config/config-context" import { expectError, makeTestGardenA, TestGarden, projectRootA, makeTestGarden } from "../../../helpers" import { join } from "path" @@ -332,14 +334,14 @@ describe("ProjectConfigContext", () => { describe("ProviderConfigContext", () => { it("should set an empty namespace and environment.fullName to environment.name if no namespace is set", async () => { const garden = await makeTestGarden(projectRootA, { environmentName: "local" }) - const c = new ProviderConfigContext(garden, await garden.resolveProviders(), {}, {}) + const c = new ProviderConfigContext(garden, await garden.resolveProviders()) expect(c.resolve({ key: ["environment", "name"], nodePath: [], opts: {} })).to.eql({ resolved: "local" }) }) it("should set environment.namespace and environment.fullName to properly if namespace is set", async () => { const garden = await makeTestGarden(projectRootA, { environmentName: "foo.local" }) - const c = new ProviderConfigContext(garden, await garden.resolveProviders(), {}, {}) + const c = new ProviderConfigContext(garden, await garden.resolveProviders()) expect(c.resolve({ key: ["environment", "name"], nodePath: [], opts: {} })).to.eql({ resolved: "local" }) expect(c.resolve({ key: ["environment", "namespace"], nodePath: [], opts: {} })).to.eql({ resolved: "foo" }) @@ -353,14 +355,13 @@ describe("ModuleConfigContext", () => { before(async () => { garden = await makeTestGardenA() + garden["secrets"] = { someSecret: "someSecretValue" } const graph = await garden.getConfigGraph(garden.log) const modules = graph.getModules() c = new ModuleConfigContext({ garden, resolvedProviders: keyBy(await garden.resolveProviders(), "name"), - variables: garden.variables, - secrets: { someSecret: "someSecretValue" }, dependencyConfigs: modules, dependencyVersions: fromPairs(modules.map((m) => [m.name, m.version])), }) @@ -368,54 +369,54 @@ describe("ModuleConfigContext", () => { it("should resolve local env variables", async () => { process.env.TEST_VARIABLE = "foo" - expect(await c.resolve({ key: ["local", "env", "TEST_VARIABLE"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["local", "env", "TEST_VARIABLE"], nodePath: [], opts: {} })).to.eql({ resolved: "foo", }) delete process.env.TEST_VARIABLE }) it("should resolve the local platform", async () => { - expect(await c.resolve({ key: ["local", "platform"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["local", "platform"], nodePath: [], opts: {} })).to.eql({ resolved: process.platform, }) }) it("should resolve the environment config", async () => { - expect(await c.resolve({ key: ["environment", "name"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["environment", "name"], nodePath: [], opts: {} })).to.eql({ resolved: garden.environmentName, }) }) it("should resolve the path of a module", async () => { const path = join(garden.projectRoot, "module-a") - expect(await c.resolve({ key: ["modules", "module-a", "path"], nodePath: [], opts: {} })).to.eql({ resolved: path }) + expect(c.resolve({ key: ["modules", "module-a", "path"], nodePath: [], opts: {} })).to.eql({ resolved: path }) }) it("should should resolve the version of a module", async () => { const config = await garden.resolveModule("module-a") const { versionString } = await garden.resolveVersion(config, []) - expect(await c.resolve({ key: ["modules", "module-a", "version"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["modules", "module-a", "version"], nodePath: [], opts: {} })).to.eql({ resolved: versionString, }) }) it("should resolve the outputs of a module", async () => { - expect(await c.resolve({ key: ["modules", "module-a", "outputs", "foo"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["modules", "module-a", "outputs", "foo"], nodePath: [], opts: {} })).to.eql({ resolved: "bar", }) }) it("should resolve a project variable", async () => { - expect(await c.resolve({ key: ["variables", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) + expect(c.resolve({ key: ["variables", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) }) it("should resolve a project variable under the var alias", async () => { - expect(await c.resolve({ key: ["var", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) + expect(c.resolve({ key: ["var", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) }) context("secrets", () => { it("should resolve a secret", async () => { - expect(await c.resolve({ key: ["secrets", "someSecret"], nodePath: [], opts: {} })).to.eql({ + expect(c.resolve({ key: ["secrets", "someSecret"], nodePath: [], opts: {} })).to.eql({ resolved: "someSecretValue", }) }) @@ -430,7 +431,7 @@ describe("ModuleConfigContext", () => { context("runtimeContext is not set", () => { it("should return runtime template strings unchanged if allowPartial=true", async () => { - expect(await c.resolve({ key: ["runtime", "some", "key"], nodePath: [], opts: { allowPartial: true } })).to.eql({ + expect(c.resolve({ key: ["runtime", "some", "key"], nodePath: [], opts: { allowPartial: true } })).to.eql({ resolved: "${runtime.some.key}", }) }) @@ -464,8 +465,6 @@ describe("ModuleConfigContext", () => { const serviceB = graph.getService("service-b") const taskB = graph.getTask("task-b") - const secrets = {} - const runtimeContext = await prepareRuntimeContext({ garden, graph, @@ -501,8 +500,6 @@ describe("ModuleConfigContext", () => { withRuntime = new ModuleConfigContext({ garden, resolvedProviders: keyBy(await garden.resolveProviders(), "name"), - variables: garden.variables, - secrets, dependencyConfigs: modules, dependencyVersions: fromPairs(modules.map((m) => [m.name, m.version])), runtimeContext, @@ -510,7 +507,7 @@ describe("ModuleConfigContext", () => { }) it("should resolve service outputs", async () => { - const result = await withRuntime.resolve({ + const result = withRuntime.resolve({ key: ["runtime", "services", "service-b", "outputs", "foo"], nodePath: [], opts: {}, @@ -519,7 +516,7 @@ describe("ModuleConfigContext", () => { }) it("should resolve task outputs", async () => { - const result = await withRuntime.resolve({ + const result = withRuntime.resolve({ key: ["runtime", "tasks", "task-b", "outputs", "moo"], nodePath: [], opts: {}, @@ -528,7 +525,7 @@ describe("ModuleConfigContext", () => { }) it("should return the template string back if allowPartial=true and outputs haven't been resolved ", async () => { - const result = await withRuntime.resolve({ + const result = withRuntime.resolve({ key: ["runtime", "services", "not-ready", "outputs", "foo"], nodePath: [], opts: { allowPartial: true }, @@ -558,3 +555,142 @@ describe("ModuleConfigContext", () => { }) }) }) + +describe("WorkflowConfigContext", () => { + let garden: TestGarden + let c: WorkflowConfigContext + + before(async () => { + garden = await makeTestGardenA() + garden["secrets"] = { someSecret: "someSecretValue" } + c = new WorkflowConfigContext(garden) + }) + + it("should resolve local env variables", async () => { + process.env.TEST_VARIABLE = "foo" + expect(c.resolve({ key: ["local", "env", "TEST_VARIABLE"], nodePath: [], opts: {} })).to.eql({ + resolved: "foo", + }) + delete process.env.TEST_VARIABLE + }) + + it("should resolve the local platform", async () => { + expect(c.resolve({ key: ["local", "platform"], nodePath: [], opts: {} })).to.eql({ + resolved: process.platform, + }) + }) + + it("should resolve the environment config", async () => { + expect(c.resolve({ key: ["environment", "name"], nodePath: [], opts: {} })).to.eql({ + resolved: garden.environmentName, + }) + }) + + it("should resolve a project variable", async () => { + expect(c.resolve({ key: ["variables", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) + }) + + it("should resolve a project variable under the var alias", async () => { + expect(c.resolve({ key: ["var", "some"], nodePath: [], opts: {} })).to.eql({ resolved: "variable" }) + }) + + it("should return a step reference back", async () => { + expect(c.resolve({ key: ["steps", "step-1", "foo"], nodePath: [], opts: {} })).to.eql({ + resolved: "${steps.step-1.foo}", + }) + }) + + context("secrets", () => { + it("should resolve a secret", async () => { + expect(c.resolve({ key: ["secrets", "someSecret"], nodePath: [], opts: {} })).to.eql({ + resolved: "someSecretValue", + }) + }) + + it("should throw when resolving a secret with a missing key", async () => { + await expectError( + () => c.resolve({ key: ["secrets", "missingSecret"], nodePath: [], opts: {} }), + (err) => expect(stripAnsi(err.message)).to.equal("Could not find key missingSecret under secrets.") + ) + }) + }) +}) + +describe("WorkflowStepConfigContext", () => { + let garden: TestGarden + + before(async () => { + garden = await makeTestGardenA() + }) + + it("should successfully resolve an output from a prior resolved step", () => { + const c = new WorkflowStepConfigContext({ + garden, + allStepNames: ["step-1", "step-2"], + resolvedSteps: { + "step-1": { + log: "bla", + number: 1, + outputs: { some: "value" }, + }, + }, + stepName: "step-2", + }) + expect(c.resolve({ key: ["steps", "step-1", "outputs", "some"], nodePath: [], opts: {} })).to.equal("value") + }) + + it("should successfully resolve the log from a prior resolved step", () => { + const c = new WorkflowStepConfigContext({ + garden, + allStepNames: ["step-1", "step-2"], + resolvedSteps: { + "step-1": { + log: "bla", + number: 1, + outputs: {}, + }, + }, + stepName: "step-2", + }) + expect(c.resolve({ key: ["steps", "step-1", "log"], nodePath: [], opts: {} })).to.equal("bla") + }) + + it("should throw error when attempting to reference a following step", () => { + const c = new WorkflowStepConfigContext({ + garden, + allStepNames: ["step-1", "step-2"], + resolvedSteps: {}, + stepName: "step-1", + }) + expectError( + () => c.resolve({ key: ["steps", "step-2", "log"], nodePath: [], opts: {} }), + (err) => expect(stripAnsi(err.message)).to.equal("") + ) + }) + + it("should throw error when attempting to reference a missing step", () => { + const c = new WorkflowStepConfigContext({ + garden, + allStepNames: ["step-1", "step-2"], + resolvedSteps: {}, + stepName: "step-1", + }) + expectError( + () => c.resolve({ key: ["steps", "step-foo", "log"], nodePath: [], opts: {} }), + (err) => expect(stripAnsi(err.message)).to.equal("") + ) + }) + + it("should throw error when attempting to reference current step", () => { + const c = new WorkflowStepConfigContext({ + garden, + allStepNames: ["step-1", "step-2"], + resolvedSteps: {}, + stepName: "step-1", + }) + expectError( + () => c.resolve({ key: ["steps", "step-1", "log"], nodePath: [], opts: {} }), + (err) => expect(stripAnsi(err.message)).to.equal("") + ) + }) +})