Skip to content

Commit

Permalink
feat(container): add env key to specify env vars for containers
Browse files Browse the repository at this point in the history
Also removed the `variables` key from the base test spec schema, and
instead added the `env` key to the `generic` test spec schema (which is
inherited by the `container` schema).

BREAKING CHANGE:
The `tests[].variables` config key has been removed from the
`garden.yml` configuration file schema.
  • Loading branch information
edvald committed Jul 2, 2018
1 parent a5096ee commit 9fa0cb8
Show file tree
Hide file tree
Showing 21 changed files with 127 additions and 95 deletions.
106 changes: 62 additions & 44 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ module:
target:


# Key/value map of environment variables. Keys must be valid POSIX environment variable names
# (must be uppercase, may not start with `GARDEN`) and values must be primitives.
#
# Optional.
env:
{}

# A list of tests to run in the module.
#
# Optional.
Expand All @@ -290,12 +297,6 @@ module:
-


# Map of key/value pairs that are available during the test execution.
#
# Optional.
variables:
{}

# Maximum duration (in seconds) of the test run.
#
# Optional.
Expand All @@ -305,7 +306,15 @@ module:
#
# Optional.
command:
-
-


# Key/value map of environment variables. Keys must be valid POSIX environment variable
# names (must be uppercase, may not start with `GARDEN`) and values must be primitives.
#
# Optional.
env:
{}

```

Expand Down Expand Up @@ -387,43 +396,6 @@ module:
target:


# A list of tests to run in the module.
#
# Optional.
tests:
# The test specification of a generic module.
#
# Optional.
- # The name of the test.
#
# Required.
name:

# The names of services that must be running before the test is run.
#
# Optional.
dependencies:
-


# Map of key/value pairs that are available during the test execution.
#
# Optional.
variables:
{}

# Maximum duration (in seconds) of the test run.
#
# Optional.
timeout:

# The command to run in the module build context in order to test it.
#
# Optional.
command:
-


# Specify build arguments when building the container image.
#
# Optional.
Expand Down Expand Up @@ -499,6 +471,13 @@ module:
port:


# Key/value map of environment variables. Keys must be valid POSIX environment variable
# names (must be uppercase, may not start with `GARDEN`) and values must be primitives.
#
# Optional.
env:
{}

# Specify how the service's health should be checked after deploying.
#
# Optional.
Expand Down Expand Up @@ -578,5 +557,44 @@ module:

hostPath:


# A list of tests to run in the module.
#
# Optional.
tests:
# The test specification of a generic module.
#
# Optional.
- # The name of the test.
#
# Required.
name:

# The names of services that must be running before the test is run.
#
# Optional.
dependencies:
-


# Maximum duration (in seconds) of the test run.
#
# Optional.
timeout:

# The command to run in the module build context in order to test it.
#
# Optional.
command:
-


# Key/value map of environment variables. Keys must be valid POSIX environment variable
# names (must be uppercase, may not start with `GARDEN`) and values must be primitives.
#
# Optional.
env:
{}

```

2 changes: 2 additions & 0 deletions examples/hello-world/services/hello-container/garden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ module:
port: http
dependencies:
- hello-function
env:
FUNCTION_ENDPOINT: ${dependencies.hello-function.outputs.endpoint}
build:
dependencies:
- hello-npm-package
Expand Down
25 changes: 13 additions & 12 deletions src/plugins/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../types/module"
import { LogSymbolType } from "../logger/types"
import {
joiEnvVars,
joiIdentifier,
joiArray,
validate,
Expand Down Expand Up @@ -43,12 +44,7 @@ import {
import { DEFAULT_PORT_PROTOCOL } from "../constants"
import { splitFirst } from "../util/util"
import { keyBy } from "lodash"
import {
genericModuleSpecSchema,
GenericModuleSpec,
GenericTestSpec,
genericTestSchema,
} from "./generic"
import { genericTestSchema, GenericTestSpec } from "./generic"

export interface ServiceEndpointSpec {
paths?: string[]
Expand Down Expand Up @@ -87,6 +83,7 @@ export interface ContainerServiceSpec extends BaseServiceSpec {
command: string[],
daemon: boolean
endpoints: ServiceEndpointSpec[],
env: PrimitiveMap,
healthCheck?: ServiceHealthCheckSpec,
ports: ServicePortSpec[],
volumes: ServiceVolumeSpec[],
Expand Down Expand Up @@ -168,6 +165,7 @@ const serviceSchema = baseServiceSchema
.description("Whether to run the service as a daemon (to ensure only one runs per node)."),
endpoints: joiArray(endpointSchema)
.description("List of endpoints that the service exposes."),
env: joiEnvVars(),
healthCheck: healthCheckSchema
.description("Specify how the service's health should be checked after deploying."),
ports: joiArray(portSchema)
Expand All @@ -178,11 +176,17 @@ const serviceSchema = baseServiceSchema
.description("List of volumes that should be mounted when deploying the container."),
})

export class ContainerService extends Service<ContainerModule> { }

export interface ContainerTestSpec extends GenericTestSpec { }

export const containerTestSchema = genericTestSchema

export interface ContainerModuleSpec extends ModuleSpec {
buildArgs: PrimitiveMap,
image?: string,
services: ContainerServiceSpec[],
tests: GenericTestSpec[],
tests: ContainerTestSpec[],
}

export type ContainerModuleConfig = ModuleConfig<ContainerModuleSpec>
Expand All @@ -203,17 +207,15 @@ export const containerModuleSpecSchema = Joi.object()
services: joiArray(serviceSchema)
.unique("name")
.description("List of services to deploy from this container module."),
tests: joiArray(genericTestSchema)
tests: joiArray(containerTestSchema)
.description("A list of tests to run in the module."),
})
.description("Configuration for a container module.")

