From 857e8458186e0f8f6d370ea134856cecf349c5a4 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Thu, 4 Feb 2021 22:54:33 +0100 Subject: [PATCH] feat(workflows): add envVars field for script steps Nice and simple, what it says on the tin. --- core/src/commands/run/workflow.ts | 2 +- core/src/config/workflow.ts | 6 +++++- core/src/plugins/exec.ts | 2 +- core/src/util/util.ts | 14 +++++++++++++- core/test/unit/src/commands/run/workflow.ts | 21 +++++++++++++++++++++ core/test/unit/src/config/workflow.ts | 9 +++++---- docs/reference/commands.md | 5 +++++ docs/reference/workflow-config.md | 13 +++++++++++++ 8 files changed, 64 insertions(+), 8 deletions(-) diff --git a/core/src/commands/run/workflow.ts b/core/src/commands/run/workflow.ts index 35545428d0..13b9b3c103 100644 --- a/core/src/commands/run/workflow.ts +++ b/core/src/commands/run/workflow.ts @@ -327,7 +327,7 @@ export async function runStepCommand({ export async function runStepScript({ garden, bodyLog, step }: RunStepParams): Promise> { try { - await runScript(bodyLog, garden.projectRoot, step.script!) + await runScript({ log: bodyLog, cwd: garden.projectRoot, script: step.script!, envVars: step.envVars }) return { result: {} } } catch (_err) { const error = _err as ExecaError diff --git a/core/src/config/workflow.ts b/core/src/config/workflow.ts index f8c0947803..80e9edec1b 100644 --- a/core/src/config/workflow.ts +++ b/core/src/config/workflow.ts @@ -7,7 +7,7 @@ */ import { isEqual, merge, omit, take } from "lodash" -import { joi, joiUserIdentifier, joiVariableName, joiIdentifier } from "./common" +import { joi, joiUserIdentifier, joiVariableName, joiIdentifier, joiEnvVars, PrimitiveMap } from "./common" import { DEFAULT_API_VERSION } from "../constants" import { deline, dedent } from "../util/string" import { defaultContainerLimits, ServiceLimitSpec } from "../plugins/container/config" @@ -142,6 +142,7 @@ export interface WorkflowStepSpec { name?: string command?: string[] description?: string + envVars?: PrimitiveMap script?: string skip?: boolean when?: workflowStepModifier @@ -183,6 +184,9 @@ export const workflowStepSchema = () => { ) .example(["run", "task", "my-task"]), description: joi.string().description("A description of the workflow step."), + envVars: joiEnvVars().description( + "A map of environment variables to use when running script steps. Ignored for `command` steps." + ), script: joi.string().description( dedent` A bash script to run. Note that the host running the workflow must have bash installed and on path. diff --git a/core/src/plugins/exec.ts b/core/src/plugins/exec.ts index 81e870337a..b3fc6cb1a1 100644 --- a/core/src/plugins/exec.ts +++ b/core/src/plugins/exec.ts @@ -387,7 +387,7 @@ export const execPlugin = () => if (ctx.provider.config.initScript) { try { log.info({ section: "exec", msg: "Running init script" }) - await runScript(log, ctx.projectRoot, ctx.provider.config.initScript) + await runScript({ log, cwd: ctx.projectRoot, script: ctx.provider.config.initScript }) } catch (_err) { const error = _err as ExecaError diff --git a/core/src/util/util.ts b/core/src/util/util.ts index 2e7958c01c..1182deed73 100644 --- a/core/src/util/util.ts +++ b/core/src/util/util.ts @@ -25,6 +25,7 @@ import { tailString, dedent } from "./string" import { Writable, Readable } from "stream" import { LogEntry } from "../logger/log-entry" import execa = require("execa") +import { PrimitiveMap } from "../config/common" export { v4 as uuidv4 } from "uuid" export type HookCallback = (callback?: () => void) => void @@ -637,7 +638,17 @@ export function getDurationMsec(start: Date, end: Date): number { return Math.round(end.getTime() - start.getTime()) } -export async function runScript(log: LogEntry, cwd: string, script: string) { +export async function runScript({ + log, + cwd, + script, + envVars, +}: { + log: LogEntry + cwd: string + script: string + envVars?: PrimitiveMap +}) { // Run the script, capturing any errors const proc = execa("bash", ["-s"], { all: true, @@ -647,6 +658,7 @@ export async function runScript(log: LogEntry, cwd: string, script: string) { // Set a very large max buffer (we only hold one of these at a time, and want to avoid overflow errors) buffer: true, maxBuffer: 100 * 1024 * 1024, + env: mapValues(envVars, (v) => (v === undefined ? undefined : "" + v)), }) // Stream output to `log`, splitting by line diff --git a/core/test/unit/src/commands/run/workflow.ts b/core/test/unit/src/commands/run/workflow.ts index 62bb36f571..f358ebabbf 100644 --- a/core/test/unit/src/commands/run/workflow.ts +++ b/core/test/unit/src/commands/run/workflow.ts @@ -469,6 +469,27 @@ describe("RunWorkflowCommand", () => { expect(result?.steps["step-1"].log).to.equal(garden.projectRoot) }) + it("should apply configured envVars when running script steps", async () => { + garden.setWorkflowConfigs([ + { + apiVersion: DEFAULT_API_VERSION, + name: "workflow-a", + kind: "Workflow", + path: garden.projectRoot, + files: [], + steps: [{ script: "echo $FOO $BAR", envVars: { FOO: "foo", BAR: 123 } }], + }, + ]) + + await cmd.action({ ...defaultParams, args: { workflow: "workflow-a" } }) + + const { result, errors } = await cmd.action({ ...defaultParams, args: { workflow: "workflow-a" } }) + + expect(result).to.exist + expect(errors).to.not.exist + expect(result?.steps["step-1"].log).to.equal("foo 123") + }) + it("should skip disabled steps", async () => { garden.setWorkflowConfigs([ { diff --git a/core/test/unit/src/config/workflow.ts b/core/test/unit/src/config/workflow.ts index b45b7a2517..1c26a9a96a 100644 --- a/core/test/unit/src/config/workflow.ts +++ b/core/test/unit/src/config/workflow.ts @@ -42,8 +42,8 @@ describe("resolveWorkflowConfig", () => { path: "/tmp/foo", description: "Sample workflow", steps: [ - { description: "Deploy the stack", command: ["deploy"], skip: false, when: "onSuccess" }, - { command: ["test"], skip: false, when: "onSuccess" }, + { description: "Deploy the stack", command: ["deploy"], skip: false, when: "onSuccess", envVars: {} }, + { command: ["test"], skip: false, when: "onSuccess", envVars: {} }, ], triggers: [ { @@ -77,6 +77,7 @@ describe("resolveWorkflowConfig", () => { command: ["deploy"], skip: ("${var.skip}" as unknown) as boolean, when: "onSuccess", + envVars: {}, }, ], } @@ -170,8 +171,8 @@ describe("resolveWorkflowConfig", () => { ...config, ...defaults, steps: [ - { description: "Deploy the stack", command: ["deploy"], skip: false, when: "onSuccess" }, - { command: ["test"], skip: false, when: "onSuccess" }, + { description: "Deploy the stack", command: ["deploy"], skip: false, when: "onSuccess", envVars: {} }, + { command: ["test"], skip: false, when: "onSuccess", envVars: {} }, ], }) }) diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 5cefe58ae4..18e2f62bb1 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -1227,6 +1227,11 @@ workflowConfigs: # A description of the workflow step. description: + # A map of environment variables to use when running script steps. Ignored for `command` steps. + envVars: + # Number, string or boolean + : + # 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, diff --git a/docs/reference/workflow-config.md b/docs/reference/workflow-config.md index 51b83310a6..00f50ea367 100644 --- a/docs/reference/workflow-config.md +++ b/docs/reference/workflow-config.md @@ -103,6 +103,9 @@ steps: # A description of the workflow step. description: + # A map of environment variables to use when running script steps. Ignored for `command` steps. + envVars: {} + # 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. @@ -368,6 +371,16 @@ A description of the workflow step. | -------- | -------- | | `string` | No | +### `steps[].envVars` + +[steps](#steps) > envVars + +A map of environment variables to use when running script steps. Ignored for `command` steps. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `object` | `{}` | No | + ### `steps[].script` [steps](#steps) > script