Skip to content

Commit

Permalink
feat(workflows): only resolve config being run
Browse files Browse the repository at this point in the history
In the `run workflow` command, we now only resolve the workflow config
being run.

This adds flexibility when some workflows reference e.g. secrets that
are created by other workflows.
  • Loading branch information
thsig committed Apr 22, 2021
1 parent ba225b9 commit 3ad56c8
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 23 deletions.
2 changes: 1 addition & 1 deletion core/src/commands/run/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class RunWorkflowCommand extends Command<Args, {}> {
async action({ garden, log, args, opts }: CommandParams<Args, {}>): Promise<CommandResult<WorkflowRunOutput>> {
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))) {
Expand Down
12 changes: 12 additions & 0 deletions core/src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,6 +27,17 @@ export class ValidateCommand extends Command {
async action({ garden, log }: CommandParams): Promise<CommandResult> {
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 {}
}
}
22 changes: 17 additions & 5 deletions core/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkflowConfig> {
return resolveWorkflowConfig(this, await this.getRawWorkflowConfig(name))
}

async getRawWorkflowConfig(name: string): Promise<WorkflowConfig> {
return (await this.getRawWorkflowConfigs([name]))[0]
}

getWorkflowConfigs(names?: string[]): WorkflowConfig[] {
async getRawWorkflowConfigs(names?: string[]): Promise<WorkflowConfig[]> {
if (!this.configsScanned) {
await this.scanAndAddConfigs()
}
if (names) {
return Object.values(pickKeys(this.workflowConfigs, names, "workflow"))
} else {
Expand Down Expand Up @@ -971,7 +981,7 @@ export class Garden {
})
}

this.workflowConfigs[key] = resolveWorkflowConfig(this, config)
this.workflowConfigs[key] = config
}

/**
Expand Down Expand Up @@ -1072,13 +1082,16 @@ export class Garden {
}): Promise<ConfigDump> {
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),
Expand All @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions core/test/data/validate/bad-workflow/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
kind: Project
name: bad-workflow-project

---

kind: Workflow
steps:
- script: [echo, "${var.MISSING_VAR}"]

26 changes: 26 additions & 0 deletions core/test/unit/src/commands/run/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions core/test/unit/src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
})
})
18 changes: 1 addition & 17 deletions core/test/unit/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
})

Expand Down Expand Up @@ -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", () => {
Expand Down

0 comments on commit 3ad56c8

Please sign in to comment.