export class ContainerService extends Service<ContainerModule> { }

export class ContainerModule<
M extends ContainerModuleSpec = ContainerModuleSpec,
S extends ContainerServiceSpec = ContainerServiceSpec,
T extends GenericTestSpec = GenericTestSpec,
T extends ContainerTestSpec = ContainerTestSpec,
> extends Module<M, S, T> { }

export async function getImage(module: ContainerModule) {
Expand Down Expand Up @@ -327,7 +329,6 @@ export async function parseContainerModule({ moduleConfig }: ParseModuleParams<C
dependencies: t.dependencies,
spec: t,
timeout: t.timeout,
variables: <PrimitiveMap>t.variables,
}))

// make sure we can build the thing
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ export const name = "generic"

export interface GenericTestSpec extends BaseTestSpec {
command: string[],
env: PrimitiveMap,
}

export const genericTestSchema = baseTestSpecSchema
.keys({
command: Joi.array().items(Joi.string())
.description("The command to run in the module build context in order to test it."),
env: joiEnvVars(),
})
.description("The test specification of a generic module.")

Expand Down Expand Up @@ -85,7 +87,6 @@ export async function parseGenericModule(
dependencies: t.dependencies,
spec: t,
timeout: t.timeout,
variables: t.variables,
})),
}
}
Expand Down Expand Up @@ -120,7 +121,7 @@ export async function testGenericModule({ module, testConfig }: TestModuleParams
command.slice(1),
{
cwd: module.path,
env: { ...process.env, ...module.spec.env },
env: { ...process.env, ...module.spec.env, ...testConfig.spec.env },
ignoreError: true,
},
)
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/google/google-app-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const gardenPlugin = (): GardenPlugin => ({
const appYaml: any = {
runtime: "custom",
env: "flex",
env_variables: runtimeContext.envVars,
env_variables: { ...runtimeContext.envVars, ...service.spec.env },
}

if (config.healthCheck) {
Expand Down
1 change: 0 additions & 1 deletion src/plugins/google/google-cloud-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export async function parseGcfModule(
tests: moduleConfig.spec.tests.map(t => ({
name: t.name,
dependencies: t.dependencies,
variables: t.variables,
timeout: t.timeout,
spec: t,
})),
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kubernetes/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export async function testModule(
): Promise<TestResult> {
const testName = testConfig.name
const command = testConfig.spec.command
runtimeContext.envVars = { ...runtimeContext.envVars, ...testConfig.variables }
runtimeContext.envVars = { ...runtimeContext.envVars, ...testConfig.spec.env }
const timeout = testConfig.timeout || DEFAULT_TEST_TIMEOUT

const result = await runModule({ ctx, provider, env, module, command, interactive, runtimeContext, silent, timeout })
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kubernetes/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function createDeployment(service: ContainerService, runtimeContext
},
}

const envVars = extend({}, runtimeContext.envVars)
const envVars = { ...runtimeContext.envVars, ...service.spec.env }

const labels = {
// tier: service.tier,
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/local/local-docker-swarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const gardenPlugin = (): GardenPlugin => ({
}
})

const envVars = map(runtimeContext.envVars, (v, k) => `${k}=${v}`)
const envVars = map({ ...runtimeContext.envVars, ...service.spec.env }, (v, k) => `${k}=${v}`)

const volumeMounts = service.spec.volumes.map(v => {
// TODO-LOW: Support named volumes
Expand Down
1 change: 1 addition & 0 deletions src/plugins/local/local-google-cloud-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const gardenPlugin = (): GardenPlugin => ({
endpoints: [{
port: "http",
}],
env: {},
healthCheck: { tcpPort: "http" },
ports: [
{
Expand Down
12 changes: 9 additions & 3 deletions src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const joiPrimitive = () => Joi.alternatives().try(Joi.number(), Joi.strin
.description("Number, string or boolean")

export const identifierRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
export const envVarRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/
export const envVarRegex = /^(?!GARDEN)[A-Z_][A-Z0-9_]*$/

export const joiIdentifier = () => Joi
.string().regex(identifierRegex)
Expand All @@ -47,18 +47,24 @@ export const joiIdentifierMap = (valueSchema: JoiObject) => Joi
export const joiVariables = () => Joi
.object().pattern(/[\w\d]+/i, joiPrimitive())
.default(() => ({}), "{}")
.unknown(false)
.description("Key/value map, keys may contain letters and numbers, and values must be primitives.")

export const joiEnvVarName = () => Joi
.string().regex(envVarRegex)
.description(
"Valid POSIX environment variable name (may contain letters, numbers and underscores and must start with a letter.",
"Valid POSIX environment variable name (may contain letters, numbers and underscores and must start with a " +
"letter). Must be uppercase, and must not start with `GARDEN`.",
)

export const joiEnvVars = () => Joi
.object().pattern(envVarRegex, joiPrimitive())
.default(() => ({}), "{}")
.description("Key/value map, keys must be valid POSIX environment variable names, and values must be primitives.")
.unknown(false)
.description(
"Key/value map of environment variables. Keys must be valid POSIX environment variable names " +
"(must be uppercase, may not start with `GARDEN`) and values must be primitives.",
)

export const joiArray = (schema) => Joi
.array().items(schema)
Expand Down
Loading

0 comments on commit 9fa0cb8

Please sign in to comment.