From d26595f6775887c3cbeb8ab6ae4ae78c5cdafe8d Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Wed, 27 Nov 2019 16:05:57 +0100 Subject: [PATCH 1/2] fix(core): error when services had runtime dependencies on task outputs --- garden-service/src/actions.ts | 1 + .../src/tasks/get-service-status.ts | 11 +- .../test-projects/exec-artifacts/garden.yml | 2 - .../exec-artifacts/module-a/garden.yml | 9 -- .../exec-artifacts/module-b/garden.yml | 9 -- garden-service/test/unit/src/tasks/deploy.ts | 139 ++++++++++++++++++ .../test/unit/src/tasks/get-service-status.ts | 132 +++++++++++++++++ 7 files changed, 278 insertions(+), 25 deletions(-) delete mode 100644 garden-service/test/unit/data/test-projects/exec-artifacts/garden.yml delete mode 100644 garden-service/test/unit/data/test-projects/exec-artifacts/module-a/garden.yml delete mode 100644 garden-service/test/unit/data/test-projects/exec-artifacts/module-b/garden.yml create mode 100644 garden-service/test/unit/src/tasks/deploy.ts create mode 100644 garden-service/test/unit/src/tasks/get-service-status.ts diff --git a/garden-service/src/actions.ts b/garden-service/src/actions.ts index 482334909a..53e6b28eb0 100644 --- a/garden-service/src/actions.ts +++ b/garden-service/src/actions.ts @@ -764,6 +764,7 @@ export class ActionRouter implements TypeGuard { const handlerParams = { ...(await this.commonParams(handler, log)), ...params, + service, module, runtimeContext, } diff --git a/garden-service/src/tasks/get-service-status.ts b/garden-service/src/tasks/get-service-status.ts index d849154755..967c4837f5 100644 --- a/garden-service/src/tasks/get-service-status.ts +++ b/garden-service/src/tasks/get-service-status.ts @@ -14,8 +14,7 @@ import { Garden } from "../garden" import { ConfigGraph } from "../config-graph" import { TaskResults } from "../task-graph" import { prepareRuntimeContext } from "../runtime-context" -import { GetTaskResultTask } from "./get-task-result" -import { getTaskVersion } from "./task" +import { getTaskVersion, TaskTask } from "./task" import Bluebird from "bluebird" export interface GetServiceStatusTaskParams { @@ -55,17 +54,19 @@ export class GetServiceStatusTask extends BaseTask { }) }) - const taskResultTasks = await Bluebird.map(deps.task, async (task) => { - return new GetTaskResultTask({ + const taskTasks = await Bluebird.map(deps.task, async (task) => { + return new TaskTask({ garden: this.garden, + graph: this.graph, log: this.log, task, force: false, + forceBuild: false, version: await getTaskVersion(this.garden, this.graph, task), }) }) - return [...statusTasks, ...taskResultTasks] + return [...statusTasks, ...taskTasks] } getName() { diff --git a/garden-service/test/unit/data/test-projects/exec-artifacts/garden.yml b/garden-service/test/unit/data/test-projects/exec-artifacts/garden.yml deleted file mode 100644 index 5cff439fd2..0000000000 --- a/garden-service/test/unit/data/test-projects/exec-artifacts/garden.yml +++ /dev/null @@ -1,2 +0,0 @@ -kind: Project -name: exec-task-outputs diff --git a/garden-service/test/unit/data/test-projects/exec-artifacts/module-a/garden.yml b/garden-service/test/unit/data/test-projects/exec-artifacts/module-a/garden.yml deleted file mode 100644 index 8b53e19079..0000000000 --- a/garden-service/test/unit/data/test-projects/exec-artifacts/module-a/garden.yml +++ /dev/null @@ -1,9 +0,0 @@ -kind: Module -name: module-a -type: exec -tasks: - - name: task-a - command: [sh, -c, '"touch foo.txt"'] - artifacts: - - source: foo.* - target: bar/ diff --git a/garden-service/test/unit/data/test-projects/exec-artifacts/module-b/garden.yml b/garden-service/test/unit/data/test-projects/exec-artifacts/module-b/garden.yml deleted file mode 100644 index 14241607ca..0000000000 --- a/garden-service/test/unit/data/test-projects/exec-artifacts/module-b/garden.yml +++ /dev/null @@ -1,9 +0,0 @@ -kind: Module -name: module-b -type: exec -tasks: - - name: task-b - dependencies: [task-a] - command: [sh, -c, '"mkdir -p moo && touch moo/boo.txt"'] - artifacts: - - source: moo/* diff --git a/garden-service/test/unit/src/tasks/deploy.ts b/garden-service/test/unit/src/tasks/deploy.ts new file mode 100644 index 0000000000..b591751fa0 --- /dev/null +++ b/garden-service/test/unit/src/tasks/deploy.ts @@ -0,0 +1,139 @@ +import tmp from "tmp-promise" +import execa from "execa" + +import { ProjectConfig } from "../../../../src/config/project" +import { DEFAULT_API_VERSION } from "../../../../src/constants" +import { Garden } from "../../../../src/garden" +import { GardenPlugin } from "../../../../src/types/plugin/plugin" +import { joi } from "../../../../src/config/common" +import { ServiceState } from "../../../../src/types/service" +import { DeployTask } from "../../../../src/tasks/deploy" +import { DeployServiceParams } from "../../../../src/types/plugin/service/deployService" +import { RunTaskParams } from "../../../../src/types/plugin/task/runTask" +import { expect } from "chai" + +describe("DeployTask", () => { + let tmpDir: tmp.DirectoryResult + let config: ProjectConfig + + before(async () => { + tmpDir = await tmp.dir({ unsafeCleanup: true }) + + await execa("git", ["init"], { cwd: tmpDir.path }) + + config = { + apiVersion: DEFAULT_API_VERSION, + kind: "Project", + name: "test", + path: tmpDir.path, + defaultEnvironment: "default", + dotIgnoreFiles: [], + environments: [{ name: "default", variables: {} }], + providers: [{ name: "test" }], + variables: {}, + } + }) + + after(async () => { + await tmpDir.cleanup() + }) + + describe("process", () => { + it("should correctly resolve runtime outputs from tasks", async () => { + const testPlugin: GardenPlugin = { + name: "test", + createModuleTypes: [ + { + name: "test", + docs: "test", + serviceOutputsSchema: joi.object().keys({ log: joi.string() }), + handlers: { + build: async () => ({}), + getServiceStatus: async () => { + return { + state: "missing", + detail: {}, + outputs: {}, + } + }, + deployService: async ({ service }: DeployServiceParams) => { + return { + state: "ready", + detail: {}, + outputs: { log: service.spec.log }, + } + }, + runTask: async ({ task }: RunTaskParams) => { + const log = task.spec.log + + return { + taskName: task.name, + moduleName: task.module.name, + success: true, + outputs: { log }, + command: [], + log, + startedAt: new Date(), + completedAt: new Date(), + version: task.module.version.versionString, + } + }, + }, + }, + ], + } + + const garden = await Garden.factory(tmpDir.path, { config, plugins: [testPlugin] }) + + garden["moduleConfigs"] = { + test: { + apiVersion: DEFAULT_API_VERSION, + name: "test", + type: "test", + allowPublish: false, + build: { dependencies: [] }, + outputs: {}, + path: tmpDir.path, + serviceConfigs: [ + { + name: "test-service", + dependencies: ["test-task"], + hotReloadable: false, + spec: { + log: "${runtime.tasks.test-task.outputs.log}", + }, + }, + ], + taskConfigs: [ + { + name: "test-task", + dependencies: [], + spec: { + log: "test output", + }, + timeout: 10, + }, + ], + testConfigs: [], + spec: { bla: "fla" }, + }, + } + + const graph = await garden.getConfigGraph() + const testService = await graph.getService("test-service") + + const deployTask = new DeployTask({ + garden, + graph, + service: testService, + force: true, + forceBuild: false, + log: garden.log, + }) + + const result = await garden.processTasks([deployTask]) + + expect(result[deployTask.getKey()]!.output.outputs).to.eql({ log: "test output" }) + }) + }) +}) diff --git a/garden-service/test/unit/src/tasks/get-service-status.ts b/garden-service/test/unit/src/tasks/get-service-status.ts new file mode 100644 index 0000000000..e322c9cc5a --- /dev/null +++ b/garden-service/test/unit/src/tasks/get-service-status.ts @@ -0,0 +1,132 @@ +import tmp from "tmp-promise" +import execa from "execa" + +import { ProjectConfig } from "../../../../src/config/project" +import { DEFAULT_API_VERSION } from "../../../../src/constants" +import { Garden } from "../../../../src/garden" +import { GardenPlugin } from "../../../../src/types/plugin/plugin" +import { joi } from "../../../../src/config/common" +import { ServiceState } from "../../../../src/types/service" +import { DeployServiceParams } from "../../../../src/types/plugin/service/deployService" +import { RunTaskParams } from "../../../../src/types/plugin/task/runTask" +import { expect } from "chai" +import { GetServiceStatusTask } from "../../../../src/tasks/get-service-status" +import { GetServiceStatusParams } from "../../../../src/types/plugin/service/getServiceStatus" + +describe("GetServiceStatusTask", () => { + let tmpDir: tmp.DirectoryResult + let config: ProjectConfig + + before(async () => { + tmpDir = await tmp.dir({ unsafeCleanup: true }) + + await execa("git", ["init"], { cwd: tmpDir.path }) + + config = { + apiVersion: DEFAULT_API_VERSION, + kind: "Project", + name: "test", + path: tmpDir.path, + defaultEnvironment: "default", + dotIgnoreFiles: [], + environments: [{ name: "default", variables: {} }], + providers: [{ name: "test" }], + variables: {}, + } + }) + + after(async () => { + await tmpDir.cleanup() + }) + + describe("process", () => { + it("should correctly resolve runtime outputs from tasks", async () => { + const testPlugin: GardenPlugin = { + name: "test", + createModuleTypes: [ + { + name: "test", + docs: "test", + serviceOutputsSchema: joi.object().keys({ log: joi.string() }), + handlers: { + build: async () => ({}), + getServiceStatus: async ({ service }: GetServiceStatusParams) => { + return { + state: "ready", + detail: {}, + outputs: { log: service.spec.log }, + } + }, + runTask: async ({ task }: RunTaskParams) => { + const log = task.spec.log + + return { + taskName: task.name, + moduleName: task.module.name, + success: true, + outputs: { log }, + command: [], + log, + startedAt: new Date(), + completedAt: new Date(), + version: task.module.version.versionString, + } + }, + }, + }, + ], + } + + const garden = await Garden.factory(tmpDir.path, { config, plugins: [testPlugin] }) + + garden["moduleConfigs"] = { + test: { + apiVersion: DEFAULT_API_VERSION, + name: "test", + type: "test", + allowPublish: false, + build: { dependencies: [] }, + outputs: {}, + path: tmpDir.path, + serviceConfigs: [ + { + name: "test-service", + dependencies: ["test-task"], + hotReloadable: false, + spec: { + log: "${runtime.tasks.test-task.outputs.log}", + }, + }, + ], + taskConfigs: [ + { + name: "test-task", + dependencies: [], + spec: { + log: "test output", + }, + timeout: 10, + }, + ], + testConfigs: [], + spec: { bla: "fla" }, + }, + } + + const graph = await garden.getConfigGraph() + const testService = await graph.getService("test-service") + + const statusTask = new GetServiceStatusTask({ + garden, + graph, + service: testService, + force: true, + log: garden.log, + }) + + const result = await garden.processTasks([statusTask]) + + expect(result[statusTask.getKey()]!.output.outputs).to.eql({ log: "test output" }) + }) + }) +}) From 8d66f8a88fe41fca66243f505cdd68f64da2d062 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Wed, 27 Nov 2019 16:18:46 +0100 Subject: [PATCH 2/2] fix(k8s): env vars weren't passed to services with `garden run service` --- .../src/plugins/kubernetes/container/run.ts | 4 +- .../test-projects/container/simple/garden.yml | 7 +++ .../plugins/kubernetes/container/container.ts | 62 +++++++++++++++++++ .../test/unit/src/tasks/get-service-status.ts | 1 - 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/garden-service/src/plugins/kubernetes/container/run.ts b/garden-service/src/plugins/kubernetes/container/run.ts index 3df9b7f884..f7d09d3a29 100644 --- a/garden-service/src/plugins/kubernetes/container/run.ts +++ b/garden-service/src/plugins/kubernetes/container/run.ts @@ -36,7 +36,9 @@ export async function runContainerService({ timeout, log, }: RunServiceParams): Promise { - const { command, args } = service.spec + const { command, args, env } = service.spec + + runtimeContext.envVars = { ...runtimeContext.envVars, ...env } return runContainerModule({ ctx, diff --git a/garden-service/test/data/test-projects/container/simple/garden.yml b/garden-service/test/data/test-projects/container/simple/garden.yml index 38ca49bf33..ef7622fcd0 100644 --- a/garden-service/test/data/test-projects/container/simple/garden.yml +++ b/garden-service/test/data/test-projects/container/simple/garden.yml @@ -2,6 +2,13 @@ kind: Module name: simple type: container image: busybox +services: + - name: echo-service + command: [sh, -c, "echo ok"] + - name: env-service + command: [sh, -c, "echo $ENV_VAR"] + env: + ENV_VAR: foo tasks: - name: echo-task command: [sh, -c, "echo ok"] diff --git a/garden-service/test/integ/src/plugins/kubernetes/container/container.ts b/garden-service/test/integ/src/plugins/kubernetes/container/container.ts index bae1b578ea..c256d3f5b7 100644 --- a/garden-service/test/integ/src/plugins/kubernetes/container/container.ts +++ b/garden-service/test/integ/src/plugins/kubernetes/container/container.ts @@ -12,6 +12,8 @@ import { TaskTask } from "../../../../../../src/tasks/task" import { runAndCopy } from "../../../../../../src/plugins/kubernetes/run" import { Provider } from "../../../../../../src/config/provider" import { containerHelpers } from "../../../../../../src/plugins/container/helpers" +import { runContainerService } from "../../../../../../src/plugins/kubernetes/container/run" +import { prepareRuntimeContext } from "../../../../../../src/runtime-context" describe("kubernetes container module handlers", () => { let garden: Garden @@ -234,6 +236,66 @@ describe("kubernetes container module handlers", () => { }) }) + describe("runContainerService", () => { + it("should run a service", async () => { + const service = await graph.getService("echo-service") + + const runtimeContext = await prepareRuntimeContext({ + garden, + graph, + dependencies: { + build: [], + service: [], + task: [], + test: [], + }, + module: service.module, + serviceStatuses: {}, + taskResults: {}, + }) + + const result = await runContainerService({ + ctx: garden.getPluginContext(provider), + log: garden.log, + service, + module: service.module, + interactive: false, + runtimeContext, + }) + + expect(result.log.trim()).to.eql("ok") + }) + + it("should add configured env vars to the runtime context", async () => { + const service = await graph.getService("env-service") + + const runtimeContext = await prepareRuntimeContext({ + garden, + graph, + dependencies: { + build: [], + service: [], + task: [], + test: [], + }, + module: service.module, + serviceStatuses: {}, + taskResults: {}, + }) + + const result = await runContainerService({ + ctx: garden.getPluginContext(provider), + log: garden.log, + service, + module: service.module, + interactive: false, + runtimeContext, + }) + + expect(result.log.trim()).to.eql("foo") + }) + }) + describe("runContainerTask", () => { it("should run a basic task", async () => { const task = await graph.getTask("echo-task") diff --git a/garden-service/test/unit/src/tasks/get-service-status.ts b/garden-service/test/unit/src/tasks/get-service-status.ts index e322c9cc5a..1950fe9e64 100644 --- a/garden-service/test/unit/src/tasks/get-service-status.ts +++ b/garden-service/test/unit/src/tasks/get-service-status.ts @@ -7,7 +7,6 @@ import { Garden } from "../../../../src/garden" import { GardenPlugin } from "../../../../src/types/plugin/plugin" import { joi } from "../../../../src/config/common" import { ServiceState } from "../../../../src/types/service" -import { DeployServiceParams } from "../../../../src/types/plugin/service/deployService" import { RunTaskParams } from "../../../../src/types/plugin/task/runTask" import { expect } from "chai" import { GetServiceStatusTask } from "../../../../src/tasks/get-service-status"