diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index 5881ac87cb..2136951e5a 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -265,6 +265,22 @@ Annotations to attach to the service (Note: May not be applicable to all provide | Type | Required | | ---- | -------- | | `object` | No +### `services[].command[]` +[services](#services) > command + +The command/entrypoint to run the container with when starting the service. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +services: + - command: + - /bin/sh + - '-c' +``` ### `services[].args[]` [services](#services) > args @@ -273,6 +289,14 @@ The arguments to run the container with when starting the service. | Type | Required | | ---- | -------- | | `array[string]` | No + +Example: +```yaml +services: + - args: + - npm + - start +``` ### `services[].daemon` [services](#services) > daemon @@ -396,14 +420,39 @@ Set this to check the service's health by checking if this TCP port is accepting | Type | Required | | ---- | -------- | | `string` | No +### `services[].hotReloadCommand[]` +[services](#services) > hotReloadCommand + +If this module uses the `hotReload` field, the container will be run with this command/entrypoint when the service is deployed with hot reloading enabled. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +services: + - hotReloadCommand: + - /bin/sh + - '-c' +``` ### `services[].hotReloadArgs[]` [services](#services) > hotReloadArgs -If this module uses the `hotReload` field, the container will be run with these arguments instead of those in `args` when the service is deployed with hot reloading enabled. +If this module uses the `hotReload` field, the container will be run with these arguments when the service is deployed with hot reloading enabled. | Type | Required | | ---- | -------- | | `array[string]` | No + +Example: +```yaml +services: + - hotReloadArgs: + - npm + - run + - dev +``` ### `services[].limits` [services](#services) > limits @@ -563,6 +612,22 @@ Maximum duration (in seconds) of the test run. | Type | Required | | ---- | -------- | | `number` | No +### `tests[].command[]` +[tests](#tests) > command + +The command/entrypoint used to run the test inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +tests: + - command: + - /bin/sh + - '-c' +``` ### `tests[].args[]` [tests](#tests) > args @@ -626,6 +691,22 @@ Maximum duration (in seconds) of the task's execution. | Type | Required | | ---- | -------- | | `number` | No +### `tasks[].command[]` +[tasks](#tasks) > command + +The command/entrypoint used to run the task inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +tasks: + - command: + - /bin/sh + - '-c' +``` ### `tasks[].args[]` [tasks](#tasks) > args @@ -680,6 +761,7 @@ services: - name: dependencies: [] annotations: {} + command: args: daemon: false ingresses: @@ -695,6 +777,7 @@ services: scheme: HTTP command: tcpPort: + hotReloadCommand: hotReloadArgs: limits: cpu: 1000 @@ -714,6 +797,7 @@ tests: - name: dependencies: [] timeout: null + command: args: env: {} tasks: @@ -721,6 +805,7 @@ tasks: description: dependencies: [] timeout: null + command: args: env: {} ``` \ No newline at end of file diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index 5eb313b235..e578188f18 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -270,6 +270,22 @@ Annotations to attach to the service (Note: May not be applicable to all provide | Type | Required | | ---- | -------- | | `object` | No +### `services[].command[]` +[services](#services) > command + +The command/entrypoint to run the container with when starting the service. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +services: + - command: + - /bin/sh + - '-c' +``` ### `services[].args[]` [services](#services) > args @@ -278,6 +294,14 @@ The arguments to run the container with when starting the service. | Type | Required | | ---- | -------- | | `array[string]` | No + +Example: +```yaml +services: + - args: + - npm + - start +``` ### `services[].daemon` [services](#services) > daemon @@ -401,14 +425,39 @@ Set this to check the service's health by checking if this TCP port is accepting | Type | Required | | ---- | -------- | | `string` | No +### `services[].hotReloadCommand[]` +[services](#services) > hotReloadCommand + +If this module uses the `hotReload` field, the container will be run with this command/entrypoint when the service is deployed with hot reloading enabled. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +services: + - hotReloadCommand: + - /bin/sh + - '-c' +``` ### `services[].hotReloadArgs[]` [services](#services) > hotReloadArgs -If this module uses the `hotReload` field, the container will be run with these arguments instead of those in `args` when the service is deployed with hot reloading enabled. +If this module uses the `hotReload` field, the container will be run with these arguments when the service is deployed with hot reloading enabled. | Type | Required | | ---- | -------- | | `array[string]` | No + +Example: +```yaml +services: + - hotReloadArgs: + - npm + - run + - dev +``` ### `services[].limits` [services](#services) > limits @@ -568,6 +617,22 @@ Maximum duration (in seconds) of the test run. | Type | Required | | ---- | -------- | | `number` | No +### `tests[].command[]` +[tests](#tests) > command + +The command/entrypoint used to run the test inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +tests: + - command: + - /bin/sh + - '-c' +``` ### `tests[].args[]` [tests](#tests) > args @@ -631,6 +696,22 @@ Maximum duration (in seconds) of the task's execution. | Type | Required | | ---- | -------- | | `number` | No +### `tasks[].command[]` +[tasks](#tasks) > command + +The command/entrypoint used to run the task inside the container. + +| Type | Required | +| ---- | -------- | +| `array[string]` | No + +Example: +```yaml +tasks: + - command: + - /bin/sh + - '-c' +``` ### `tasks[].args[]` [tasks](#tasks) > args @@ -711,6 +792,7 @@ services: - name: dependencies: [] annotations: {} + command: args: daemon: false ingresses: @@ -726,6 +808,7 @@ services: scheme: HTTP command: tcpPort: + hotReloadCommand: hotReloadArgs: limits: cpu: 1000 @@ -745,6 +828,7 @@ tests: - name: dependencies: [] timeout: null + command: args: env: {} tasks: @@ -752,6 +836,7 @@ tasks: description: dependencies: [] timeout: null + command: args: env: {} jarPath: diff --git a/garden-service/package-lock.json b/garden-service/package-lock.json index 65190738a8..a25af01593 100644 --- a/garden-service/package-lock.json +++ b/garden-service/package-lock.json @@ -157,7 +157,7 @@ "dependencies": { "chalk": { "version": "2.3.1", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { @@ -3312,7 +3312,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -4487,7 +4487,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -5168,7 +5168,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -5311,7 +5311,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -6171,7 +6171,7 @@ }, "got": { "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -6964,7 +6964,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -7339,7 +7339,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -7546,7 +7546,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "escodegen": { @@ -9320,7 +9320,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, @@ -9367,7 +9367,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -9387,7 +9387,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -9396,7 +9396,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -9412,7 +9412,7 @@ }, "yargs": { "version": "3.32.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "dev": true, "requires": { @@ -10498,7 +10498,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -10703,7 +10703,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" }, "plugin-error": { @@ -10725,7 +10725,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "requires": { "core-util-is": "~1.0.0", @@ -10743,12 +10743,12 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "through2": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/through2/-/through2-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", "requires": { "readable-stream": "~2.0.0", @@ -11090,7 +11090,7 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { @@ -13217,7 +13217,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, diff --git a/garden-service/src/commands/run/module.ts b/garden-service/src/commands/run/module.ts index f5bf5d3b8b..134455ff7b 100644 --- a/garden-service/src/commands/run/module.ts +++ b/garden-service/src/commands/run/module.ts @@ -85,8 +85,6 @@ export class RunModuleCommand extends Command { const buildTask = new BuildTask({ garden, log, module, force: opts["force-build"] }) await garden.processTasks([buildTask]) - const command = args.command || [] - const runtimeContext = await runtimeContextForServiceDeps(garden, graph, module) printRuntimeContext(log, runtimeContext) @@ -96,7 +94,7 @@ export class RunModuleCommand extends Command { const result = await actions.runModule({ log, module, - command, + args: args.command || [], runtimeContext, interactive: opts.interactive, }) diff --git a/garden-service/src/plugins/container/config.ts b/garden-service/src/plugins/container/config.ts index 04853d0de3..2fdab1551b 100644 --- a/garden-service/src/plugins/container/config.ts +++ b/garden-service/src/plugins/container/config.ts @@ -77,17 +77,21 @@ interface Annotations { export interface ContainerServiceSpec extends CommonServiceSpec { annotations: Annotations, + command?: string[], args: string[], daemon: boolean ingresses: ContainerIngressSpec[], env: PrimitiveMap, healthCheck?: ServiceHealthCheckSpec, + hotReloadCommand?: string[], hotReloadArgs?: string[], limits: ServiceLimitSpec, ports: ServicePortSpec[], volumes: ServiceVolumeSpec[], } +const commandExample = ["/bin/sh", "-c"] + const hotReloadSyncSchema = Joi.object() .keys({ source: Joi.string().uri({ relativeOnly: true }) @@ -221,8 +225,12 @@ const serviceSchema = baseServiceSpecSchema .keys({ annotations: annotationsSchema .description("Annotations to attach to the service (Note: May not be applicable to all providers)"), + command: Joi.array().items(Joi.string()) + .description("The command/entrypoint to run the container with when starting the service.") + .example([commandExample, {}]), args: Joi.array().items(Joi.string()) - .description("The arguments to run the container with when starting the service."), + .description("The arguments to run the container with when starting the service.") + .example([["npm", "start"], {}]), daemon: Joi.boolean() .default(false) .description("Whether to run the service as a daemon (to ensure only one runs per node)."), @@ -235,12 +243,16 @@ const serviceSchema = baseServiceSpecSchema env: joiEnvVars(), healthCheck: healthCheckSchema .description("Specify how the service's health should be checked after deploying."), + hotReloadCommand: Joi.array().items(Joi.string()) + .description(deline` + If this module uses the \`hotReload\` field, the container will be run with + this command/entrypoint when the service is deployed with hot reloading enabled.`) + .example([commandExample, {}]), hotReloadArgs: Joi.array().items(Joi.string()) .description(deline` If this module uses the \`hotReload\` field, the container will be run with - these arguments instead of those in \`args\` when the service is deployed - with hot reloading enabled.`, - ), + these arguments when the service is deployed with hot reloading enabled.`) + .example([["npm", "run", "dev"], {}]), limits: limitsSchema .description("Specify resource limits for the service.") .default(() => defaultContainerLimits, JSON.stringify(defaultContainerLimits)), @@ -280,12 +292,16 @@ export const containerRegistryConfigSchema = Joi.object() export interface ContainerService extends Service { } export interface ContainerTestSpec extends BaseTestSpec { + command?: string[], args: string[], env: { [key: string]: string }, } export const containerTestSchema = baseTestSpecSchema .keys({ + command: Joi.array().items(Joi.string()) + .description("The command/entrypoint used to run the test inside the container.") + .example([commandExample, {}]), args: Joi.array().items(Joi.string()) .description("The arguments used to run the test inside the container.") .example([["npm", "test"], {}]), @@ -293,12 +309,16 @@ export const containerTestSchema = baseTestSpecSchema }) export interface ContainerTaskSpec extends BaseTaskSpec { + command?: string[], args: string[] env: PrimitiveMap } export const containerTaskSchema = baseTaskSpecSchema .keys({ + command: Joi.array().items(Joi.string()) + .description("The command/entrypoint used to run the task inside the container.") + .example([commandExample, {}]), args: Joi.array().items(Joi.string()) .description("The arguments used to run the task inside the container.") .example([["rake", "db:migrate"], {}]), diff --git a/garden-service/src/plugins/container/helpers.ts b/garden-service/src/plugins/container/helpers.ts index 0bcafac786..918e7b4f35 100644 --- a/garden-service/src/plugins/container/helpers.ts +++ b/garden-service/src/plugins/container/helpers.ts @@ -15,6 +15,7 @@ import { ModuleConfig } from "../../config/module" import { ContainerModule, ContainerRegistryConfig, defaultTag, defaultNamespace, ContainerModuleConfig } from "./config" export const minDockerVersion = "17.07.0" +export const defaultContainerCommand = ["/bin/sh", "-c"] interface ParsedImageId { host?: string diff --git a/garden-service/src/plugins/kubernetes/container/deployment.ts b/garden-service/src/plugins/kubernetes/container/deployment.ts index f70e35c95a..4febf887fa 100644 --- a/garden-service/src/plugins/kubernetes/container/deployment.ts +++ b/garden-service/src/plugins/kubernetes/container/deployment.ts @@ -133,6 +133,10 @@ export async function createDeployment( imagePullPolicy: "IfNotPresent", } + if (service.spec.command && service.spec.command.length > 0) { + container.command = service.spec.command + } + if (service.spec.args && service.spec.args.length > 0) { container.args = service.spec.args } @@ -219,6 +223,7 @@ export async function createDeployment( configureHotReload({ target: deployment, hotReloadSpec, + hotReloadCommand: service.spec.hotReloadCommand, hotReloadArgs: service.spec.hotReloadArgs, }) } diff --git a/garden-service/src/plugins/kubernetes/container/run.ts b/garden-service/src/plugins/kubernetes/container/run.ts index f570fee9db..a14d9e8c06 100644 --- a/garden-service/src/plugins/kubernetes/container/run.ts +++ b/garden-service/src/plugins/kubernetes/container/run.ts @@ -81,7 +81,7 @@ export async function execInService(params: ExecInServiceParams export async function runContainerModule( { - ctx, log, module, command, ignoreError = true, interactive, runtimeContext, timeout, + ctx, log, module, args, command, ignoreError = true, interactive, runtimeContext, timeout, }: RunModuleParams, ): Promise { const provider = ctx.provider @@ -94,7 +94,8 @@ export async function runContainerModule( namespace, module, envVars: runtimeContext.envVars, - args: command, + command, + args, image, interactive, ignoreError, @@ -105,11 +106,13 @@ export async function runContainerModule( export async function runContainerService( { ctx, service, interactive, runtimeContext, timeout, log }: RunServiceParams, -) { +): Promise { + const { command, args } = service.spec return runContainerModule({ ctx, module: service.module, - command: service.spec.args || [], + command, + args, interactive, runtimeContext, timeout, @@ -126,13 +129,15 @@ export async function runContainerTask( const context = provider.config.context const namespace = await getAppNamespace(ctx, log, provider) const image = await containerHelpers.getDeploymentImageId(module, provider.config.deploymentRegistry) + const { command, args } = task.spec const res = await runPod({ context, namespace, module, envVars: runtimeContext.envVars, - args: task.spec.args || [], + command, + args, image, interactive, ignoreError: false, diff --git a/garden-service/src/plugins/kubernetes/container/test.ts b/garden-service/src/plugins/kubernetes/container/test.ts index 84de52447e..d674ecf799 100644 --- a/garden-service/src/plugins/kubernetes/container/test.ts +++ b/garden-service/src/plugins/kubernetes/container/test.ts @@ -18,7 +18,7 @@ export async function testContainerModule( TestModuleParams, ): Promise { const testName = testConfig.name - const command = testConfig.spec.args + const { command, args } = testConfig.spec runtimeContext.envVars = { ...runtimeContext.envVars, ...testConfig.spec.env } const timeout = testConfig.timeout || DEFAULT_TEST_TIMEOUT @@ -26,6 +26,7 @@ export async function testContainerModule( ctx, module, command, + args, interactive, ignoreError: true, // to ensure results get stored when an error occurs runtimeContext, diff --git a/garden-service/src/plugins/kubernetes/helm/config.ts b/garden-service/src/plugins/kubernetes/helm/config.ts index 49c9f1f243..4e46582dd6 100644 --- a/garden-service/src/plugins/kubernetes/helm/config.ts +++ b/garden-service/src/plugins/kubernetes/helm/config.ts @@ -38,17 +38,20 @@ export interface HelmResourceSpec { name?: string, containerName?: string, containerModule?: string + hotReloadCommand?: string[] hotReloadArgs?: string[], } export interface HelmTaskSpec extends BaseTaskSpec { resource: HelmResourceSpec + command?: string[] args: string[] env: { [key: string]: string } } export interface HelmTestSpec extends BaseTestSpec { resource: HelmResourceSpec + command?: string[] args: string[] env: { [key: string]: string } } diff --git a/garden-service/src/plugins/kubernetes/helm/run.ts b/garden-service/src/plugins/kubernetes/helm/run.ts index 23ed219dfe..814b8d1ecf 100644 --- a/garden-service/src/plugins/kubernetes/helm/run.ts +++ b/garden-service/src/plugins/kubernetes/helm/run.ts @@ -21,7 +21,7 @@ import { RunTaskParams, RunTaskResult } from "../../../types/plugin/task/runTask export async function runHelmModule( { - ctx, module, command, ignoreError = true, interactive, runtimeContext, timeout, log, + ctx, module, args, command, ignoreError = true, interactive, runtimeContext, timeout, log, }: RunModuleParams, ): Promise { const k8sCtx = ctx @@ -44,7 +44,8 @@ export async function runHelmModule( namespace, module, envVars: runtimeContext.envVars, - args: command, + command, + args, image, interactive, ignoreError, @@ -60,7 +61,7 @@ export async function runHelmTask( const context = k8sCtx.provider.config.context const namespace = await getAppNamespace(k8sCtx, log, k8sCtx.provider) - const args = task.spec.args + const { command, args } = task.spec const image = await getImage(k8sCtx, module, log, task.spec.resource || getServiceResourceSpec(module)) const res = await runPod({ @@ -68,6 +69,7 @@ export async function runHelmTask( namespace, module, envVars: { ...runtimeContext.envVars, ...task.spec.env }, + command, args, image, interactive, diff --git a/garden-service/src/plugins/kubernetes/hot-reload.ts b/garden-service/src/plugins/kubernetes/hot-reload.ts index 0883fb7601..7657a4e73b 100644 --- a/garden-service/src/plugins/kubernetes/hot-reload.ts +++ b/garden-service/src/plugins/kubernetes/hot-reload.ts @@ -37,6 +37,7 @@ export const hotReloadableKinds: HotReloadableKind[] = ["Deployment", "DaemonSet interface ConfigureHotReloadParams { target: HotReloadableResource, hotReloadSpec: ContainerHotReloadSpec, + hotReloadCommand?: string[], hotReloadArgs?: string[], containerName?: string, } @@ -48,7 +49,7 @@ interface ConfigureHotReloadParams { * and an initContainer to perform the initial population of the emptyDir volume. */ export function configureHotReload({ - target, hotReloadSpec, hotReloadArgs, containerName, + target, hotReloadSpec, hotReloadCommand, hotReloadArgs, containerName, }: ConfigureHotReloadParams) { const kind = target.kind @@ -102,6 +103,10 @@ export function configureHotReload({ ${RSYNC_PORT} from your services' port config.`) } + if (hotReloadCommand) { + container.command = hotReloadCommand + } + if (hotReloadArgs) { container.args = hotReloadArgs } diff --git a/garden-service/src/plugins/kubernetes/run.ts b/garden-service/src/plugins/kubernetes/run.ts index 2e695d4e2a..e9f9fe3f95 100644 --- a/garden-service/src/plugins/kubernetes/run.ts +++ b/garden-service/src/plugins/kubernetes/run.ts @@ -11,6 +11,7 @@ import { kubectl } from "./kubectl" import { PrimitiveMap } from "../../config/common" import { Module } from "../../types/module" import { LogEntry } from "../../logger/log-entry" +import { defaultContainerCommand } from "../container/helpers" interface RunPodParams { context: string, @@ -18,6 +19,7 @@ interface RunPodParams { module: Module, image: string, envVars: PrimitiveMap, + command?: string[], args: string[], interactive: boolean, ignoreError: boolean, @@ -27,11 +29,15 @@ interface RunPodParams { } export async function runPod( - { context, namespace, module, image, envVars, args, interactive, ignoreError, timeout, overrides, log }: RunPodParams, + { + context, namespace, module, image, envVars, + command, args, interactive, ignoreError, + timeout, overrides, log, + }: RunPodParams, ): Promise { const envArgs = Object.entries(envVars).map(([k, v]) => `--env=${k}=${v}`) - const commandStr = args.join(" ") + const cmd = (command && command.length) ? command : defaultContainerCommand const opts = [ `--image=${image}`, @@ -56,12 +62,11 @@ export async function runPod( ...opts, ...envArgs, "--", - "/bin/sh", - "-c", - commandStr, + ...cmd, + args.join(" "), ] - log.verbose(`Running kubectl ${args.join(" ")}`) + log.verbose(`Running ${cmd.join(" ")} '${args.join(" ")}'`) const startedAt = new Date() @@ -77,7 +82,7 @@ export async function runPod( return { moduleName: module.name, - command: args, + command: [...cmd, ...args], version: module.version, startedAt, completedAt: new Date(), diff --git a/garden-service/src/types/plugin/module/runModule.ts b/garden-service/src/types/plugin/module/runModule.ts index 407ebbf9bb..1014891b6f 100644 --- a/garden-service/src/types/plugin/module/runModule.ts +++ b/garden-service/src/types/plugin/module/runModule.ts @@ -14,7 +14,8 @@ import { RuntimeContext } from "../../service" import { joiArray } from "../../../config/common" export interface RunModuleParams extends PluginModuleActionParamsBase { - command: string[] + command?: string[] + args: string[] interactive: boolean runtimeContext: RuntimeContext ignoreError?: boolean @@ -27,7 +28,10 @@ export const runModuleBaseSchema = moduleActionParamsSchema export const runModuleParamsSchema = runModuleBaseSchema .keys({ command: joiArray(Joi.string()) - .description("The command to run in the module."), + .optional() + .description("The command/entrypoint to run in the module."), + args: joiArray(Joi.string()) + .description("The arguments passed to the command/entrypoint to run in the module."), }) export const runModule = { diff --git a/garden-service/test/helpers.ts b/garden-service/test/helpers.ts index 72046c4a36..8869944bbb 100644 --- a/garden-service/test/helpers.ts +++ b/garden-service/test/helpers.ts @@ -43,6 +43,7 @@ import { GetSecretParams } from "../src/types/plugin/provider/getSecret" import { DeleteSecretParams } from "../src/types/plugin/provider/deleteSecret" import { RunServiceParams } from "../src/types/plugin/service/runService" import { RunTaskParams } from "../src/types/plugin/task/runTask" +import { RunResult, RunTaskResult } from "../src/types/plugin/outputs" export const dataDir = resolve(GARDEN_SERVICE_ROOT, "test", "unit", "data") export const examplesDir = resolve(GARDEN_SERVICE_ROOT, "..", "examples") @@ -66,12 +67,12 @@ export async function profileBlock(description: string, block: () => Promise { const version = await params.module.version return { moduleName: params.module.name, - command: params.command, + command: params.args, completedAt: testNow, output: "OK", version, @@ -176,12 +177,12 @@ export const testPlugin: PluginFactory = (): GardenPlugin => { async runService( { ctx, service, interactive, runtimeContext, timeout, log }: RunServiceParams, - ) { + ): Promise { return runModule({ ctx, log, module: service.module, - command: [service.name], + args: [service.name], interactive, runtimeContext, timeout, @@ -190,14 +191,14 @@ export const testPlugin: PluginFactory = (): GardenPlugin => { async runTask( { ctx, task, interactive, runtimeContext, log }: RunTaskParams, - ) { + ): Promise { const result = await runModule({ ctx, interactive, log, runtimeContext, module: task.module, - command: task.spec.command || [], + args: task.spec.command, ignoreError: false, timeout: task.spec.timeout || 9999, }) diff --git a/garden-service/test/unit/src/actions.ts b/garden-service/test/unit/src/actions.ts index 577e6fa071..f813f699fb 100644 --- a/garden-service/test/unit/src/actions.ts +++ b/garden-service/test/unit/src/actions.ts @@ -156,7 +156,7 @@ describe("ActionHelper", () => { const result = await actions.runModule({ log, module, - command, + args: command, interactive: true, runtimeContext: { envVars: { FOO: "bar" }, @@ -481,7 +481,7 @@ const testPlugin: PluginFactory = async () => ({ validate(params, moduleActionDescriptions.runModule.paramsSchema) return { moduleName: params.module.name, - command: params.command, + command: params.args, completedAt: now, output: "bla bla", success: true,