diff --git a/core/src/commands/run/workflow.ts b/core/src/commands/run/workflow.ts index eaef1c4bc3..5775746279 100644 --- a/core/src/commands/run/workflow.ts +++ b/core/src/commands/run/workflow.ts @@ -71,7 +71,7 @@ export class RunWorkflowCommand extends Command { async action({ garden, log, args, opts }: CommandParams): Promise> { const outerLog = log.placeholder() // Prepare any configured files before continuing - const workflow = garden.getWorkflowConfig(args.workflow) + const workflow = await garden.getWorkflowConfig(args.workflow) // Merge any workflow-level environment variables into process.env. for (const [key, value] of Object.entries(toEnvVars(workflow.envVars))) { diff --git a/core/src/commands/validate.ts b/core/src/commands/validate.ts index 0bfe831dc0..3f7048923b 100644 --- a/core/src/commands/validate.ts +++ b/core/src/commands/validate.ts @@ -9,6 +9,7 @@ import { Command, CommandParams, CommandResult } from "./base" import { printHeader } from "../logger/util" import dedent = require("dedent") +import { resolveWorkflowConfig } from "../config/workflow" export class ValidateCommand extends Command { name = "validate" @@ -26,6 +27,17 @@ export class ValidateCommand extends Command { async action({ garden, log }: CommandParams): Promise { await garden.getConfigGraph(log) + /* + * Normally, workflow configs are only resolved when they're run via the `run workflow` command (and only the + * workflow being run). + * + * Here, we want to validate all workflow configs (so we try resolving them all). + */ + const rawWorkflowConfigs = await garden.getRawWorkflowConfigs() + for (const config of rawWorkflowConfigs) { + resolveWorkflowConfig(garden, config) + } + return {} } } diff --git a/core/src/garden.ts b/core/src/garden.ts index ad3f1ecade..2adcc6e405 100644 --- a/core/src/garden.ts +++ b/core/src/garden.ts @@ -560,11 +560,21 @@ export class Garden { return this.tools } - getWorkflowConfig(name: string): WorkflowConfig { - return this.getWorkflowConfigs([name])[0] + /** + * When running workflows via the `run workflow` command, we only resolve the workflow being executed. + */ + async getWorkflowConfig(name: string): Promise { + return resolveWorkflowConfig(this, await this.getRawWorkflowConfig(name)) + } + + async getRawWorkflowConfig(name: string): Promise { + return (await this.getRawWorkflowConfigs([name]))[0] } - getWorkflowConfigs(names?: string[]): WorkflowConfig[] { + async getRawWorkflowConfigs(names?: string[]): Promise { + if (!this.configsScanned) { + await this.scanAndAddConfigs() + } if (names) { return Object.values(pickKeys(this.workflowConfigs, names, "workflow")) } else { @@ -971,7 +981,7 @@ export class Garden { }) } - this.workflowConfigs[key] = resolveWorkflowConfig(this, config) + this.workflowConfigs[key] = config } /** @@ -1072,13 +1082,16 @@ export class Garden { }): Promise { let providers: ConfigDump["providers"] = [] let moduleConfigs: ModuleConfig[] + let workflowConfigs: WorkflowConfig[] if (partial) { providers = this.getRawProviderConfigs() moduleConfigs = await this.getRawModuleConfigs() + workflowConfigs = await this.getRawWorkflowConfigs() } else { const graph = await this.getConfigGraph(log) const modules = graph.getModules({ includeDisabled }) + workflowConfigs = (await this.getRawWorkflowConfigs()).map((config) => resolveWorkflowConfig(this, config)) moduleConfigs = sortBy( modules.map((m) => m._config), @@ -1088,7 +1101,6 @@ export class Garden { providers = Object.values(await this.resolveProviders(log)) } - const workflowConfigs = await this.getWorkflowConfigs() const allEnvironmentNames = this.environmentConfigs.map((c) => c.name) return { diff --git a/core/test/data/validate/bad-workflow/garden.yml b/core/test/data/validate/bad-workflow/garden.yml new file mode 100644 index 0000000000..76670f0e5b --- /dev/null +++ b/core/test/data/validate/bad-workflow/garden.yml @@ -0,0 +1,9 @@ +kind: Project +name: bad-workflow-project + +--- + +kind: Workflow +steps: + - script: [echo, "${var.MISSING_VAR}"] + diff --git a/core/test/unit/src/commands/run/workflow.ts b/core/test/unit/src/commands/run/workflow.ts index 061c3ad989..ffe67f7869 100644 --- a/core/test/unit/src/commands/run/workflow.ts +++ b/core/test/unit/src/commands/run/workflow.ts @@ -812,6 +812,32 @@ describe("RunWorkflowCommand", () => { expect(result).to.exist expect(result?.steps["step-2"].log).to.equal("task-a") }) + + it("should only resolve the workflow that's being run", async () => { + garden.setWorkflowConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + name: "workflow-to-run", + kind: "Workflow", + path: garden.projectRoot, + envVars: {}, + resources: defaultWorkflowResources, + steps: [{ command: ["deploy"] }], + }, + { + apiVersion: DEFAULT_API_VERSION, + name: "some-other-workflow", + kind: "Workflow", + path: garden.projectRoot, + envVars: { FOO: "${secrets.missing}" }, // <--- should not be resolved, so no error should be thrown + resources: defaultWorkflowResources, + steps: [{ command: ["deploy"] }], + }, + ]) + + // This workflow should run without errors, despite a missing secret being referenced in a separate workflow config. + await cmd.action({ ...defaultParams, args: { workflow: "workflow-to-run" } }) + }) }) function getWorkflowEvents(garden: TestGarden) { diff --git a/core/test/unit/src/commands/validate.ts b/core/test/unit/src/commands/validate.ts index 64ddffcb5d..b52b168f4c 100644 --- a/core/test/unit/src/commands/validate.ts +++ b/core/test/unit/src/commands/validate.ts @@ -51,4 +51,24 @@ describe("commands.validate", () => { "configuration" ) }) + + it("should fail validating the bad-workflow project", async () => { + const root = join(dataDir, "validate", "bad-workflow") + const garden = await TestGarden.factory(root) + const log = garden.log + const command = new ValidateCommand() + + await expectError( + async () => + await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: {}, + opts: withDefaultGlobalOpts({}), + }), + "configuration" + ) + }) }) diff --git a/core/test/unit/src/garden.ts b/core/test/unit/src/garden.ts index d3e07ca837..9fa9997c94 100644 --- a/core/test/unit/src/garden.ts +++ b/core/test/unit/src/garden.ts @@ -2261,7 +2261,7 @@ describe("Garden", () => { const garden = await makeTestGarden(resolve(dataDir, "test-projects", "custom-config-names")) await garden.scanAndAddConfigs() - const workflows = garden.getWorkflowConfigs() + const workflows = await garden.getRawWorkflowConfigs() expect(getNames(workflows)).to.eql(["workflow-a", "workflow-b"]) }) @@ -2467,22 +2467,6 @@ describe("Garden", () => { (err) => expect(err.message).to.match(/Module module-a: missing/) ) }) - - it.skip("should throw an error if references to missing secrets are present in a workflow config", async () => { - const garden = await makeTestGarden(join(dataDir, "missing-secrets", "workflow")) - await expectError( - () => garden.scanAndAddConfigs(), - (err) => expect(err.message).to.match(/Workflow test-workflow: missing/) - ) - }) - - it("should throw an error if an invalid workflow config is present", async () => { - const garden = await makeTestGarden(join(dataDir, "test-project-invalid-workflow")) - await expectError( - () => garden.scanAndAddConfigs(), - (err) => expect(stripAnsi(err.message)).to.match(/key .triggers must be an array/) - ) - }) }) describe("resolveModules", () => {