diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ddf952502..aa6094f817 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -725,6 +725,11 @@ workflows: name: e2e-tasks project: tasks requires: [build] + - e2e-project: + <<: *only-internal-prs + name: e2e-templated-k8s-container + project: templated-k8s-container + requires: [build] - e2e-project: <<: *only-internal-prs name: e2e-vote diff --git a/core/src/actions.ts b/core/src/actions.ts index be8d68707e..b32be86dbc 100644 --- a/core/src/actions.ts +++ b/core/src/actions.ts @@ -849,7 +849,7 @@ export class ActionRouter implements TypeGuard { ? Object.keys(runtimeContext.envVars).length === 0 && runtimeContext.dependencies.length === 0 : true - if (!runtimeContextIsEmpty && (await getRuntimeTemplateReferences(module)).length > 0) { + if (!runtimeContextIsEmpty && getRuntimeTemplateReferences(module).length > 0) { log.silly(`Resolving runtime template strings for service '${service.name}'`) const providers = await this.garden.resolveProviders(log) @@ -863,6 +863,9 @@ export class ActionRouter implements TypeGuard { resolvedProviders: providers, dependencies: modules, runtimeContext, + parentName: module.parentName, + templateName: module.templateName, + inputs: module.inputs, }) // Set allowPartial=false to ensure all required strings are resolved. @@ -908,7 +911,7 @@ export class ActionRouter implements TypeGuard { }) // Resolve ${runtime.*} template strings if needed. - if (runtimeContext && (await getRuntimeTemplateReferences(module)).length > 0) { + if (runtimeContext && getRuntimeTemplateReferences(module).length > 0) { log.silly(`Resolving runtime template strings for task '${task.name}'`) const providers = await this.garden.resolveProviders(log) @@ -922,6 +925,9 @@ export class ActionRouter implements TypeGuard { resolvedProviders: providers, dependencies: modules, runtimeContext, + parentName: module.parentName, + templateName: module.templateName, + inputs: module.inputs, }) // Set allowPartial=false to ensure all required strings are resolved. diff --git a/core/src/config-store.ts b/core/src/config-store.ts index d6a799128a..1893cd0eea 100644 --- a/core/src/config-store.ts +++ b/core/src/config-store.ts @@ -135,7 +135,7 @@ export abstract class ConfigStore { private async loadConfig() { await this.ensureConfigFile() - const config = (await yaml.safeLoad((await readFile(this.configPath)).toString())) || {} + const config = yaml.safeLoad((await readFile(this.configPath)).toString()) || {} this.config = this.validate(config) } diff --git a/core/src/config/base.ts b/core/src/config/base.ts index 0a09006cfe..6c7d5ffb2b 100644 --- a/core/src/config/base.ts +++ b/core/src/config/base.ts @@ -19,14 +19,18 @@ import { validateWithPath } from "./validation" import { WorkflowResource } from "./workflow" import { listDirectory } from "../util/fs" import { isConfigFilename } from "../util/fs" +import { TemplateKind, templateKind, ModuleTemplateResource } from "./module-template" export interface GardenResource { apiVersion: string kind: string name: string path: string + configPath?: string } +export type ConfigKind = "Module" | "Workflow" | "Project" | TemplateKind + /** * Attempts to parse content as YAML, and applies a linter to produce more informative error messages when * content is not valid YAML. @@ -77,8 +81,6 @@ export async function loadConfigResources( return resources } -export type ConfigKind = "Module" | "Workflow" | "Project" - /** * Each YAML document in a garden.yml file defines a project, a module or a workflow. */ @@ -109,6 +111,8 @@ function prepareResource({ return prepareModuleResource(spec, configPath, projectRoot) } else if (kind === "Workflow") { return prepareWorkflowResource(spec, configPath) + } else if (kind === templateKind) { + return prepareTemplateResource(spec, configPath) } else if (allowInvalid) { return spec } else if (!kind) { @@ -160,6 +164,7 @@ export function prepareModuleResource(spec: any, configPath: string, projectRoot configPath, description: spec.description, disabled: spec.disabled, + generateFiles: spec.generateFiles, include: spec.include, exclude: spec.exclude, name: spec.name, @@ -178,7 +183,7 @@ export function prepareModuleResource(spec: any, configPath: string, projectRoot validateWithPath({ config, schema: coreModuleSpecSchema(), - path: dirname(configPath), + path: configPath, projectRoot, configType: "module", ErrorClass: ConfigurationError, @@ -199,6 +204,18 @@ export function prepareWorkflowResource(spec: any, configPath: string): Workflow return spec } +export function prepareTemplateResource(spec: any, configPath: string): ModuleTemplateResource { + if (!spec.apiVersion) { + spec.apiVersion = DEFAULT_API_VERSION + } + + spec.kind = templateKind + spec.path = dirname(configPath) + spec.configPath = configPath + + return spec +} + export async function findProjectConfig(path: string, allowInvalid = false): Promise { let sepCount = path.split(sep).length - 1 diff --git a/core/src/config/common.ts b/core/src/config/common.ts index 7585d0848d..4be88c21be 100644 --- a/core/src/config/common.ts +++ b/core/src/config/common.ts @@ -12,6 +12,7 @@ import { splitLast } from "../util/util" import { deline, dedent } from "../util/string" import { cloneDeep } from "lodash" import { joiPathPlaceholder } from "./validation" +import { DEFAULT_API_VERSION } from "../constants" export const objectSpreadKey = "$merge" @@ -483,3 +484,10 @@ export const moduleVersionSchema = () => .description("The version of each of the dependencies of the module."), files: fileNamesSchema(), }) + +export const apiVersionSchema = () => + joi + .string() + .default(DEFAULT_API_VERSION) + .valid(DEFAULT_API_VERSION) + .description("The schema version of this config (currently not used).") diff --git a/core/src/config/config-context.ts b/core/src/config/config-context.ts index f39896bae3..fbeebc3bb3 100644 --- a/core/src/config/config-context.ts +++ b/core/src/config/config-context.ts @@ -9,7 +9,15 @@ import Joi from "@hapi/joi" import chalk from "chalk" import { isString, mapValues } from "lodash" -import { PrimitiveMap, joiIdentifierMap, joiStringMap, joiPrimitive, DeepPrimitiveMap, joiVariables } from "./common" +import { + PrimitiveMap, + joiIdentifierMap, + joiStringMap, + joiPrimitive, + DeepPrimitiveMap, + joiVariables, + joiIdentifier, +} from "./common" import { Provider, GenericProviderConfig, ProviderMap } from "./provider" import { ConfigurationError } from "../exceptions" import { resolveTemplateString, TemplateStringMissingKeyError } from "../template-string" @@ -21,6 +29,7 @@ import { deline, dedent, naturalList } from "../util/string" import { getProviderUrl, getModuleTypeUrl } from "../docs/common" import { GardenModule } from "../types/module" import { isPrimitive } from "util" +import { templateKind } from "./module-template" export type ContextKeySegment = string | number export type ContextKey = ContextKeySegment[] @@ -300,6 +309,13 @@ export class DefaultEnvironmentContext extends ConfigContext { } } +export interface ProjectConfigContextParams { + projectName: string + artifactsPath: string + username?: string + secrets: PrimitiveMap +} + /** * This context is available for template strings for all Project config fields (except `name`, `id` and * `domain`). @@ -328,17 +344,7 @@ export class ProjectConfigContext extends DefaultEnvironmentContext { ) public secrets: PrimitiveMap - constructor({ - projectName, - artifactsPath, - username, - secrets, - }: { - projectName: string - artifactsPath: string - username?: string - secrets: PrimitiveMap - }) { + constructor({ projectName, artifactsPath, username, secrets }: ProjectConfigContextParams) { super({ projectName, artifactsPath, username }) this.secrets = secrets } @@ -741,6 +747,52 @@ class ErrorContext extends ConfigContext { } } +export class ParentContext extends ConfigContext { + @schema(joiIdentifier().description(`The name of the parent module.`)) + public name: string + + constructor(root: ConfigContext, name: string) { + super(root) + this.name = name + } +} + +export class ModuleTemplateContext extends ConfigContext { + @schema(joiIdentifier().description(`The name of the ${templateKind} being resolved.`)) + public name: string + + constructor(root: ConfigContext, name: string) { + super(root) + this.name = name + } +} + +export class ModuleTemplateConfigContext extends ProjectConfigContext { + @schema(ParentContext.getSchema().description(`Information about the templated module being resolved.`)) + public parent: ParentContext + + @schema( + ModuleTemplateContext.getSchema().description(`Information about the template used when generating the module.`) + ) + public template: ModuleTemplateContext + + @schema( + joiVariables().description(`The inputs provided when resolving the ${templateKind}.`).meta({ + keyPlaceholder: "", + }) + ) + public inputs: DeepPrimitiveMap + + constructor( + params: { parentName: string; templateName: string; inputs: DeepPrimitiveMap } & ProjectConfigContextParams + ) { + super(params) + this.parent = new ParentContext(this, params.parentName) + this.template = new ModuleTemplateContext(this, params.templateName) + this.inputs = params.inputs + } +} + /** * This context is available for template strings under the `module` key in configuration files. * It is a superset of the context available under the `project` key. @@ -761,12 +813,36 @@ export class ModuleConfigContext extends ProviderConfigContext { ) public runtime: RuntimeConfigContext + @schema( + ParentContext.getSchema().description( + `Information about the parent module (if the module is a submodule, e.g. generated in a templated module).` + ) + ) + public parent?: ParentContext + + @schema( + ModuleTemplateContext.getSchema().description( + `Information about the ${templateKind} used when generating the module.` + ) + ) + public template?: ModuleTemplateContext + + @schema( + joiVariables().description(`The inputs provided to the module through a ${templateKind}, if applicable.`).meta({ + keyPlaceholder: "", + }) + ) + public inputs: DeepPrimitiveMap + constructor({ garden, resolvedProviders, moduleName, dependencies, runtimeContext, + parentName, + templateName, + inputs, }: { garden: Garden resolvedProviders: ProviderMap @@ -775,6 +851,9 @@ export class ModuleConfigContext extends ProviderConfigContext { // We only supply this when resolving configuration in dependency order. // Otherwise we pass `${runtime.*} template strings through for later resolution. runtimeContext?: RuntimeContext + parentName: string | undefined + templateName: string | undefined + inputs: DeepPrimitiveMap | undefined }) { super(garden, resolvedProviders) @@ -788,6 +867,11 @@ export class ModuleConfigContext extends ProviderConfigContext { } this.runtime = new RuntimeConfigContext(this, runtimeContext) + if (parentName && templateName) { + this.parent = new ParentContext(this, parentName) + this.template = new ModuleTemplateContext(this, templateName) + } + this.inputs = inputs || {} } } @@ -811,6 +895,9 @@ export class OutputConfigContext extends ModuleConfigContext { resolvedProviders, dependencies: modules, runtimeContext, + parentName: undefined, + templateName: undefined, + inputs: {}, }) } } diff --git a/core/src/config/module-template.ts b/core/src/config/module-template.ts new file mode 100644 index 0000000000..0226075249 --- /dev/null +++ b/core/src/config/module-template.ts @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2018-2020 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { joi, apiVersionSchema, joiUserIdentifier, CustomObjectSchema } from "./common" +import { baseModuleSpecSchema, BaseModuleSpec, ModuleConfig } from "./module" +import { dedent, deline } from "../util/string" +import { GardenResource, prepareModuleResource } from "./base" +import { DOCS_BASE_URL } from "../constants" +import { ProjectConfigContext, ModuleTemplateConfigContext } from "./config-context" +import { resolveTemplateStrings } from "../template-string" +import { validateWithPath } from "./validation" +import { Garden } from "../garden" +import { ConfigurationError } from "../exceptions" +import { resolve, posix, dirname } from "path" +import { readFile, ensureDir } from "fs-extra" +import Bluebird from "bluebird" +import { TemplatedModuleConfig, templatedModuleSpecSchema } from "../plugins/templated" +import { omit } from "lodash" + +const inputTemplatePattern = "${inputs.*}" +const parentNameTemplate = "${parent.name}" +const moduleTemplateNameTemplate = "${template.name}" +const moduleTemplateReferenceUrl = DOCS_BASE_URL + "/reference/template-strings#module-configuration-context" + +export const templateKind = "ModuleTemplate" + +export type TemplateKind = typeof templateKind + +interface TemplatedModuleSpec extends Partial { + type: string +} + +export interface ModuleTemplateResource extends GardenResource { + inputsSchemaPath?: string + modules?: TemplatedModuleSpec[] +} + +export interface ModuleTemplateConfig extends ModuleTemplateResource { + inputsSchema: CustomObjectSchema +} + +export async function resolveModuleTemplate( + garden: Garden, + resource: ModuleTemplateResource +): Promise { + // Resolve template strings, minus module templates and files + const partial = { + ...resource, + modules: [], + } + const context = new ProjectConfigContext(garden) + const resolved = resolveTemplateStrings(partial, context) + + // Validate the partial config + const validated = validateWithPath({ + config: resolved, + path: resource.configPath || resource.path, + schema: moduleTemplateSchema(), + projectRoot: garden.projectRoot, + configType: templateKind, + }) + + // Read and validate the JSON schema, if specified + // -> default to object with no properties + let inputsJsonSchema = { + type: "object", + additionalProperties: false, + } + + const configDir = resource.configPath ? dirname(resource.configPath) : resource.path + + if (validated.inputsSchemaPath) { + const path = resolve(configDir, ...validated.inputsSchemaPath.split(posix.sep)) + try { + inputsJsonSchema = JSON.parse((await readFile(path)).toString()) + } catch (error) { + throw new ConfigurationError(`Unable to read inputs schema for ${templateKind} ${validated.name}: ${error}`, { + path, + error, + }) + } + + const type = inputsJsonSchema?.type + + if (type !== "object") { + throw new ConfigurationError( + `Inputs schema for ${templateKind} ${validated.name} has type ${type}, but should be "object".`, + { path, type } + ) + } + } + + // Add the module templates back and return + return { + ...validated, + inputsSchema: joi.object().jsonSchema(inputsJsonSchema), + modules: resource.modules, + } +} + +export async function resolveTemplatedModule( + garden: Garden, + config: TemplatedModuleConfig, + templates: { [name: string]: ModuleTemplateConfig } +) { + // Resolve template strings for fields + const resolved = resolveTemplateStrings(config, new ProjectConfigContext(garden)) + const configType = "templated module " + resolved.name + + let resolvedSpec = omit(resolved.spec, "build") + + // Return immediately if module is disabled + if (resolved.disabled) { + return { resolvedSpec, modules: [] } + } + + // Validate + resolvedSpec = validateWithPath({ + config: omit(resolved.spec, "build"), + configType, + path: resolved.configPath || resolved.path, + schema: templatedModuleSpecSchema(), + projectRoot: garden.projectRoot, + }) + + const template = templates[resolvedSpec.template] + + if (!template) { + const availableTemplates = Object.keys(templates) + throw new ConfigurationError( + deline` + Templated module ${resolved.name} references template ${resolvedSpec.template}, + which cannot be found. Available templates: ${availableTemplates.join(", ")} + `, + { availableTemplates } + ) + } + + // Validate template inputs + resolvedSpec = validateWithPath({ + config: resolvedSpec, + configType, + path: resolved.configPath || resolved.path, + schema: templatedModuleSpecSchema().keys({ inputs: template.inputsSchema }), + projectRoot: garden.projectRoot, + }) + + const inputs = resolvedSpec.inputs || {} + + // Prepare modules and resolve templated names + const context = new ModuleTemplateConfigContext({ + ...garden, + parentName: resolved.name, + templateName: template.name, + inputs, + }) + + const modules = await Bluebird.map(template.modules || [], async (m) => { + // Run a partial template resolution with the parent+template info and inputs + const spec = resolveTemplateStrings(m, context, { allowPartial: true }) + + let moduleConfig: ModuleConfig + + try { + moduleConfig = prepareModuleResource(spec, resolved.configPath || resolved.path, garden.projectRoot) + } catch (error) { + throw new ConfigurationError( + `${templateKind} ${template.name} returned an invalid module (named ${spec.name}) for templated module ${resolved.name}: ${error.message}`, + { + moduleSpec: spec, + parent: resolvedSpec, + error, + } + ) + } + + // Resolve the file source path to an absolute path, so that it can be used during module resolution + moduleConfig.generateFiles = (moduleConfig.generateFiles || []).map((f) => ({ + ...f, + sourcePath: f.sourcePath && resolve(template.path, ...f.sourcePath.split(posix.sep)), + })) + + // If a path is set, resolve the path and ensure that directory exists + if (spec.path) { + moduleConfig.path = resolve(resolved.path, ...spec.path.split(posix.sep)) + await ensureDir(moduleConfig.path) + } + + // Attach metadata + moduleConfig.parentName = resolved.name + moduleConfig.templateName = template.name + moduleConfig.inputs = inputs + + return moduleConfig + }) + + return { resolvedSpec, modules } +} + +export const moduleTemplateSchema = () => + joi.object().keys({ + apiVersion: apiVersionSchema(), + kind: joi.string().allow(templateKind).only().default(templateKind), + name: joiUserIdentifier().description("The name of the template."), + path: joi.string().description(`The directory path of the ${templateKind}.`).meta({ internal: true }), + configPath: joi.string().description(`The path of the ${templateKind} config file.`).meta({ internal: true }), + inputsSchemaPath: joi + .posixPath() + .relativeOnly() + .description( + "Path to a JSON schema file describing the expected inputs for the template. Must be an object schema. If none is provided, no inputs will be accepted and an error thrown if attempting to do so." + ), + modules: joi + .array() + .items(moduleSchema()) + .description( + dedent` + A list of modules this template will output. The schema for each is the same as when you create modules normally in configuration files, with the addition of a \`path\` field, which allows you to specify a sub-directory to set as the module root. + + In addition to any template strings you can normally use for modules (see [the reference](${moduleTemplateReferenceUrl})), you can reference the inputs described by the inputs schema for the template, using ${inputTemplatePattern} template strings, as well as ${parentNameTemplate} and ${moduleTemplateNameTemplate}, to reference the name of the module using the template, and the name of the template itself, respectively. This also applies to file contents specified under the \`files\` key. + + **Important: Make sure you use templates for any identifiers that must be unique, such as module names, service names and task names. Otherwise you'll inevitably run into configuration errors. The module names can reference the ${inputTemplatePattern}, ${parentNameTemplate} and ${moduleTemplateNameTemplate} keys. Other identifiers can also reference those, plus any other keys available for module templates (see [the module context reference](${moduleTemplateReferenceUrl})).** + ` + ), + }) + +const moduleSchema = () => + baseModuleSpecSchema().keys({ + path: joi + .posixPath() + .relativeOnly() + .subPathOnly() + .description( + "POSIX-style path of a sub-directory to set as the module root. If the directory does not exist, it is automatically created." + ), + }) diff --git a/core/src/config/module.ts b/core/src/config/module.ts index f9e2a82e6e..b8bd511009 100644 --- a/core/src/config/module.ts +++ b/core/src/config/module.ts @@ -7,11 +7,21 @@ */ import { ServiceConfig, serviceConfigSchema } from "./service" -import { joiArray, joiIdentifier, joiRepositoryUrl, joiUserIdentifier, joi, includeGuideLink } from "./common" +import { + joiArray, + joiIdentifier, + joiRepositoryUrl, + joiUserIdentifier, + joi, + includeGuideLink, + apiVersionSchema, + DeepPrimitiveMap, + joiVariables, +} from "./common" import { TestConfig, testConfigSchema } from "./test" import { TaskConfig, taskConfigSchema } from "./task" -import { DEFAULT_API_VERSION } from "../constants" import { dedent, stableStringify } from "../util/string" +import { templateKind } from "./module-template" export interface BuildCopySpec { source: string @@ -43,10 +53,8 @@ export interface BuildDependencyConfig { export const buildDependencySchema = () => joi.object().keys({ - name: joiIdentifier().required().description("Module name to build ahead of this module."), - plugin: joiIdentifier() - .meta({ internal: true }) - .description("The name of plugin that provides the build dependency."), + name: joi.string().required().description("Module name to build ahead of this module."), + plugin: joi.string().meta({ internal: true }).description("The name of plugin that provides the build dependency."), copy: joiArray(copySchema()).description( "Specify one or more files or directories to copy from the built dependency to this module." ), @@ -56,6 +64,12 @@ export interface BaseBuildSpec { dependencies: BuildDependencyConfig[] } +export interface ModuleFileSpec { + sourcePath?: string + targetPath: string + value?: string +} + export interface ModuleSpec {} interface ModuleSpecCommon { @@ -65,6 +79,7 @@ interface ModuleSpecCommon { description?: string disabled?: boolean exclude?: string[] + generateFiles?: ModuleFileSpec[] include?: string[] name: string path?: string @@ -84,6 +99,35 @@ export interface BaseModuleSpec extends ModuleSpecCommon { disabled: boolean } +const generatedFileSchema = () => + joi + .object() + .keys({ + sourcePath: joi + .posixPath() + .relativeOnly() + .description( + dedent` + POSIX-style filename to read the source file contents from, relative to the path of the module (or the ${templateKind} configuration file if one is being applied). + This file may contain template strings, much like any other field in the configuration. + ` + ), + targetPath: joi + .posixPath() + .relativeOnly() + .subPathOnly() + .required() + .description( + dedent` + POSIX-style filename to write the resolved file contents to, relative to the path of the module. + + Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. + ` + ), + value: joi.string().description("The desired file contents as a string."), + }) + .xor("value", "sourcePath") + export const baseBuildSpecSchema = () => joi .object() @@ -96,77 +140,78 @@ export const baseBuildSpecSchema = () => .description("Specify how to build the module. Note that plugins may define additional keys on this object.") // These fields are validated immediately when loading the config file +const coreModuleSpecKeys = () => ({ + apiVersion: apiVersionSchema(), + kind: joi.string().default("Module").valid("Module"), + type: joiIdentifier().required().description("The type of this module.").example("container"), + name: joiUserIdentifier().required().description("The name of this module.").example("my-sweet-module"), +}) + export const coreModuleSpecSchema = () => joi .object() - .keys({ - apiVersion: joi - .string() - .default(DEFAULT_API_VERSION) - .valid(DEFAULT_API_VERSION) - .description("The schema version of this module's config (currently not used)."), - kind: joi.string().default("Module").valid("Module"), - type: joiIdentifier().required().description("The type of this module.").example("container"), - name: joiUserIdentifier().required().description("The name of this module.").example("my-sweet-module"), - }) - .required() + .keys(coreModuleSpecKeys()) .unknown(true) .description("Configure a module whose sources are located in this directory.") .meta({ extendable: true }) // These fields may be resolved later in the process, and allow for usage of template strings -export const baseModuleSpecSchema = () => - coreModuleSpecSchema().keys({ - description: joi.string().description("A description of the module."), - disabled: joi - .boolean() - .default(false) - .description( - dedent` - Set this to \`true\` to disable the module. You can use this with conditional template strings to disable modules based on, for example, the current environment or other variables (e.g. \`disabled: \${environment.name == "prod"}\`). This can be handy when you only need certain modules for specific environments, e.g. only for development. - - Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in which case building this module is necessary for the dependant to be built). - - If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden will automatically ignore those dependency declarations. Note however that template strings referencing the module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you need to make sure to provide alternate values for those if you're using them, using conditional expressions. - ` - ), - include: joi - .array() - .items(joi.posixPath().allowGlobs().subPathOnly()) - .description( - dedent` - Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files that do *not* match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. - - Note that you can also _exclude_ files using the \`exclude\` field or by placing \`.gardenignore\` files in your source tree, which use the same format as \`.gitignore\` files. See the [Configuration Files guide](${includeGuideLink}) for details. - - Also note that specifying an empty list here means _no sources_ should be included.` - ) - .example(["Dockerfile", "my-app.js"]), - exclude: joi - .array() - .items(joi.posixPath().allowGlobs().subPathOnly()) - .description( - dedent` - Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. - - Note that you can also explicitly _include_ files using the \`include\` field. If you also specify the \`include\` field, the files/patterns specified here are filtered from the files matched by \`include\`. See the [Configuration Files guide](${includeGuideLink}) for details. - - Unlike the \`modules.exclude\` field in the project config, the filters here have _no effect_ on which files and directories are watched for changes. Use the project \`modules.exclude\` field to affect those, if you have large directories that should not be watched for changes. - ` - ) - .example(["tmp/**/*", "*.log"]), - repositoryUrl: joiRepositoryUrl().description( +export const baseModuleSpecKeys = () => ({ + build: baseBuildSpecSchema().unknown(true), + description: joi.string().description("A description of the module."), + disabled: joi + .boolean() + .default(false) + .description( dedent` - ${(joiRepositoryUrl().describe().flags).description} + Set this to \`true\` to disable the module. You can use this with conditional template strings to disable modules based on, for example, the current environment or other variables (e.g. \`disabled: \${environment.name == "prod"}\`). This can be handy when you only need certain modules for specific environments, e.g. only for development. + + Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in which case building this module is necessary for the dependant to be built). - Garden will import the repository source code into this module, but read the module's config from the local garden.yml file.` + If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden will automatically ignore those dependency declarations. Note however that template strings referencing the module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you need to make sure to provide alternate values for those if you're using them, using conditional expressions. + ` ), - allowPublish: joi - .boolean() - .default(true) - .description("When false, disables pushing this module to remote registries."), - build: baseBuildSpecSchema().unknown(true), - }) + include: joi + .array() + .items(joi.posixPath().allowGlobs().subPathOnly()) + .description( + dedent` + Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files that do *not* match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + + Note that you can also _exclude_ files using the \`exclude\` field or by placing \`.gardenignore\` files in your source tree, which use the same format as \`.gitignore\` files. See the [Configuration Files guide](${includeGuideLink}) for details. + + Also note that specifying an empty list here means _no sources_ should be included.` + ) + .example(["Dockerfile", "my-app.js"]), + exclude: joi + .array() + .items(joi.posixPath().allowGlobs().subPathOnly()) + .description( + dedent` + Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + + Note that you can also explicitly _include_ files using the \`include\` field. If you also specify the \`include\` field, the files/patterns specified here are filtered from the files matched by \`include\`. See the [Configuration Files guide](${includeGuideLink}) for details. + + Unlike the \`modules.exclude\` field in the project config, the filters here have _no effect_ on which files and directories are watched for changes. Use the project \`modules.exclude\` field to affect those, if you have large directories that should not be watched for changes. + ` + ) + .example(["tmp/**/*", "*.log"]), + repositoryUrl: joiRepositoryUrl().description( + dedent` + ${(joiRepositoryUrl().describe().flags).description} + + Garden will import the repository source code into this module, but read the module's config from the local garden.yml file.` + ), + allowPublish: joi + .boolean() + .default(true) + .description("When false, disables pushing this module to remote registries."), + generateFiles: joi.array().items(generatedFileSchema()).description(dedent` + A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. + `), +}) + +export const baseModuleSpecSchema = () => coreModuleSpecSchema().keys(baseModuleSpecKeys()) export interface ModuleConfig extends BaseModuleSpec { @@ -177,6 +222,11 @@ export interface ModuleConfig[] taskConfigs: TaskConfig[] + // set by ModuleTemplates for templated modules + parentName?: string + templateName?: string + inputs?: DeepPrimitiveMap + // Plugins can add custom fields that are kept here spec: M } @@ -195,6 +245,23 @@ export const moduleConfigSchema = () => taskConfigs: joiArray(taskConfigSchema()).description("List of tasks configured by this module."), testConfigs: joiArray(testConfigSchema()).description("List of tests configured by this module."), spec: joi.object().meta({ extendable: true }).description("The module spec, as defined by the provider plugin."), + generateFiles: joi + .array() + .items( + generatedFileSchema().keys({ + // Allowing any file path for resolved configs + sourcePath: joi.string(), + }) + ) + .description("Files to write upon resolution, defined by a ModuleTemplate.") + .meta({ internal: true }), + parentName: joiIdentifier().description( + "The name of the parent module (e.g. a templated module that generated this module), if applicable." + ), + templateName: joiIdentifier().description("The module template that generated the module, if applicable."), + inputs: joiVariables().description( + "Inputs provided when rendering the module from a module template, if applicable." + ), _config: joi.object().meta({ internal: true }), }) .description("The configuration for a module.") diff --git a/core/src/config/project.ts b/core/src/config/project.ts index 38d040bb4f..67d60a61f5 100644 --- a/core/src/config/project.ts +++ b/core/src/config/project.ts @@ -21,6 +21,7 @@ import { joiPrimitive, DeepPrimitiveMap, joiVariablesDescription, + apiVersionSchema, } from "./common" import { validateWithPath } from "./validation" import { resolveTemplateStrings } from "../template-string" @@ -30,7 +31,7 @@ import { ConfigurationError, ParameterError, ValidationError } from "../exceptio import { PrimitiveMap } from "./common" import { cloneDeep, omit, isPlainObject } from "lodash" import { providerConfigBaseSchema, GenericProviderConfig } from "./provider" -import { DEFAULT_API_VERSION, DOCS_BASE_URL } from "../constants" +import { DOCS_BASE_URL } from "../constants" import { defaultDotIgnoreFiles } from "../util/fs" import { pathExists, readFile } from "fs-extra" import { resolve, basename, relative } from "path" @@ -42,7 +43,7 @@ export const defaultEnvVarfilePath = (environmentName: string) => `garden.${envi // These plugins are always loaded export const defaultNamespace = "default" -export const fixedPlugins = ["exec", "container"] +export const fixedPlugins = ["exec", "container", "templated"] export type EnvironmentNamespacing = "disabled" | "optional" | "required" @@ -275,11 +276,7 @@ export const projectDocsSchema = () => joi .object() .keys({ - apiVersion: joi - .string() - .default(DEFAULT_API_VERSION) - .valid(DEFAULT_API_VERSION) - .description("The schema version of this project's config (currently not used)."), + apiVersion: apiVersionSchema(), kind: joi.string().default("Project").valid("Project").description("Indicate what kind of config this is."), path: projectRootSchema().meta({ internal: true }), configPath: joi.string().meta({ internal: true }).description("The path to the project config file."), diff --git a/core/src/config/validation.ts b/core/src/config/validation.ts index 52a028fd31..765b39596b 100644 --- a/core/src/config/validation.ts +++ b/core/src/config/validation.ts @@ -46,7 +46,7 @@ export interface ValidateWithPathParams { path: string // Absolute path to the config file, including filename projectRoot: string name?: string // Name of the top-level entity that the config belongs to, e.g. "some-module" or "some-project" - configType?: string // The type of top-level entity that the config belongs to, e.g. "module" or "project" + configType: string // The type of top-level entity that the config belongs to, e.g. "module" or "project" ErrorClass?: typeof ConfigurationError | typeof LocalConfigError } @@ -62,11 +62,15 @@ export function validateWithPath({ path, projectRoot, name, - configType = "module", + configType, ErrorClass, }: ValidateWithPathParams) { + const context = + `${configType} ${name ? `'${name}' ` : ""}` + + `${path && projectRoot !== path ? "(" + relative(projectRoot, path) + ")" : ""}` + const validateOpts = { - context: `${configType} ${name ? `'${name}' ` : ""}(${relative(projectRoot, path)}/garden.yml)`, + context: context.trim(), } if (ErrorClass) { diff --git a/core/src/docs/config.ts b/core/src/docs/config.ts index 37eeca3c52..ddb4dfccab 100644 --- a/core/src/docs/config.ts +++ b/core/src/docs/config.ts @@ -12,7 +12,6 @@ import linewrap from "linewrap" import { resolve } from "path" import { projectDocsSchema } from "../config/project" import { get, isFunction } from "lodash" -import { baseModuleSpecSchema } from "../config/module" import handlebars = require("handlebars") import { joi } from "../config/common" import { STATIC_DIR } from "../constants" @@ -25,7 +24,6 @@ import { } from "./common" import { normalizeJoiSchemaDescription, JoiDescription } from "./joi-schema" import { safeDumpYaml } from "../util/util" -import { workflowConfigSchema } from "../config/workflow" export const TEMPLATES_DIR = resolve(STATIC_DIR, "docs", "templates") const partialTemplatePath = resolve(TEMPLATES_DIR, "config-partial.hbs") @@ -411,27 +409,3 @@ export function renderProjectConfigReference(opts: RenderConfigOpts = {}) { opts ) } - -/** - * Generates the base project and module level config references from the base-config.hbs template. - * The reference includes the rendered output from the config-partial.hbs template for - * the base project and base module schemas. - */ -export function renderBaseConfigReference() { - const baseTemplatePath = resolve(TEMPLATES_DIR, "base-config.hbs") - const { markdownReference: projectMarkdownReference, yaml: projectYaml } = renderProjectConfigReference() - const { markdownReference: moduleMarkdownReference, yaml: moduleYaml } = renderConfigReference(baseModuleSpecSchema()) - const { markdownReference: workflowMarkdownReference, yaml: workflowYaml } = renderConfigReference( - workflowConfigSchema() - ) - - const template = handlebars.compile(readFileSync(baseTemplatePath).toString()) - return template({ - projectMarkdownReference, - projectYaml, - moduleMarkdownReference, - moduleYaml, - workflowMarkdownReference, - workflowYaml, - }) -} diff --git a/core/src/docs/generate.ts b/core/src/docs/generate.ts index 533be9f889..dbc8597284 100644 --- a/core/src/docs/generate.ts +++ b/core/src/docs/generate.ts @@ -6,19 +6,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import handlebars = require("handlebars") import { resolve } from "path" import { writeCommandReferenceDocs } from "./commands" -import { renderBaseConfigReference } from "./config" +import { TEMPLATES_DIR, renderProjectConfigReference, renderConfigReference } from "./config" import { writeTemplateStringReferenceDocs } from "./template-strings" import { writeTableOfContents } from "./table-of-contents" import { Garden } from "../garden" import { defaultDotIgnoreFiles } from "../util/fs" import { keyBy } from "lodash" -import { writeFileSync } from "fs-extra" +import { writeFileSync, readFile, writeFile } from "fs-extra" import { renderModuleTypeReference, moduleTypes } from "./module-type" import { renderProviderReference } from "./provider" import { defaultNamespace } from "../config/project" import { GardenPlugin } from "../types/plugin/plugin" +import { workflowConfigSchema } from "../config/workflow" +import { moduleTemplateSchema } from "../config/module-template" export async function generateDocs(targetDir: string, plugins: GardenPlugin[]) { // tslint:disable: no-console @@ -37,7 +40,6 @@ export async function generateDocs(targetDir: string, plugins: GardenPlugin[]) { export async function writeConfigReferenceDocs(docsRoot: string, plugins: GardenPlugin[]) { // tslint:disable: no-console const referenceDir = resolve(docsRoot, "reference") - const configPath = resolve(referenceDir, "config.md") const garden = await Garden.factory(__dirname, { config: { @@ -105,7 +107,17 @@ export async function writeConfigReferenceDocs(docsRoot: string, plugins: Garden writeFileSync(resolve(moduleTypeDir, `README.md`), readme.join("\n")) - // Render base config docs - console.log("->", configPath) - writeFileSync(configPath, renderBaseConfigReference()) + // Render other config file references + async function renderConfigTemplate(configType: string, context: any) { + const templateData = await readFile(resolve(TEMPLATES_DIR, configType + "-config.hbs")) + const template = handlebars.compile(templateData.toString()) + + const targetPath = resolve(referenceDir, configType + "-config.md") + console.log("->", targetPath) + await writeFile(targetPath, template(context)) + } + + await renderConfigTemplate("project", renderProjectConfigReference()) + await renderConfigTemplate("workflow", renderConfigReference(workflowConfigSchema())) + await renderConfigTemplate("module-template", renderConfigReference(moduleTemplateSchema())) } diff --git a/core/src/docs/module-type.ts b/core/src/docs/module-type.ts index e379d64484..04014d2d43 100644 --- a/core/src/docs/module-type.ts +++ b/core/src/docs/module-type.ts @@ -28,6 +28,7 @@ export const moduleTypes = [ { name: "maven-container" }, { name: "openfaas" }, { name: "persistentvolumeclaim", pluginName: "local-kubernetes" }, + { name: "templated" }, { name: "terraform" }, ] diff --git a/core/src/garden.ts b/core/src/garden.ts index 90ab49e2c5..ff1d760415 100644 --- a/core/src/garden.ts +++ b/core/src/garden.ts @@ -12,7 +12,7 @@ import { ensureDir } from "fs-extra" import dedent from "dedent" import { platform, arch } from "os" import { parse, relative, resolve, join } from "path" -import { flatten, isString, sortBy, fromPairs, keyBy, mapValues, cloneDeep } from "lodash" +import { flatten, isString, sortBy, fromPairs, keyBy, mapValues, cloneDeep, groupBy } from "lodash" const AsyncLock = require("async-lock") import { TreeCache } from "./cache" @@ -29,7 +29,7 @@ import { parseEnvironment, getDefaultEnvironmentName, } from "./config/project" -import { findByName, pickKeys, getPackageVersion, getNames, findByNames } from "./util/util" +import { findByName, pickKeys, getPackageVersion, getNames, findByNames, duplicatesByKey } from "./util/util" import { ConfigurationError, PluginError, RuntimeError } from "./exceptions" import { VcsHandler, ModuleVersion } from "./vcs/vcs" import { GitHandler } from "./vcs/git" @@ -38,13 +38,13 @@ import { ConfigGraph } from "./config-graph" import { TaskGraph, GraphResults, ProcessTasksOpts } from "./task-graph" import { getLogger } from "./logger/logger" import { PluginActionHandlers, GardenPlugin } from "./types/plugin/plugin" -import { loadConfigResources, findProjectConfig, prepareModuleResource } from "./config/base" +import { loadConfigResources, findProjectConfig, prepareModuleResource, GardenResource } from "./config/base" import { DeepPrimitiveMap, StringMap, PrimitiveMap } from "./config/common" import { validateSchema } from "./config/validation" import { BaseTask } from "./tasks/base" import { LocalConfigStore, ConfigStore, GlobalConfigStore, LinkedSource } from "./config-store" import { getLinkedSources, ExternalSourceType } from "./util/ext-source-util" -import { BuildDependencyConfig, ModuleConfig, ModuleResource } from "./config/module" +import { BuildDependencyConfig, ModuleConfig } from "./config/module" import { resolveModuleConfig } from "./resolve-module" import { ModuleConfigContext, OutputConfigContext, DefaultEnvironmentContext } from "./config/config-context" import { createPluginContext, CommandInfo } from "./plugin-context" @@ -79,9 +79,16 @@ import { Profile } from "./util/profiling" import { ResolveModuleTask, getResolvedModules, moduleResolutionConcurrencyLimit } from "./tasks/resolve-module" import username from "username" import { throwOnMissingSecretKeys, resolveTemplateString } from "./template-string" -import { WorkflowConfig, WorkflowResource, WorkflowConfigMap, resolveWorkflowConfig } from "./config/workflow" +import { WorkflowConfig, WorkflowConfigMap, resolveWorkflowConfig } from "./config/workflow" import { enterpriseInit } from "./enterprise/init" import { PluginTool, PluginTools } from "./util/ext-tools" +import { + ModuleTemplateResource, + resolveModuleTemplate, + resolveTemplatedModule, + templateKind, +} from "./config/module-template" +import { TemplatedModuleConfig } from "./plugins/templated" export interface ActionHandlerMap { [actionName: string]: PluginActionHandlers[T] @@ -563,7 +570,7 @@ export class Garden { names = getNames(rawConfigs) } - throwOnMissingSecretKeys(Object.fromEntries(rawConfigs.map((c) => [c.name, c])), this.secrets, "Provider") + throwOnMissingSecretKeys(rawConfigs, this.secrets, "Provider") // As an optimization, we return immediately if all requested providers are already resolved const alreadyResolvedProviders = names.map((name) => this.resolvedProviders[name]).filter(Boolean) @@ -831,6 +838,9 @@ export class Garden { resolvedProviders: keyBy(providers, "name"), dependencies: resolvedModules, runtimeContext, + parentName: undefined, + templateName: undefined, + inputs: {}, }) // Resolve modules from specs and add to the list @@ -997,7 +1007,7 @@ export class Garden { return } - this.log.silly(`Scanning for modules and workflows`) + this.log.silly(`Scanning for configs`) // Add external sources that are defined at the project level. External sources are either kept in // the .garden/sources dir (and cloned there if needed), or they're linked to a local path via the link command. @@ -1014,22 +1024,44 @@ export class Garden { const dirsToScan = [this.projectRoot, ...extSourcePaths] const configPaths = flatten(await Bluebird.map(dirsToScan, (path) => this.scanForConfigs(path))) - const rawModuleConfigs: ModuleConfig[] = [...this.pluginModuleConfigs] - const rawWorkflowConfigs: WorkflowConfig[] = [] + const allResources = flatten( + await Bluebird.map(configPaths, async (path) => (await this.loadResources(path)) || []) + ) + const groupedResources = groupBy(allResources, "kind") - await Bluebird.map(configPaths, async (path) => { - const configs = await this.loadResources(path) - if (configs) { - const moduleConfigs = configs.filter((c) => c.kind === "Module") - const workflowConfigs = configs.filter((c) => c.kind === "Workflow") - rawModuleConfigs.push(...moduleConfigs) - rawWorkflowConfigs.push(...workflowConfigs) - } - }) + for (const [kind, configs] of Object.entries(groupedResources)) { + throwOnMissingSecretKeys(configs, this.secrets, kind) + } + + let rawModuleConfigs = [...this.pluginModuleConfigs, ...((groupedResources.Module as ModuleConfig[]) || [])] + const rawWorkflowConfigs = (groupedResources.Workflow as WorkflowConfig[]) || [] + const rawModuleTemplateResources = (groupedResources[templateKind] as ModuleTemplateResource[]) || [] + + // Resolve module templates + const moduleTemplates = await Bluebird.map(rawModuleTemplateResources, (r) => resolveModuleTemplate(this, r)) + // -> detect duplicate templates + const duplicateTemplates = duplicatesByKey(moduleTemplates, "name") + + if (duplicateTemplates.length > 0) { + const messages = duplicateTemplates + .map( + (d) => + `Name ${d.value} is used at ${naturalList( + d.duplicateItems.map((i) => relative(this.projectRoot, i.configPath || i.path)) + )}` + ) + .join("\n") + throw new ConfigurationError(`Found duplicate names of ${templateKind}s:\n${messages}`, { duplicateTemplates }) + } + + // Resolve templated modules + const templatesByKey = keyBy(moduleTemplates, "name") + const rawTemplated = rawModuleConfigs.filter((m) => m.type === "templated") as TemplatedModuleConfig[] + const resolvedTemplated = await Bluebird.map(rawTemplated, (r) => resolveTemplatedModule(this, r, templatesByKey)) - throwOnMissingSecretKeys(Object.fromEntries(rawModuleConfigs.map((c) => [c.name, c])), this.secrets, "Module") - throwOnMissingSecretKeys(Object.fromEntries(rawWorkflowConfigs.map((c) => [c.name, c])), this.secrets, "Workflow") + rawModuleConfigs.push(...resolvedTemplated.flatMap((c) => c.modules)) + // Add all the module and workflow configs await Bluebird.all([ Bluebird.map(rawModuleConfigs, async (config) => this.addModuleConfig(config)), Bluebird.map(rawWorkflowConfigs, async (config) => this.addWorkflow(config)), @@ -1094,17 +1126,16 @@ export class Garden { } /** - * Load a module and/or a workflow from the specified config file path and return the configs, - * or null if no module or workflow is found. + * Load any non-Project resources from the specified config file path. * * @param configPath Path to a garden config file */ - private async loadResources(configPath: string): Promise<(ModuleResource | WorkflowResource)[]> { + private async loadResources(configPath: string): Promise { configPath = resolve(this.projectRoot, configPath) this.log.silly(`Load module and workflow configs from ${configPath}`) const resources = await loadConfigResources(this.projectRoot, configPath) this.log.silly(`Loaded module and workflow configs from ${configPath}`) - return <(ModuleResource | WorkflowResource)[]>resources.filter((r) => r.kind === "Module" || r.kind === "Workflow") + return resources.filter((r) => r.kind && r.kind !== "Project") } //=========================================================================== diff --git a/core/src/plugins/exec.ts b/core/src/plugins/exec.ts index 44dba874a8..1c5bb4bf28 100644 --- a/core/src/plugins/exec.ts +++ b/core/src/plugins/exec.ts @@ -183,6 +183,7 @@ export async function configureExecModule({ moduleConfig.spec = validateWithPath({ config: moduleConfig.spec, + configType: "Module", schema: execModuleSpecSchema(), name: moduleConfig.name, path: moduleConfig.path, diff --git a/core/src/plugins/plugins.ts b/core/src/plugins/plugins.ts index 117c02ebf1..792fe5cba8 100644 --- a/core/src/plugins/plugins.ts +++ b/core/src/plugins/plugins.ts @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { InternalError } from "../exceptions" + // These plugins are always registered export const supportedPlugins = [ require("./container/container"), @@ -17,7 +19,8 @@ export const supportedPlugins = [ require("./octant/octant"), require("./openfaas/openfaas"), require("./terraform/terraform"), -].map((m) => m.gardenPlugin) + require("./templated"), +].map(resolvePluginFromModule) // These plugins are always registered export const builtinPlugins = supportedPlugins.concat( @@ -26,5 +29,20 @@ export const builtinPlugins = supportedPlugins.concat( require("./google/google-cloud-functions"), require("./local/local-google-cloud-functions"), require("./npm-package"), - ].map((m) => m.gardenPlugin) + ].map(resolvePluginFromModule) ) + +function resolvePluginFromModule(module: NodeModule) { + const filename = module.filename + const gardenPlugin = module["gardenPlugin"] + + if (!gardenPlugin) { + throw new InternalError(`Module ${filename} does not define a gardenPlugin`, { filename }) + } + + if (typeof gardenPlugin === "function") { + return gardenPlugin() + } + + return gardenPlugin +} diff --git a/core/src/plugins/templated.ts b/core/src/plugins/templated.ts new file mode 100644 index 0000000000..7e1ea8ef86 --- /dev/null +++ b/core/src/plugins/templated.ts @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018-2020 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { createGardenPlugin } from "../types/plugin/plugin" +import { ModuleConfig, ModuleSpec, baseModuleSpecKeys, baseBuildSpecSchema } from "../config/module" +import { templateKind } from "../config/module-template" +import { joiIdentifier, joi, DeepPrimitiveMap } from "../config/common" +import { dedent, naturalList } from "../util/string" +import { omit } from "lodash" + +export interface TemplatedModuleSpec extends ModuleSpec { + template: string + inputs?: DeepPrimitiveMap +} + +export interface TemplatedModuleConfig extends ModuleConfig { + modules: ModuleConfig[] +} + +export const templatedModuleSpecSchema = () => + joi.object().keys({ + disabled: baseModuleSpecKeys().disabled, + template: joiIdentifier() + .required() + .description(`The ${templateKind} to use to generate the sub-modules of this module.`), + inputs: joi.object().description( + dedent` + A map of inputs to pass to the ${templateKind}. These must match the inputs schema of the ${templateKind}. + ` + ), + }) + +// Note: This module type is currently special-cased when resolving modules in Garden.resolveModules() +export const gardenPlugin = () => { + const baseKeys = baseModuleSpecKeys() + const disallowedKeys = Object.keys(omit(baseKeys, "disabled")) + + return createGardenPlugin({ + name: "templated", + createModuleTypes: [ + { + name: "templated", + docs: dedent` + A special module type, for rendering [module templates](../../using-garden/module-templates.md). See the [Module Templates guide](../../using-garden/module-templates.md) for more information. + + Specify the name of a ModuleTemplate with the \`template\` field, and provide any expected inputs using the \`inputs\` field. The generated modules becomes sub-modules of this module. + + Note that the following common Module configuration fields are disallowed for this module type: + ${naturalList(disallowedKeys.map((k) => "`" + k + "`"))} + `, + schema: templatedModuleSpecSchema().keys({ + build: baseBuildSpecSchema(), + }), + handlers: { + async configure({ moduleConfig }) { + moduleConfig.allowPublish = false + moduleConfig.include = [] + return { moduleConfig } + }, + }, + }, + ], + }) +} diff --git a/core/src/resolve-module.ts b/core/src/resolve-module.ts index d78ff86d55..de296f44d8 100644 --- a/core/src/resolve-module.ts +++ b/core/src/resolve-module.ts @@ -51,6 +51,7 @@ export const resolveModuleConfig = profileAsync(async function $resolveModuleCon if (description.schema) { config.spec = validateWithPath({ config: config.spec, + configType: "Module", schema: description.schema, name: config.name, path: config.path, diff --git a/core/src/tasks/resolve-module.ts b/core/src/tasks/resolve-module.ts index 94295d254c..28726bb2c2 100644 --- a/core/src/tasks/resolve-module.ts +++ b/core/src/tasks/resolve-module.ts @@ -14,15 +14,18 @@ import { LogEntry } from "../logger/log-entry" import { ModuleConfig } from "../config/module" import { GraphResults } from "../task-graph" import { keyBy } from "lodash" -import { ConfigurationError, PluginError } from "../exceptions" +import { ConfigurationError, PluginError, FilesystemError } from "../exceptions" import { RuntimeContext } from "../runtime-context" import { ModuleConfigContext } from "../config/config-context" import { ProviderMap } from "../config/provider" import { resolveModuleConfig } from "../resolve-module" -import { getModuleTemplateReferences } from "../template-string" +import { getModuleTemplateReferences, resolveTemplateString } from "../template-string" import { Profile } from "../util/profiling" import { validateWithPath } from "../config/validation" import { getModuleTypeBases } from "../plugins" +import Bluebird from "bluebird" +import { posix, resolve } from "path" +import { mkdirp, writeFile, readFile } from "fs-extra" interface ResolveModuleConfigTaskParams { garden: Garden @@ -54,7 +57,18 @@ export class ResolveModuleConfigTask extends BaseTask { async resolveDependencies() { const rawConfigs = keyBy(await this.garden.getRawModuleConfigs(), "name") - const templateRefs = getModuleTemplateReferences(this.moduleConfig) + const configContext = new ModuleConfigContext({ + garden: this.garden, + resolvedProviders: this.resolvedProviders, + moduleName: this.moduleConfig.name, + dependencies: [], + runtimeContext: this.runtimeContext, + parentName: this.moduleConfig.parentName, + templateName: this.moduleConfig.templateName, + inputs: this.moduleConfig.inputs, + }) + + const templateRefs = getModuleTemplateReferences(this.moduleConfig, configContext) const deps = templateRefs.filter((d) => d[1] !== this.moduleConfig.name) return deps.map((d) => { @@ -99,6 +113,9 @@ export class ResolveModuleConfigTask extends BaseTask { moduleName: this.moduleConfig.name, dependencies, runtimeContext: this.runtimeContext, + parentName: this.moduleConfig.parentName, + templateName: this.moduleConfig.templateName, + inputs: this.moduleConfig.inputs, }) return resolveModuleConfig(this.garden, this.moduleConfig, { @@ -194,6 +211,44 @@ export class ResolveModuleTask extends BaseTask { const resolvedConfig = dependencyResults["resolve-module-config." + this.getName()]!.output as ModuleConfig const dependencyModules = getResolvedModules(dependencyResults) + // Write module files + const configContext = new ModuleConfigContext({ + garden: this.garden, + resolvedProviders: this.resolvedProviders, + moduleName: this.moduleConfig.name, + dependencies: dependencyModules, + runtimeContext: this.runtimeContext, + parentName: this.moduleConfig.parentName, + templateName: this.moduleConfig.templateName, + inputs: this.moduleConfig.inputs, + }) + + await Bluebird.map(resolvedConfig.generateFiles || [], async (fileSpec) => { + let contents = fileSpec.value || "" + + if (fileSpec.sourcePath) { + contents = (await readFile(fileSpec.sourcePath)).toString() + contents = await resolveTemplateString(contents, configContext) + } + + const resolvedContents = resolveTemplateString(contents, configContext) + const targetDir = resolve(resolvedConfig.path, ...posix.dirname(fileSpec.targetPath).split(posix.sep)) + const targetPath = resolve(resolvedConfig.path, ...fileSpec.targetPath.split(posix.sep)) + + try { + await mkdirp(targetDir) + await writeFile(targetPath, resolvedContents) + } catch (error) { + throw new FilesystemError( + `Unable to write templated file ${fileSpec.targetPath} from ${resolvedConfig.name}: ${error.message}`, + { + fileSpec, + error, + } + ) + } + }) + const module = await moduleFromConfig(this.garden, this.log, resolvedConfig, dependencyModules) const moduleTypeDefinitions = await this.garden.getModuleTypes() @@ -206,7 +261,7 @@ export class ResolveModuleTask extends BaseTask { schema: description.moduleOutputsSchema, configType: `outputs for module`, name: module.name, - path: module.path, + path: module.configPath || module.path, projectRoot: this.garden.projectRoot, ErrorClass: PluginError, }) @@ -222,7 +277,7 @@ export class ResolveModuleTask extends BaseTask { module.outputs = validateWithPath({ config: module.outputs, schema: base.moduleOutputsSchema.unknown(true), - path: module.path, + path: module.configPath || module.path, projectRoot: this.garden.projectRoot, configType: `outputs for module '${module.name}' (base schema from '${base.name}' plugin)`, ErrorClass: PluginError, diff --git a/core/src/template-string.ts b/core/src/template-string.ts index 42a3edcfff..2643108ea3 100644 --- a/core/src/template-string.ts +++ b/core/src/template-string.ts @@ -13,12 +13,14 @@ import { ScanContext, ContextResolveOutput, ContextKeySegment, + ModuleConfigContext, } from "./config/config-context" import { difference, flatten, uniq, isPlainObject, isNumber } from "lodash" import { Primitive, StringMap, isPrimitive, objectSpreadKey } from "./config/common" import { profile } from "./util/profiling" import { dedent, deline } from "./util/string" import { isArray } from "util" +import { ObjectWithName } from "./util/util" export type StringOrStringPromise = Promise | string @@ -172,9 +174,12 @@ export function getRuntimeTemplateReferences(obj: T) { return refs.filter((ref) => ref[0] === "runtime") } -export function getModuleTemplateReferences(obj: T) { +export function getModuleTemplateReferences(obj: T, context: ModuleConfigContext) { const refs = collectTemplateReferences(obj) - return refs.filter((ref) => ref[0] === "modules" && ref.length > 1) + const moduleNames = refs.filter((ref) => ref[0] === "modules" && ref.length > 1) + // Resolve template strings in name refs. This would ideally be done ahead of this function, but is currently + // necessary to resolve templated module name references in ModuleTemplates. + return resolveTemplateStrings(moduleNames, context) } /** @@ -183,16 +188,12 @@ export function getModuleTemplateReferences(obj: T) { * * Prefix should be e.g. "Module" or "Provider" (used when generating error messages). */ -export function throwOnMissingSecretKeys( - configs: { [key: string]: T }, - secrets: StringMap, - prefix: string -) { +export function throwOnMissingSecretKeys(configs: ObjectWithName[], secrets: StringMap, prefix: string) { const allMissing: [string, ContextKeySegment[]][] = [] // [[key, missing keys]] - for (const [key, config] of Object.entries(configs)) { + for (const config of configs) { const missing = detectMissingSecretKeys(config, secrets) if (missing.length > 0) { - allMissing.push([key, missing]) + allMissing.push([config.name, missing]) } } @@ -208,7 +209,7 @@ export function throwOnMissingSecretKeys( const loadedKeys = Object.entries(secrets) .filter(([_key, value]) => value) .map(([key, _value]) => key) - let footer + let footer: string if (loadedKeys.length === 0) { footer = deline` Note: No secrets have been loaded. If you have defined secrets for the current project and environment in Garden diff --git a/core/src/util/util.ts b/core/src/util/util.ts index 5300f9efeb..351aaa7eb6 100644 --- a/core/src/util/util.ts +++ b/core/src/util/util.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { trimEnd, omit } from "lodash" +import { trimEnd, omit, groupBy } from "lodash" import split2 = require("split2") import Bluebird = require("bluebird") import { ResolvableProps } from "bluebird" @@ -705,3 +705,23 @@ export class StringCollector extends Writable { return Buffer.concat(this.chunks).toString("utf8") } } + +/** + * Given a list of `items`, group them by `key` and return a list of `{ value, duplicateItems }` objects, where + * `value` is the value of item[key] and `duplicateItems` are the items that share the value. If the list is empty, + * no items have a duplicate value for the `key`. + * + * @example + * const items = [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 2, b: 2 }] + * // returns [{ value: 1, duplicateItems: [{ a: 1, b: 1 }, { a: 1, b: 2 }] }] + * duplicateKeys(items, "a") + * // returns [{ value: 2, duplicateItems: [{ a: 1, b: 2 }, { a: 2, b: 2 }] }] + * duplicateKeys(items, "b") + */ +export function duplicatesByKey(items: any[], key: string) { + const grouped = groupBy(items, key) + + return Object.entries(grouped) + .map(([value, duplicateItems]) => ({ value, duplicateItems })) + .filter(({ duplicateItems }) => duplicateItems.length > 1) +} diff --git a/core/test/data/test-projects/1067-module-ref-within-file/garden.yml b/core/test/data/test-projects/1067-module-ref-within-file/garden.yml index 8cb787a081..5715b783d1 100644 --- a/core/test/data/test-projects/1067-module-ref-within-file/garden.yml +++ b/core/test/data/test-projects/1067-module-ref-within-file/garden.yml @@ -1,5 +1,9 @@ kind: Project name: issue-1067 +environments: + - name: default +providers: + - name: container --- kind: Module name: container-module diff --git a/core/test/data/test-projects/duplicate-module-templates/project.garden.yml b/core/test/data/test-projects/duplicate-module-templates/project.garden.yml new file mode 100644 index 0000000000..0347975c56 --- /dev/null +++ b/core/test/data/test-projects/duplicate-module-templates/project.garden.yml @@ -0,0 +1,6 @@ +kind: Project +name: duplicate-module-templates +environments: + - name: local +providers: + - name: test-plugin diff --git a/core/test/data/test-projects/duplicate-module-templates/templates.garden.yml b/core/test/data/test-projects/duplicate-module-templates/templates.garden.yml new file mode 100644 index 0000000000..38e49a7d65 --- /dev/null +++ b/core/test/data/test-projects/duplicate-module-templates/templates.garden.yml @@ -0,0 +1,10 @@ +kind: ModuleTemplate +name: combo +modules: + - type: test + name: ${component-name}-${inputs.key}-test-a + - type: test + name: ${component-name}-${inputs.key}-test-b +--- +kind: ModuleTemplate +name: combo diff --git a/core/test/data/test-projects/module-self-ref/module-a/garden.yml b/core/test/data/test-projects/module-self-ref/module-a/garden.yml index 662ac8e73a..cdf489bc83 100644 --- a/core/test/data/test-projects/module-self-ref/module-a/garden.yml +++ b/core/test/data/test-projects/module-self-ref/module-a/garden.yml @@ -2,4 +2,4 @@ kind: Module name: module-a type: test build: - args: ["${modules.module-a.version}"] + command: ["${modules.module-a.version}"] diff --git a/core/test/data/test-projects/module-templates/invalid.json b/core/test/data/test-projects/module-templates/invalid.json new file mode 100644 index 0000000000..169a0d70f6 --- /dev/null +++ b/core/test/data/test-projects/module-templates/invalid.json @@ -0,0 +1,3 @@ +{ + "type": "string" +} \ No newline at end of file diff --git a/core/test/data/test-projects/module-templates/module-templates.json b/core/test/data/test-projects/module-templates/module-templates.json new file mode 100644 index 0000000000..0947b5b2fb --- /dev/null +++ b/core/test/data/test-projects/module-templates/module-templates.json @@ -0,0 +1,6 @@ +{ + "type": "object", + "properties": { + "foo": { "type": "string" } + } +} \ No newline at end of file diff --git a/core/test/data/test-projects/module-templates/modules.garden.yml b/core/test/data/test-projects/module-templates/modules.garden.yml new file mode 100644 index 0000000000..0e37eefdc7 --- /dev/null +++ b/core/test/data/test-projects/module-templates/modules.garden.yml @@ -0,0 +1,6 @@ +kind: Module +type: templated +template: combo +name: foo +inputs: + foo: bar diff --git a/core/test/data/test-projects/module-templates/project.garden.yml b/core/test/data/test-projects/module-templates/project.garden.yml new file mode 100644 index 0000000000..7a949a5837 --- /dev/null +++ b/core/test/data/test-projects/module-templates/project.garden.yml @@ -0,0 +1,6 @@ +kind: Project +name: module-templates +environments: + - name: local +providers: + - name: test-plugin diff --git a/core/test/data/test-projects/module-templates/source.txt b/core/test/data/test-projects/module-templates/source.txt new file mode 100644 index 0000000000..50ae3e2ef1 --- /dev/null +++ b/core/test/data/test-projects/module-templates/source.txt @@ -0,0 +1,3 @@ +Hello I am file! +input: ${inputs.foo} +module reference: ${modules["${parent.name}-${inputs.foo}-test-a"].path} diff --git a/core/test/data/test-projects/module-templates/templates.garden.yml b/core/test/data/test-projects/module-templates/templates.garden.yml new file mode 100644 index 0000000000..53675eb430 --- /dev/null +++ b/core/test/data/test-projects/module-templates/templates.garden.yml @@ -0,0 +1,29 @@ +kind: ModuleTemplate +name: combo +inputsSchemaPath: "module-templates.json" +modules: + - type: test + name: ${parent.name}-${inputs.foo}-test-a + include: [] + generateFiles: + - targetPath: module-a.log + value: "hellow" + - type: test + name: ${parent.name}-${inputs.foo}-test-b + build: + dependencies: ["${parent.name}-${inputs.foo}-test-a"] + include: [] + generateFiles: + - targetPath: module-b.log + sourcePath: source.txt + - type: test + name: ${parent.name}-${inputs.foo}-test-c + build: + dependencies: ["${parent.name}-${inputs.foo}-test-a"] + include: [] + generateFiles: + - targetPath: .garden/subdir/module-c.log + value: | + Hello I am string! + input: ${inputs.foo} + module reference: ${modules["${parent.name}-${inputs.foo}-test-a"].path} diff --git a/core/test/integ/src/plugins/kubernetes/helm/common.ts b/core/test/integ/src/plugins/kubernetes/helm/common.ts index 1ba7c39034..5ae237f254 100644 --- a/core/test/integ/src/plugins/kubernetes/helm/common.ts +++ b/core/test/integ/src/plugins/kubernetes/helm/common.ts @@ -100,6 +100,7 @@ describe("Helm common functions", () => { describe("renderTemplates", () => { it("should render and return the manifests for a local template", async () => { const module = graph.getModule("api") + const imageModule = graph.getModule("api-image") const templates = await renderTemplates(ctx, module, false, log) expect(templates).to.eql(dedent` @@ -149,7 +150,7 @@ describe("Helm common functions", () => { spec: containers: - name: api - image: "api-image:v-74e9653167" + image: "api-image:${imageModule.version.versionString}" imagePullPolicy: IfNotPresent args: [python, app.py] ports: diff --git a/core/test/integ/src/plugins/kubernetes/helm/config.ts b/core/test/integ/src/plugins/kubernetes/helm/config.ts index f4a76a06a1..702d21b23c 100644 --- a/core/test/integ/src/plugins/kubernetes/helm/config.ts +++ b/core/test/integ/src/plugins/kubernetes/helm/config.ts @@ -56,7 +56,9 @@ describe("configureHelmModule", () => { configPath: resolve(ctx.projectRoot, "api", "garden.yml"), description: "The API backend for the voting UI", disabled: false, + generateFiles: undefined, include: ["*", "charts/**/*", "templates/**/*"], + inputs: {}, exclude: undefined, name: "api", path: resolve(ctx.projectRoot, "api"), diff --git a/core/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts b/core/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts index fd25399d09..235a785092 100644 --- a/core/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts +++ b/core/test/integ/src/plugins/kubernetes/kubernetes-module/config.ts @@ -80,7 +80,9 @@ describe("validateKubernetesModule", () => { description: "Simple Kubernetes module with minimum config", disabled: false, exclude: undefined, + generateFiles: undefined, include: [], + inputs: {}, kind: "Module", name: "module-simple", path: resolve(ctx.projectRoot, "module-simple"), diff --git a/core/test/unit/src/commands/dev.ts b/core/test/unit/src/commands/dev.ts index 60e5a394b8..6294be3e4a 100644 --- a/core/test/unit/src/commands/dev.ts +++ b/core/test/unit/src/commands/dev.ts @@ -102,6 +102,7 @@ describe("DevCommand", () => { "resolve-module-config.module-c", "resolve-provider.container", "resolve-provider.exec", + "resolve-provider.templated", "resolve-provider.test-plugin", "resolve-provider.test-plugin-b", "stage-build.module-a", diff --git a/core/test/unit/src/commands/test.ts b/core/test/unit/src/commands/test.ts index b745e3cddb..84b868307f 100644 --- a/core/test/unit/src/commands/test.ts +++ b/core/test/unit/src/commands/test.ts @@ -10,6 +10,7 @@ import { expect } from "chai" import { TestCommand } from "../../../../src/commands/test" import isSubset = require("is-subset") import { makeTestGardenA, taskResultOutputs, withDefaultGlobalOpts } from "../../../helpers" +import { keyBy } from "lodash" describe("TestCommand", () => { const command = new TestCommand() @@ -17,6 +18,8 @@ describe("TestCommand", () => { it("should run all tests in a simple project", async () => { const garden = await makeTestGardenA() const log = garden.log + const graph = await garden.getConfigGraph(log) + const modules = keyBy(graph.getModules(), "name") const { result } = await command.action({ garden, @@ -80,7 +83,7 @@ describe("TestCommand", () => { moduleName: "module-a", command: ["echo", "OK"], testName: "unit", - version: "v-9b30bc93e5", + version: modules["module-a"].version.versionString, success: true, startedAt: dummyDate, completedAt: dummyDate, @@ -93,7 +96,7 @@ describe("TestCommand", () => { moduleName: "module-a", command: ["echo", "OK"], testName: "integration", - version: "v-9b30bc93e5", + version: modules["module-a"].version.versionString, success: true, startedAt: dummyDate, completedAt: dummyDate, @@ -106,7 +109,7 @@ describe("TestCommand", () => { moduleName: "module-b", command: ["echo", "OK"], testName: "unit", - version: "v-4ce023171a", + version: modules["module-b"].version.versionString, success: true, startedAt: dummyDate, completedAt: dummyDate, @@ -119,7 +122,7 @@ describe("TestCommand", () => { moduleName: "module-c", command: ["echo", "OK"], testName: "unit", - version: "v-f4716c5e03", + version: modules["module-c"].version.versionString, success: true, startedAt: dummyDate, completedAt: dummyDate, @@ -132,7 +135,7 @@ describe("TestCommand", () => { moduleName: "module-c", command: ["echo", "OK"], testName: "integ", - version: "v-f4716c5e03", + version: modules["module-c"].version.versionString, success: true, startedAt: dummyDate, completedAt: dummyDate, diff --git a/core/test/unit/src/config/base.ts b/core/test/unit/src/config/base.ts index 66e81da1d4..06aa98ab4d 100644 --- a/core/test/unit/src/config/base.ts +++ b/core/test/unit/src/config/base.ts @@ -126,6 +126,7 @@ describe("loadConfigResources", () => { configPath, description: undefined, disabled: undefined, + generateFiles: undefined, include: undefined, exclude: undefined, repositoryUrl: undefined, @@ -165,6 +166,65 @@ describe("loadConfigResources", () => { ]) }) + it("should load and parse a module template", async () => { + const projectPath = getDataDir("test-projects", "module-templates") + const configPath = resolve(projectPath, "templates.garden.yml") + const parsed: any = await loadConfigResources(projectPath, configPath) + + expect(parsed).to.eql([ + { + apiVersion: "garden.io/v0", + configPath, + path: projectPath, + kind: "ModuleTemplate", + name: "combo", + inputsSchemaPath: "module-templates.json", + modules: [ + { + type: "test", + name: "${parent.name}-${inputs.foo}-test-a", + include: [], + generateFiles: [ + { + targetPath: "module-a.log", + value: "hellow", + }, + ], + }, + { + type: "test", + name: "${parent.name}-${inputs.foo}-test-b", + include: [], + build: { + dependencies: ["${parent.name}-${inputs.foo}-test-a"], + }, + generateFiles: [ + { + targetPath: "module-b.log", + sourcePath: "source.txt", + }, + ], + }, + { + type: "test", + name: "${parent.name}-${inputs.foo}-test-c", + include: [], + build: { + dependencies: ["${parent.name}-${inputs.foo}-test-a"], + }, + generateFiles: [ + { + targetPath: ".garden/subdir/module-c.log", + value: + 'Hello I am string!\ninput: ${inputs.foo}\nmodule reference: ${modules["${parent.name}-${inputs.foo}-test-a"].path}\n', + }, + ], + }, + ], + }, + ]) + }) + it("should load and parse a config file defining a project and a module", async () => { const configPath = resolve(projectPathMultipleModules, "garden.yml") const parsed = await loadConfigResources(projectPathMultipleModules, configPath) @@ -198,6 +258,7 @@ describe("loadConfigResources", () => { configPath, description: undefined, disabled: undefined, + generateFiles: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, @@ -231,6 +292,7 @@ describe("loadConfigResources", () => { allowPublish: undefined, description: undefined, disabled: undefined, + generateFiles: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, @@ -260,6 +322,7 @@ describe("loadConfigResources", () => { allowPublish: undefined, description: undefined, disabled: undefined, + generateFiles: undefined, include: ["*"], exclude: undefined, repositoryUrl: undefined, diff --git a/core/test/unit/src/config/config-context.ts b/core/test/unit/src/config/config-context.ts index 56ec8ba802..8b5630abd6 100644 --- a/core/test/unit/src/config/config-context.ts +++ b/core/test/unit/src/config/config-context.ts @@ -405,6 +405,9 @@ describe("ModuleConfigContext", () => { garden, resolvedProviders: keyBy(await garden.resolveProviders(garden.log), "name"), dependencies: modules, + parentName: undefined, + templateName: undefined, + inputs: {}, }) }) @@ -511,6 +514,9 @@ describe("ModuleConfigContext", () => { resolvedProviders: keyBy(await garden.resolveProviders(garden.log), "name"), dependencies: modules, runtimeContext, + parentName: undefined, + templateName: undefined, + inputs: {}, }) }) diff --git a/core/test/unit/src/config/module-template.ts b/core/test/unit/src/config/module-template.ts new file mode 100644 index 0000000000..0dd03c9ec6 --- /dev/null +++ b/core/test/unit/src/config/module-template.ts @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2018-2020 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { expect } from "chai" +import { DEFAULT_API_VERSION } from "../../../../src/constants" +import { expectError, TestGarden, getDataDir, makeTestGarden } from "../../../helpers" +import stripAnsi from "strip-ansi" +import { + ModuleTemplateResource, + resolveModuleTemplate, + ModuleTemplateConfig, + resolveTemplatedModule, +} from "../../../../src/config/module-template" +import { resolve } from "path" +import { joi } from "../../../../src/config/common" +import { pathExists, remove } from "fs-extra" +import { TemplatedModuleConfig } from "../../../../src/plugins/templated" + +describe("module templates", () => { + let garden: TestGarden + + const projectRoot = getDataDir("test-projects", "module-templates") + + before(async () => { + garden = await makeTestGarden(projectRoot) + }) + + describe("resolveModuleTemplate", () => { + const defaults = { + apiVersion: DEFAULT_API_VERSION, + kind: "ModuleTemplate", + name: "test", + path: projectRoot, + configPath: resolve(projectRoot, "templates.garden.yml"), + } + + it("resolves template strings for fields other than modules and files", async () => { + const config: ModuleTemplateResource = { + ...defaults, + inputsSchemaPath: "${project.name}.json", + } + const resolved = await resolveModuleTemplate(garden, config) + expect(resolved.inputsSchemaPath).to.eql("module-templates.json") + }) + + it("ignores template strings in modules", async () => { + const config: ModuleTemplateResource = { + ...defaults, + modules: [ + { + type: "test", + name: "${inputs.foo}", + }, + ], + } + const resolved = await resolveModuleTemplate(garden, config) + expect(resolved.modules).to.eql(config.modules) + }) + + it("throws on an invalid schema", async () => { + const config: any = { + ...defaults, + foo: "bar", + } + await expectError( + () => resolveModuleTemplate(garden, config), + (err) => + expect(stripAnsi(err.message)).to.equal( + 'Error validating ModuleTemplate (templates.garden.yml): key "foo" is not allowed at path [foo]' + ) + ) + }) + + it("defaults to an empty object schema for inputs", async () => { + const config: ModuleTemplateResource = { + ...defaults, + } + const resolved = await resolveModuleTemplate(garden, config) + expect((resolved.inputsSchema)._rules[0].args.jsonSchema.schema).to.eql({ + type: "object", + additionalProperties: false, + }) + }) + + it("parses a valid JSON inputs schema", async () => { + const config: ModuleTemplateResource = { + ...defaults, + inputsSchemaPath: "module-templates.json", + } + const resolved = await resolveModuleTemplate(garden, config) + expect(resolved.inputsSchema).to.exist + }) + + it("throws if inputs schema cannot be found", async () => { + const config: ModuleTemplateResource = { + ...defaults, + inputsSchemaPath: "foo.json", + } + const path = resolve(config.path, config.inputsSchemaPath!) + await expectError( + () => resolveModuleTemplate(garden, config), + (err) => + expect(stripAnsi(err.message)).to.equal( + `Unable to read inputs schema for ModuleTemplate test: Error: ENOENT: no such file or directory, open '${path}'` + ) + ) + }) + + it("throws if an invalid JSON schema is provided", async () => { + const config: ModuleTemplateResource = { + ...defaults, + inputsSchemaPath: "invalid.json", + } + await expectError( + () => resolveModuleTemplate(garden, config), + (err) => + expect(stripAnsi(err.message)).to.equal( + `Inputs schema for ModuleTemplate test has type string, but should be "object".` + ) + ) + }) + }) + + describe("resolveTemplatedModule", () => { + const template: ModuleTemplateConfig = { + apiVersion: DEFAULT_API_VERSION, + kind: "ModuleTemplate", + name: "test", + path: projectRoot, + configPath: resolve(projectRoot, "modules.garden.yml"), + inputsSchema: joi.object().keys({ + foo: joi.string(), + }), + modules: [], + } + + const templates = { + test: template, + } + + const defaults: TemplatedModuleConfig = { + apiVersion: DEFAULT_API_VERSION, + kind: "Module", + name: "test", + type: "templated", + path: projectRoot, + configPath: resolve(projectRoot, "modules.garden.yml"), + spec: { + template: "test", + }, + allowPublish: false, + build: { dependencies: [] }, + disabled: false, + modules: [], + serviceConfigs: [], + taskConfigs: [], + testConfigs: [], + } + + it("resolves template strings on the templated module config", async () => { + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "${project.name}", + }, + }, + } + const { resolvedSpec } = await resolveTemplatedModule(garden, config, templates) + expect(resolvedSpec.inputs?.foo).to.equal("module-templates") + }) + + it("resolves all parent, template and input template strings, ignoring others", async () => { + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "${parent.name}-${template.name}-${inputs.foo}", + build: { + dependencies: [{ name: "${parent.name}-${template.name}-foo", copy: [] }], + }, + image: "${modules.foo.outputs.bar || inputs.foo}", + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "bar", + }, + }, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + const module = resolved.modules[0] + + expect(module.name).to.equal("test-test-bar") + expect(module.build.dependencies).to.eql([{ name: "test-test-foo", copy: [] }]) + expect(module.spec.image).to.equal("${modules.foo.outputs.bar || inputs.foo}") + }) + + it("throws if module is invalid", async () => { + const config: any = { + ...defaults, + spec: { + ...defaults.spec, + foo: "bar", + }, + } + await expectError( + () => resolveTemplatedModule(garden, config, templates), + (err) => + expect(stripAnsi(err.message)).to.equal( + 'Error validating templated module test (modules.garden.yml): key "foo" is not allowed at path [foo]' + ) + ) + }) + + it("throws if template cannot be found", async () => { + const config: TemplatedModuleConfig = { + ...defaults, + spec: { ...defaults.spec, template: "foo" }, + } + await expectError( + () => resolveTemplatedModule(garden, config, templates), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Templated module test references template foo, which cannot be found. Available templates: test" + ) + ) + }) + + it("throws if inputs don't match inputs schema", async () => { + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: 123, + }, + }, + } + await expectError( + () => resolveTemplatedModule(garden, config, templates), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Error validating templated module test (modules.garden.yml): key .inputs.foo must be a string" + ) + ) + }) + + it("fully resolves the source path on module files", async () => { + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "foo", + generateFiles: [{ sourcePath: "foo/bar.txt", targetPath: "foo.txt" }], + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "bar", + }, + }, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + + const absPath = resolve(config.path, "foo", "bar.txt") + expect(resolved.modules[0].generateFiles![0].sourcePath).to.equal(absPath) + }) + + it("creates the module path directory, if necessary", async () => { + const absPath = resolve(projectRoot, ".garden", "foo") + await remove(absPath) + + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "foo", + path: `.garden/foo`, + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "bar", + }, + }, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + const module = resolved.modules[0] + + expect(module.path).to.equal(absPath) + expect(await pathExists(module.path)).to.be.true + }) + + it("attaches parent module and template metadata to the output modules", async () => { + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "foo", + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "bar", + }, + }, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + + expect(resolved.modules[0].parentName).to.equal(config.name) + expect(resolved.modules[0].templateName).to.equal(template.name) + expect(resolved.modules[0].inputs).to.eql(config.spec.inputs) + }) + + it("resolves template strings in template module names", async () => { + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "${inputs.foo}", + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + spec: { + ...defaults.spec, + inputs: { + foo: "bar", + }, + }, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + + expect(resolved.modules[0].name).to.equal("bar") + }) + + it("returns no modules if templated module is disabled", async () => { + const _templates = { + test: { + ...template, + modules: [ + { + type: "test", + name: "foo", + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + disabled: true, + } + + const resolved = await resolveTemplatedModule(garden, config, _templates) + + expect(resolved.modules.length).to.equal(0) + }) + + it("throws if an invalid module spec is in the template", async () => { + const _templates: any = { + test: { + ...template, + modules: [ + { + type: 123, + name: "foo", + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + } + await expectError( + () => resolveTemplatedModule(garden, config, _templates), + (err) => + expect(stripAnsi(err.message)).to.equal( + "ModuleTemplate test returned an invalid module (named foo) for templated module test: Error validating module (modules.garden.yml): key .type must be a string" + ) + ) + }) + + it("throws if a module spec has an invalid name", async () => { + const _templates: any = { + test: { + ...template, + modules: [ + { + type: "test", + name: 123, + }, + ], + }, + } + const config: TemplatedModuleConfig = { + ...defaults, + } + await expectError( + () => resolveTemplatedModule(garden, config, _templates), + (err) => + expect(stripAnsi(err.message)).to.equal( + "ModuleTemplate test returned an invalid module (named 123) for templated module test: Error validating module (modules.garden.yml): key .name must be a string" + ) + ) + }) + }) +}) diff --git a/core/test/unit/src/config/project.ts b/core/test/unit/src/config/project.ts index be6b68bc9a..68546da1d4 100644 --- a/core/test/unit/src/config/project.ts +++ b/core/test/unit/src/config/project.ts @@ -18,6 +18,7 @@ import { defaultEnvVarfilePath, parseEnvironment, defaultNamespace, + fixedPlugins, } from "../../../../src/config/project" import { DEFAULT_API_VERSION } from "../../../../src/constants" import { expectError } from "../../../helpers" @@ -513,7 +514,7 @@ describe("pickEnvironment", () => { ).to.eql({ environmentName: "default", namespace: "default", - providers: [{ name: "exec" }, { name: "container" }], + providers: fixedPlugins.map((name) => ({ name })), production: false, variables: {}, }) @@ -552,6 +553,7 @@ describe("pickEnvironment", () => { providers: [ { name: "exec" }, { name: "container", newKey: "foo" }, + { name: "templated" }, { name: "my-provider", a: "c", b: "d" }, { name: "env-provider" }, ], @@ -583,7 +585,12 @@ describe("pickEnvironment", () => { ).to.eql({ environmentName: "default", namespace: "default", - providers: [{ name: "exec" }, { name: "container", newKey: "foo" }, { name: "my-provider", b: "b" }], + providers: [ + { name: "exec" }, + { name: "container", newKey: "foo" }, + { name: "templated" }, + { name: "my-provider", b: "b" }, + ], production: false, variables: {}, }) @@ -1141,7 +1148,7 @@ describe("pickEnvironment", () => { () => pickEnvironment({ projectConfig: config, envString: "default", artifactsPath, username, secrets: {} }), (err) => expect(stripAnsi(err.message)).to.equal( - "Error validating environment default (/garden.yml): key .defaultNamespace must be a string" + "Error validating environment default: key .defaultNamespace must be a string" ) ) }) @@ -1216,7 +1223,7 @@ describe("pickEnvironment", () => { ).to.eql({ environmentName: "default", namespace: "foo", - providers: [{ name: "exec" }, { name: "container" }], + providers: fixedPlugins.map((name) => ({ name })), production: false, variables: {}, }) @@ -1240,7 +1247,7 @@ describe("pickEnvironment", () => { ).to.eql({ environmentName: "default", namespace: "foo", - providers: [{ name: "exec" }, { name: "container" }], + providers: fixedPlugins.map((name) => ({ name })), production: false, variables: {}, }) @@ -1264,7 +1271,7 @@ describe("pickEnvironment", () => { ).to.eql({ environmentName: "default", namespace: "default", - providers: [{ name: "exec" }, { name: "container" }], + providers: fixedPlugins.map((name) => ({ name })), production: false, variables: {}, }) diff --git a/core/test/unit/src/garden.ts b/core/test/unit/src/garden.ts index f0b5359c8d..3238330847 100644 --- a/core/test/unit/src/garden.ts +++ b/core/test/unit/src/garden.ts @@ -27,7 +27,7 @@ import { resetLocalConfig, testGitUrl, } from "../../helpers" -import { getNames, findByName } from "../../../src/util/util" +import { getNames, findByName, omitUndefined } from "../../../src/util/util" import { LinkedSource } from "../../../src/config-store" import { ModuleVersion } from "../../../src/vcs/vcs" import { getModuleCacheContext } from "../../../src/types/module" @@ -41,7 +41,7 @@ import { keyBy, set, mapValues } from "lodash" import stripAnsi from "strip-ansi" import { joi } from "../../../src/config/common" import { defaultDotIgnoreFiles } from "../../../src/util/fs" -import { realpath, writeFile } from "fs-extra" +import { realpath, writeFile, readFile, remove } from "fs-extra" import { dedent, deline } from "../../../src/util/string" import { ServiceState } from "../../../src/types/service" import execa from "execa" @@ -138,6 +138,10 @@ describe("Garden", () => { dependencies: [], path: projectRoot, }, + "templated": { + name: "templated", + path: projectRoot, + }, "test-plugin": testPluginProvider.config, "test-plugin-b": { name: "test-plugin-b", @@ -183,6 +187,10 @@ describe("Garden", () => { dependencies: [], path: projectRoot, }, + "templated": { + name: "templated", + path: projectRoot, + }, "test-plugin": { name: "test-plugin", dependencies: [], @@ -867,7 +875,7 @@ describe("Garden", () => { const moduleTypes = await garden.getModuleTypes() - expect(Object.keys(moduleTypes).sort()).to.eql(["bar", "container", "exec", "foo"]) + expect(Object.keys(moduleTypes).sort()).to.eql(["bar", "container", "exec", "foo", "templated"]) }) it("should throw if attempting to redefine a module type defined in the base", async () => { @@ -1213,7 +1221,7 @@ describe("Garden", () => { const moduleTypes = await garden.getModuleTypes() - expect(Object.keys(moduleTypes).sort()).to.eql(["a", "b", "c", "container", "exec"]) + expect(Object.keys(moduleTypes).sort()).to.eql(["a", "b", "c", "container", "exec", "templated"]) }) it("should throw if attempting to redefine a module type defined in the base's base", async () => { @@ -1431,24 +1439,12 @@ describe("Garden", () => { const providers = await garden.resolveProviders(garden.log) const configs = mapValues(providers, (p) => p.config) - expect(configs).to.eql({ - "exec": { - name: "exec", - dependencies: [], - path: projectRoot, - }, - "container": { - name: "container", - dependencies: [], - path: projectRoot, - }, - "test-plugin": testPluginProvider.config, - "test-plugin-b": { - name: "test-plugin-b", - dependencies: [], - environments: ["local"], - path: projectRoot, - }, + expect(configs["test-plugin"]).to.eql(testPluginProvider.config) + expect(configs["test-plugin-b"]).to.eql({ + name: "test-plugin-b", + dependencies: [], + environments: ["local"], + path: projectRoot, }) }) @@ -1842,7 +1838,7 @@ describe("Garden", () => { (err) => { expect(err.message).to.equal("Failed resolving one or more providers:\n" + "- test") expect(stripAnsi(err.detail.messages[0])).to.equal( - "- test: Error validating provider configuration (/garden.yml): key .foo must be a string" + "- test: Error validating provider configuration: key .foo must be a string" ) } ) @@ -1880,7 +1876,7 @@ describe("Garden", () => { (err) => { expect(err.message).to.equal("Failed resolving one or more providers:\n" + "- test") expect(stripAnsi(err.detail.messages[0])).to.equal( - "- test: Error validating provider configuration (/garden.yml): key .foo must be a string" + "- test: Error validating provider configuration: key .foo must be a string" ) } ) @@ -2119,7 +2115,7 @@ describe("Garden", () => { (err) => { expect(err.message).to.equal("Failed resolving one or more providers:\n" + "- test") expect(stripAnsi(err.detail.messages[0])).to.equal( - "- test: Error validating provider configuration (/garden.yml): key .foo must be a string" + "- test: Error validating provider configuration: key .foo must be a string" ) } ) @@ -2163,8 +2159,7 @@ describe("Garden", () => { (err) => { expect(err.message).to.equal("Failed resolving one or more providers:\n" + "- test") expect(stripAnsi(err.detail.messages[0])).to.equal( - "- test: Error validating provider configuration (base schema from 'base' plugin) " + - "(/garden.yml): key .foo must be a string" + "- test: Error validating provider configuration (base schema from 'base' plugin): key .foo must be a string" ) } ) @@ -2263,6 +2258,94 @@ describe("Garden", () => { expect(getNames(modules).sort()).to.eql(["module-a", "module-b", "module-c"]) }) + it("should resolve module templates and any modules referencing them", async () => { + const root = resolve(dataDir, "test-projects", "module-templates") + const garden = await makeTestGarden(root) + await garden.scanAndAddConfigs() + + const configA = (await garden.getRawModuleConfigs(["foo-bar-test-a"]))[0] + const configB = (await garden.getRawModuleConfigs(["foo-bar-test-b"]))[0] + + expect(omitUndefined(configA)).to.eql({ + apiVersion: "garden.io/v0", + kind: "Module", + build: { + dependencies: [], + }, + include: [], + configPath: resolve(root, "modules.garden.yml"), + name: "foo-bar-test-a", + path: root, + serviceConfigs: [], + spec: { + build: { + dependencies: [], + }, + }, + testConfigs: [], + type: "test", + taskConfigs: [], + generateFiles: [ + { + sourcePath: undefined, + targetPath: "module-a.log", + value: "hellow", + }, + ], + parentName: "foo", + templateName: "combo", + inputs: { + foo: "bar", + }, + }) + expect(omitUndefined(configB)).to.eql({ + apiVersion: "garden.io/v0", + kind: "Module", + build: { + dependencies: [{ name: "foo-bar-test-a", copy: [] }], + }, + include: [], + configPath: resolve(root, "modules.garden.yml"), + name: "foo-bar-test-b", + path: root, + serviceConfigs: [], + spec: { + build: { + dependencies: [{ name: "foo-bar-test-a", copy: [] }], + }, + }, + testConfigs: [], + type: "test", + taskConfigs: [], + generateFiles: [ + { + targetPath: "module-b.log", + sourcePath: resolve(root, "source.txt"), + }, + ], + parentName: "foo", + templateName: "combo", + inputs: { + foo: "bar", + }, + }) + }) + + it("should throw on duplicate module template names", async () => { + const garden = await makeTestGarden(resolve(dataDir, "test-projects", "duplicate-module-templates")) + + await expectError( + () => garden.scanAndAddConfigs(), + (err) => + expect(err.message).to.equal( + dedent` + Found duplicate names of ModuleTemplates: + Name combo is used at templates.garden.yml and templates.garden.yml + ` + ) + ) + }) + it("should throw when two modules have the same name", async () => { const garden = await makeTestGarden(resolve(dataDir, "test-projects", "duplicate-module")) @@ -2539,6 +2622,56 @@ describe("Garden", () => { await garden.resolveModules({ log: garden.log }) }) + it("resolves and writes a module file with a string value", async () => { + const projectRoot = getDataDir("test-projects", "module-templates") + const filePath = resolve(projectRoot, "module-a.log") + + await remove(filePath) + + const garden = await makeTestGarden(projectRoot) + await garden.resolveModules({ log: garden.log }) + + const fileContents = await readFile(filePath) + + expect(fileContents.toString()).to.equal("hellow") + }) + + it("resolves and writes a module file with a source file", async () => { + const projectRoot = getDataDir("test-projects", "module-templates") + const filePath = resolve(projectRoot, "module-b.log") + + await remove(filePath) + + const garden = await makeTestGarden(projectRoot) + await garden.resolveModules({ log: garden.log }) + + const fileContents = await readFile(filePath) + + expect(fileContents.toString().trim()).to.equal(dedent` + Hello I am file! + input: bar + module reference: ${projectRoot} + `) + }) + + it("resolves and writes a module file to a subdirectory and creates the directory", async () => { + const projectRoot = getDataDir("test-projects", "module-templates") + const filePath = resolve(projectRoot, ".garden", "subdir", "module-c.log") + + await remove(filePath) + + const garden = await makeTestGarden(projectRoot) + await garden.resolveModules({ log: garden.log }) + + const fileContents = await readFile(filePath) + + expect(fileContents.toString().trim()).to.equal(dedent` + Hello I am string! + input: bar + module reference: ${projectRoot} + `) + }) + it("should throw if a module type is not recognized", async () => { const garden = await makeTestGardenA() const config = (await garden.getRawModuleConfigs(["module-a"]))[0] @@ -2596,7 +2729,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating module 'foo' (/garden.yml): key "bla" is not allowed at path [bla] + foo: Error validating Module 'foo': key "bla" is not allowed at path [bla] `) ) }) @@ -2645,7 +2778,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating outputs for module 'foo' (/garden.yml): key .foo must be a string + foo: Error validating outputs for module 'foo': key .foo must be a string `) ) }) @@ -2718,7 +2851,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating configuration for module 'foo' (base schema from 'base' plugin) (/garden.yml): key .base is required + foo: Error validating configuration for module 'foo' (base schema from 'base' plugin): key .base is required `) ) }) @@ -2782,7 +2915,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating outputs for module 'foo' (base schema from 'base' plugin) (/garden.yml): key .foo must be a string + foo: Error validating outputs for module 'foo' (base schema from 'base' plugin): key .foo must be a string `) ) }) @@ -2867,7 +3000,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating configuration for module 'foo' (base schema from 'base-a' plugin) (/garden.yml): key .base is required + foo: Error validating configuration for module 'foo' (base schema from 'base-a' plugin): key .base is required `) ) }) @@ -2943,7 +3076,7 @@ describe("Garden", () => { expect(stripAnsi(err.message)).to.equal(dedent` Failed resolving one or more modules: - foo: Error validating outputs for module 'foo' (base schema from 'base-a' plugin) (/garden.yml): key .foo must be a string + foo: Error validating outputs for module 'foo' (base schema from 'base-a' plugin): key .foo must be a string `) ) }) @@ -3461,9 +3594,9 @@ describe("Garden", () => { }) context("test against fixed version hashes", async () => { - const moduleAVersionString = "v-4b68c1fda7" - const moduleBVersionString = "v-e145423c6c" - const moduleCVersionString = "v-73c52d0676" + const moduleAVersionString = "v-6aec27c89f" + const moduleBVersionString = "v-b201651929" + const moduleCVersionString = "v-878395c3ad" it("should return the same module versions between runtimes", async () => { const projectRoot = getDataDir("test-projects", "fixed-version-hashes-1") diff --git a/core/test/unit/src/template-string.ts b/core/test/unit/src/template-string.ts index 13bd58ca30..1e2ada0643 100644 --- a/core/test/unit/src/template-string.ts +++ b/core/test/unit/src/template-string.ts @@ -923,29 +923,32 @@ describe("collectTemplateReferences", () => { describe("throwOnMissingSecretKeys", () => { it("should not throw an error if no secrets are referenced", () => { - const configs = { - foo: { + const configs = [ + { + name: "foo", foo: "${banana.llama}", nested: { boo: "${moo}" }, }, - } + ] throwOnMissingSecretKeys(configs, {}, "Module") throwOnMissingSecretKeys(configs, { someSecret: "123" }, "Module") }) it("should throw an error if one or more secrets is missing", async () => { - const configs = { - moduleA: { + const configs = [ + { + name: "moduleA", foo: "${secrets.a}", nested: { boo: "${secrets.b}" }, }, - moduleB: { + { + name: "moduleB", bar: "${secrets.a}", nested: { boo: "${secrets.b}" }, baz: "${secrets.c}", }, - } + ] await expectError( () => throwOnMissingSecretKeys(configs, { b: "123" }, "Module"), diff --git a/core/test/unit/src/vcs/vcs.ts b/core/test/unit/src/vcs/vcs.ts index a765054240..e606b8b905 100644 --- a/core/test/unit/src/vcs/vcs.ts +++ b/core/test/unit/src/vcs/vcs.ts @@ -268,7 +268,7 @@ describe("VcsHandler", () => { const garden = await makeTestGarden(projectRoot) const module = await garden.resolveModule("module-a") - const fixedVersionString = "v-4b68c1fda7" + const fixedVersionString = "v-6aec27c89f" expect(module.version.versionString).to.eql(fixedVersionString) delete process.env.TEST_ENV_VAR diff --git a/docs/README.md b/docs/README.md index c67e69bee6..8df511e7de 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,7 @@ * [Tasks](./using-garden/tasks.md) * [Workflows](./using-garden/workflows.md) * [Variables and templating](./using-garden/variables-and-templating.md) +* [Module Templates](./using-garden/module-templates.md) * [Using the CLI](./using-garden/using-the-cli.md) ## 🌿 Guides @@ -68,11 +69,15 @@ * [`maven-container`](./reference/module-types/maven-container.md) * [`openfaas`](./reference/module-types/openfaas.md) * [`persistentvolumeclaim`](./reference/module-types/persistentvolumeclaim.md) + * [`templated`](./reference/module-types/templated.md) * [`terraform`](./reference/module-types/terraform.md) -* [Commands](./reference/commands.md) * [Config Files](./reference/config.md) -* [Template Strings](./reference/template-strings.md) * [Glossary](./reference/glossary.md) +* [Commands](./reference/commands.md) +* [Project Configuration](./reference/project-config.md) +* [Module Template Configuration](./reference/module-template-config.md) +* [Workflow Configuration](./reference/workflow-config.md) +* [Template Strings](./reference/template-strings.md) ## 🌹 Misc diff --git a/docs/guides/remote-kubernetes.md b/docs/guides/remote-kubernetes.md index 55370be8e3..73aabcbc3f 100644 --- a/docs/guides/remote-kubernetes.md +++ b/docs/guides/remote-kubernetes.md @@ -179,7 +179,7 @@ environments: ## Deploying to production -Depending on your setup and requirements, you may or may not want to use Garden to deploy to your production environment. In either case, if you do configure your production environment in your Garden project configuration, we highly recommend that you set the [production flag](../reference/config.md#environmentsproduction) on it. +Depending on your setup and requirements, you may or may not want to use Garden to deploy to your production environment. In either case, if you do configure your production environment in your Garden project configuration, we highly recommend that you set the [production flag](../reference/project-config.md#environmentsproduction) on it. This will protect against accidentally messing with your production environments, by prompting for confirmation before e.g. deploying or running tests in the environment. diff --git a/docs/guides/using-helm-charts.md b/docs/guides/using-helm-charts.md index da5960d0eb..90e3c9174e 100644 --- a/docs/guides/using-helm-charts.md +++ b/docs/guides/using-helm-charts.md @@ -304,7 +304,7 @@ a lot of flexibility in how you organize your charts. ## Production environments -You can define a remote environment as a `production` environment by setting the [production flag](../reference/config.md#environmentsproduction) to `true`. This affects some default behavior when deploying `helm`  modules. See the [Deploying to production](./remote-kubernetes.md#deploying-to-production) section in the [Remote Kubernetes](./remote-kubernetes.md) guide for details. +You can define a remote environment as a `production` environment by setting the [production flag](../reference/project-config.md#environmentsproduction) to `true`. This affects some default behavior when deploying `helm`  modules. See the [Deploying to production](./remote-kubernetes.md#deploying-to-production) section in the [Remote Kubernetes](./remote-kubernetes.md) guide for details. ## Next steps diff --git a/docs/reference/commands.md b/docs/reference/commands.md index bdc0d53bed..cec2d33911 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -1,5 +1,5 @@ --- -order: 3 +order: 30 title: Commands --- @@ -941,7 +941,7 @@ variables: # All module configs in the project. moduleConfigs: - - # The schema version of this module's config (currently not used). + - # The schema version of this config (currently not used). apiVersion: kind: @@ -952,6 +952,22 @@ moduleConfigs: # The name of this module. name: + # Specify how to build the module. Note that plugins may define additional keys on this object. + build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: + # A description of the module. description: @@ -1007,22 +1023,6 @@ moduleConfigs: # When false, disables pushing this module to remote registries. allowPublish: - # Specify how to build the module. Note that plugins may define additional keys on this object. - build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: - - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: - # The filesystem path of the module. path: @@ -1125,6 +1125,16 @@ moduleConfigs: # The module spec, as defined by the provider plugin. spec: + # The name of the parent module (e.g. a templated module that generated this module), if applicable. + parentName: + + # The module template that generated the module, if applicable. + templateName: + + # Inputs provided when rendering the module from a module template, if applicable. + inputs: + : + # All workflow configs in the project. workflowConfigs: - # The schema version of this workflow's config (currently not used). @@ -1353,7 +1363,7 @@ Examples: modules: # The configuration for a module. : - # The schema version of this module's config (currently not used). + # The schema version of this config (currently not used). apiVersion: kind: @@ -1364,6 +1374,22 @@ modules: # The name of this module. name: + # Specify how to build the module. Note that plugins may define additional keys on this object. + build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: + # A description of the module. description: @@ -1419,22 +1445,6 @@ modules: # When false, disables pushing this module to remote registries. allowPublish: - # Specify how to build the module. Note that plugins may define additional keys on this object. - build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: - - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: - # The filesystem path of the module. path: @@ -1534,6 +1544,16 @@ modules: # The module spec, as defined by the provider plugin. spec: + # The name of the parent module (e.g. a templated module that generated this module), if applicable. + parentName: + + # The module template that generated the module, if applicable. + templateName: + + # Inputs provided when rendering the module from a module template, if applicable. + inputs: + : + # The path to the build staging directory for the module. buildPath: diff --git a/docs/reference/config.md b/docs/reference/config.md index cef2c2416f..e96ebba0cc 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -671,7 +671,7 @@ Key/value map of variables to configure for all environments. Keys may contain l ## Module YAML schema ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -682,6 +682,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -735,21 +751,23 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the ModuleTemplate + # configuration file. + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the Bundle that references + # the template. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: ``` ## Module configuration keys @@ -757,7 +775,7 @@ build: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -797,6 +815,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -881,73 +967,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` +### `generateFiles[]` -[build](#build) > [dependencies](#builddependencies) > name +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -Module name to build ahead of this module. +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -| Type | Required | -| -------- | -------- | -| `string` | Yes | +### `generateFiles[].sourcePath` -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the ModuleTemplate configuration file. +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the Bundle that references the template. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ## Workflow YAML schema diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index 1862bede8b..4d1046c116 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -13,7 +13,7 @@ Several named environment configurations may be defined (e.g. _dev_, _testing_, `garden.yml`](../using-garden/projects.md). #### Module -The unit of building in Garden. A module is defined by its [`garden.yml` configuration file](./config.md), +The unit of building in Garden. A module is defined by its [`garden.yml` configuration file](../using-garden/configuration-overview.md), located in the module's top-level directory. Each module has a plugin type, and may define one or more [services](#service). @@ -24,7 +24,7 @@ first, and their build output made available to the requiring module's build ste #### Project The top-level unit of organization in Garden. A project consists of one or more [modules](#module), along with a -project-level [`garden.yml` configuration file](./config.md). +project-level [`garden.yml` configuration file](./project-config.md). Garden CLI commands are run in the context of a project, and are aware of all its configuration, modules and services. diff --git a/docs/reference/module-template-config.md b/docs/reference/module-template-config.md new file mode 100644 index 0000000000..b76648363c --- /dev/null +++ b/docs/reference/module-template-config.md @@ -0,0 +1,472 @@ +--- +order: 43 +title: Module Template Configuration +--- + +# Module Template Configuration Reference + +Below is the schema reference for `ModuleTemplate` configuration files. To learn more about module templates, see the [Module Templates guide](../using-garden/module-templates.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for the configuration files. + +Also check out the [`templated` module type reference](./module-types/templated.md). + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +# The schema version of this config (currently not used). +apiVersion: garden.io/v0 + +kind: ModuleTemplate + +# The name of the template. +name: + +# Path to a JSON schema file describing the expected inputs for the template. Must be an object schema. If none is +# provided, no inputs will be accepted and an error thrown if attempting to do so. +inputsSchemaPath: + +# A list of modules this template will output. The schema for each is the same as when you create modules normally in +# configuration files, with the addition of a `path` field, which allows you to specify a sub-directory to set as the +# module root. +# +# In addition to any template strings you can normally use for modules (see [the +# reference](https://docs.garden.io/reference/template-strings#module-configuration-context)), you can reference the +# inputs described by the inputs schema for the template, using ${inputs.*} template strings, as well as +# ${parent.name} and ${template.name}, to reference the name of the module using the template, and the name of the +# template itself, respectively. This also applies to file contents specified under the `files` key. +# +# **Important: Make sure you use templates for any identifiers that must be unique, such as module names, service +# names and task names. Otherwise you'll inevitably run into configuration errors. The module names can reference the +# ${inputs.*}, ${parent.name} and ${template.name} keys. Other identifiers can also reference those, plus any other +# keys available for module templates (see [the module context +# reference](https://docs.garden.io/reference/template-strings#module-configuration-context)).** +modules: + - # The schema version of this config (currently not used). + apiVersion: garden.io/v0 + + kind: Module + + # The type of this module. + type: + + # The name of this module. + name: + + # Specify how to build the module. Note that plugins may define additional keys on this object. + build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + + # A description of the module. + description: + + # Set this to `true` to disable the module. You can use this with conditional template strings to disable modules + # based on, for example, the current environment or other variables (e.g. `disabled: \${environment.name == + # "prod"}`). This can be handy when you only need certain modules for specific environments, e.g. only for + # development. + # + # Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also + # means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in + # which case building this module is necessary for the dependant to be built). + # + # If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden + # will automatically ignore those dependency declarations. Note however that template strings referencing the + # module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you + # need to make sure to provide alternate values for those if you're using them, using conditional expressions. + disabled: false + + # Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files + # that do *not* match these paths or globs are excluded when computing the version of the module, when responding + # to filesystem watch events, and when staging builds. + # + # Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your + # source tree, which use the same format as `.gitignore` files. See the [Configuration Files + # guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for + # details. + # + # Also note that specifying an empty list here means _no sources_ should be included. + include: + + # Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match + # these paths or globs are excluded when computing the version of the module, when responding to filesystem watch + # events, and when staging builds. + # + # Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` + # field, the files/patterns specified here are filtered from the files matched by `include`. See the + # [Configuration Files + # guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for + # details. + # + # Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files and + # directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have large + # directories that should not be watched for changes. + exclude: + + # A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific + # branch or tag, with the format: # + # + # Garden will import the repository source code into this module, but read the module's config from the local + # garden.yml file. + repositoryUrl: + + # When false, disables pushing this module to remote registries. + allowPublish: true + + # A list of files to write to the module directory when resolving this module. This is useful to automatically + # generate (and template) any supporting files needed for the module. + generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: + + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: + + # The desired file contents as a string. + value: + + # POSIX-style path of a sub-directory to set as the module root. If the directory does not exist, it is + # automatically created. + path: +``` + +## Configuration Keys + + +### `apiVersion` + +The schema version of this config (currently not used). + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------------- | -------- | +| `string` | "garden.io/v0" | `"garden.io/v0"` | Yes | + +### `kind` + +| Type | Allowed Values | Default | Required | +| -------- | ---------------- | ------------------ | -------- | +| `string` | "ModuleTemplate" | `"ModuleTemplate"` | Yes | + +### `name` + +The name of the template. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `inputsSchemaPath` + +Path to a JSON schema file describing the expected inputs for the template. Must be an object schema. If none is provided, no inputs will be accepted and an error thrown if attempting to do so. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + +### `modules[]` + +A list of modules this template will output. The schema for each is the same as when you create modules normally in configuration files, with the addition of a `path` field, which allows you to specify a sub-directory to set as the module root. + +In addition to any template strings you can normally use for modules (see [the reference](https://docs.garden.io/reference/template-strings#module-configuration-context)), you can reference the inputs described by the inputs schema for the template, using ${inputs.*} template strings, as well as ${parent.name} and ${template.name}, to reference the name of the module using the template, and the name of the template itself, respectively. This also applies to file contents specified under the `files` key. + +**Important: Make sure you use templates for any identifiers that must be unique, such as module names, service names and task names. Otherwise you'll inevitably run into configuration errors. The module names can reference the ${inputs.*}, ${parent.name} and ${template.name} keys. Other identifiers can also reference those, plus any other keys available for module templates (see [the module context reference](https://docs.garden.io/reference/template-strings#module-configuration-context)).** + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `modules[].apiVersion` + +[modules](#modules) > apiVersion + +The schema version of this config (currently not used). + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------------- | -------- | +| `string` | "garden.io/v0" | `"garden.io/v0"` | Yes | + +### `modules[].kind` + +[modules](#modules) > kind + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------- | -------- | +| `string` | "Module" | `"Module"` | Yes | + +### `modules[].type` + +[modules](#modules) > type + +The type of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +modules: + - type: "container" +``` + +### `modules[].name` + +[modules](#modules) > name + +The name of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +modules: + - name: "my-sweet-module" +``` + +### `modules[].build` + +[modules](#modules) > build + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `modules[].build.dependencies[]` + +[modules](#modules) > [build](#modulesbuild) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +modules: + - build: + ... + dependencies: + - name: some-other-module-name +``` + +### `modules[].build.dependencies[].name` + +[modules](#modules) > [build](#modulesbuild) > [dependencies](#modulesbuilddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `modules[].build.dependencies[].copy[]` + +[modules](#modules) > [build](#modulesbuild) > [dependencies](#modulesbuilddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `modules[].build.dependencies[].copy[].source` + +[modules](#modules) > [build](#modulesbuild) > [dependencies](#modulesbuilddependencies) > [copy](#modulesbuilddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `modules[].build.dependencies[].copy[].target` + +[modules](#modules) > [build](#modulesbuild) > [dependencies](#modulesbuilddependencies) > [copy](#modulesbuilddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + +### `modules[].description` + +[modules](#modules) > description + +A description of the module. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `modules[].disabled` + +[modules](#modules) > disabled + +Set this to `true` to disable the module. You can use this with conditional template strings to disable modules based on, for example, the current environment or other variables (e.g. `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden will automatically ignore those dependency declarations. Note however that template strings referencing the module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you need to make sure to provide alternate values for those if you're using them, using conditional expressions. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `false` | No | + +### `modules[].include[]` + +[modules](#modules) > include + +Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files that do *not* match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + +Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your source tree, which use the same format as `.gitignore` files. See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +Also note that specifying an empty list here means _no sources_ should be included. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +modules: + - include: + - Dockerfile + - my-app.js +``` + +### `modules[].exclude[]` + +[modules](#modules) > exclude + +Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + +Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have large directories that should not be watched for changes. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +modules: + - exclude: + - tmp/**/* + - '*.log' +``` + +### `modules[].repositoryUrl` + +[modules](#modules) > repositoryUrl + +A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific branch or tag, with the format: # + +Garden will import the repository source code into this module, but read the module's config from the local garden.yml file. + +| Type | Required | +| ----------------- | -------- | +| `gitUrl | string` | No | + +Example: + +```yaml +modules: + - repositoryUrl: "git+https://github.com/org/repo.git#v2.0" +``` + +### `modules[].allowPublish` + +[modules](#modules) > allowPublish + +When false, disables pushing this module to remote registries. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `true` | No | + +### `modules[].generateFiles[]` + +[modules](#modules) > generateFiles + +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `modules[].generateFiles[].sourcePath` + +[modules](#modules) > [generateFiles](#modulesgeneratefiles) > sourcePath + +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + +### `modules[].generateFiles[].targetPath` + +[modules](#modules) > [generateFiles](#modulesgeneratefiles) > targetPath + +POSIX-style filename to write the resolved file contents to, relative to the path of the module. + +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `modules[].generateFiles[].value` + +[modules](#modules) > [generateFiles](#modulesgeneratefiles) > value + +The desired file contents as a string. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `modules[].path` + +[modules](#modules) > path + +POSIX-style path of a sub-directory to set as the module root. If the directory does not exist, it is automatically created. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + diff --git a/docs/reference/module-types/README.md b/docs/reference/module-types/README.md index d93c9c0215..5d75002d68 100644 --- a/docs/reference/module-types/README.md +++ b/docs/reference/module-types/README.md @@ -14,4 +14,5 @@ title: Module Types * [`maven-container`](./maven-container.md) * [`openfaas`](./openfaas.md) * [`persistentvolumeclaim`](./persistentvolumeclaim.md) +* [`templated`](./templated.md) * [`terraform`](./terraform.md) \ No newline at end of file diff --git a/docs/reference/module-types/conftest.md b/docs/reference/module-types/conftest.md index 9558bd101e..9ce96bc75b 100644 --- a/docs/reference/module-types/conftest.md +++ b/docs/reference/module-types/conftest.md @@ -26,7 +26,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -37,6 +37,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -90,21 +106,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # Specify a module whose sources we want to test. sourceModule: @@ -129,7 +146,7 @@ files: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -169,6 +186,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -253,73 +338,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: +### `generateFiles[]` -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -### `build.dependencies[].name` +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -[build](#build) > [dependencies](#builddependencies) > name +### `generateFiles[].sourcePath` -Module name to build ahead of this module. +[generateFiles](#generatefiles) > sourcePath -| Type | Required | -| -------- | -------- | -| `string` | Yes | +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -### `build.dependencies[].copy[]` - -[build](#build) > [dependencies](#builddependencies) > copy - -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `sourceModule` diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index 92e34dbda4..549e08235b 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -27,7 +27,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -38,6 +38,29 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + + # For multi-stage Dockerfiles, specify which image to build (see + # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). + targetImage: + + # Maximum time in seconds to wait for build to finish. + timeout: 1200 + # A description of the module. description: @@ -98,28 +121,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' - - # For multi-stage Dockerfiles, specify which image to build (see - # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). - targetImage: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # Maximum time in seconds to wait for build to finish. - timeout: 1200 + # The desired file contents as a string. + value: # Specify build arguments to use when building the container image. # @@ -488,7 +505,7 @@ tasks: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -528,6 +545,94 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + +### `build.targetImage` + +[build](#build) > targetImage + +For multi-stage Dockerfiles, specify which image to build (see https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `build.timeout` + +[build](#build) > timeout + +Maximum time in seconds to wait for build to finish. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `number` | `1200` | No | + ### `description` A description of the module. @@ -619,94 +724,47 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` +### `generateFiles[]` -[build](#build) > [dependencies](#builddependencies) > name +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -Module name to build ahead of this module. +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -| Type | Required | -| -------- | -------- | -| `string` | Yes | +### `generateFiles[].sourcePath` -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. - -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | - -### `build.targetImage` - -[build](#build) > targetImage - -For multi-stage Dockerfiles, specify which image to build (see https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). +The desired file contents as a string. | Type | Required | | -------- | -------- | | `string` | No | -### `build.timeout` - -[build](#build) > timeout - -Maximum time in seconds to wait for build to finish. - -| Type | Default | Required | -| -------- | ------- | -------- | -| `number` | `1200` | No | - ### `buildArgs` Specify build arguments to use when building the container image. diff --git a/docs/reference/module-types/exec.md b/docs/reference/module-types/exec.md index f41f69f3c5..4610e6e536 100644 --- a/docs/reference/module-types/exec.md +++ b/docs/reference/module-types/exec.md @@ -30,7 +30,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -41,6 +41,28 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + + # The command to run to perform the build. + # + # By default, the command is run inside the Garden build directory (under .garden/build/). + # If the top level `local` directive is set to `true`, the command runs in the module source directory instead. + command: [] + # A description of the module. description: @@ -94,27 +116,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: - - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # The command to run to perform the build. - # - # By default, the command is run inside the Garden build directory (under .garden/build/). - # If the top level `local` directive is set to `true`, the command runs in the module source directory instead. - command: [] + # The desired file contents as a string. + value: # If set to true, Garden will run the build command, tests, and tasks in the module source directory, # instead of in the Garden build directory (under .garden/build/). @@ -216,7 +233,7 @@ tests: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -256,6 +273,98 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + +### `build.command[]` + +[build](#build) > command + +The command to run to perform the build. + +By default, the command is run inside the Garden build directory (under .garden/build/). +If the top level `local` directive is set to `true`, the command runs in the module source directory instead. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[string]` | `[]` | No | + +Example: + +```yaml +build: + ... + command: + - npm + - run + - build +``` + ### `description` A description of the module. @@ -340,97 +449,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies +### `generateFiles[]` -A list of modules that must be built before this module is built. +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -Module name to build ahead of this module. +### `generateFiles[].sourcePath` -| Type | Required | -| -------- | -------- | -| `string` | Yes | +[generateFiles](#generatefiles) > sourcePath -### `build.dependencies[].copy[]` +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -[build](#build) > [dependencies](#builddependencies) > copy +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -Specify one or more files or directories to copy from the built dependency to this module. +### `generateFiles[].targetPath` -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +[generateFiles](#generatefiles) > targetPath -### `build.dependencies[].copy[].source` +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source - -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. - -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +The desired file contents as a string. -### `build.command[]` - -[build](#build) > command - -The command to run to perform the build. - -By default, the command is run inside the Garden build directory (under .garden/build/). -If the top level `local` directive is set to `true`, the command runs in the module source directory instead. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[string]` | `[]` | No | - -Example: - -```yaml -build: - ... - command: - - npm - - run - - build -``` +| Type | Required | +| -------- | -------- | +| `string` | No | ### `local` diff --git a/docs/reference/module-types/hadolint.md b/docs/reference/module-types/hadolint.md index 9cbd43d442..098e12aadf 100644 --- a/docs/reference/module-types/hadolint.md +++ b/docs/reference/module-types/hadolint.md @@ -29,7 +29,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -40,6 +40,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -93,21 +109,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # POSIX-style path to a Dockerfile that you want to lint with `hadolint`. dockerfilePath: @@ -117,7 +134,7 @@ dockerfilePath: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -157,6 +174,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -241,73 +326,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` +### `generateFiles[]` -Specify how to build the module. Note that plugins may define additional keys on this object. +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -### `build.dependencies[]` +### `generateFiles[].sourcePath` -[build](#build) > dependencies +[generateFiles](#generatefiles) > sourcePath -A list of modules that must be built before this module is built. +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name - -Module name to build ahead of this module. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` - -[build](#build) > [dependencies](#builddependencies) > copy - -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `dockerfilePath` diff --git a/docs/reference/module-types/helm.md b/docs/reference/module-types/helm.md index 1aff109cd5..314743c0e8 100644 --- a/docs/reference/module-types/helm.md +++ b/docs/reference/module-types/helm.md @@ -22,7 +22,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -33,6 +33,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -92,21 +108,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # The name of another `helm` module to use as a base for this one. Use this to re-use a Helm chart across multiple # services. For example, you might have an organization-wide base chart for certain types of services. @@ -343,7 +360,7 @@ valueFiles: [] ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -383,6 +400,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -473,73 +558,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` +### `generateFiles[]` -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -Module name to build ahead of this module. +### `generateFiles[].sourcePath` -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `base` diff --git a/docs/reference/module-types/kubernetes.md b/docs/reference/module-types/kubernetes.md index 5c79ef02a5..20b331fe44 100644 --- a/docs/reference/module-types/kubernetes.md +++ b/docs/reference/module-types/kubernetes.md @@ -29,7 +29,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -40,6 +40,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -96,21 +112,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # The names of any services that this service depends on at runtime, and the names of any tasks that should be # executed before this service is deployed. @@ -272,7 +289,7 @@ tests: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -312,6 +329,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -399,73 +484,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` +### `generateFiles[]` -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -Module name to build ahead of this module. +### `generateFiles[].sourcePath` -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `dependencies[]` diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index 621c555bd1..1809448358 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -32,7 +32,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -43,6 +43,29 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + + # For multi-stage Dockerfiles, specify which image to build (see + # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). + targetImage: + + # Maximum time in seconds to wait for build to finish. + timeout: 1200 + # A description of the module. description: @@ -96,28 +119,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: - - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' - - # For multi-stage Dockerfiles, specify which image to build (see - # https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). - targetImage: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # Maximum time in seconds to wait for build to finish. - timeout: 1200 + # The desired file contents as a string. + value: # Specify build arguments to use when building the container image. # @@ -503,7 +520,7 @@ useDefaultDockerfile: true ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -543,6 +560,94 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + +### `build.targetImage` + +[build](#build) > targetImage + +For multi-stage Dockerfiles, specify which image to build (see https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `build.timeout` + +[build](#build) > timeout + +Maximum time in seconds to wait for build to finish. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `number` | `1200` | No | + ### `description` A description of the module. @@ -627,94 +732,47 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` +### `generateFiles[]` -[build](#build) > [dependencies](#builddependencies) > name +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -Module name to build ahead of this module. +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -| Type | Required | -| -------- | -------- | -| `string` | Yes | +### `generateFiles[].sourcePath` -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. - -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | - -### `build.targetImage` - -[build](#build) > targetImage - -For multi-stage Dockerfiles, specify which image to build (see https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target for details). +The desired file contents as a string. | Type | Required | | -------- | -------- | | `string` | No | -### `build.timeout` - -[build](#build) > timeout - -Maximum time in seconds to wait for build to finish. - -| Type | Default | Required | -| -------- | ------- | -------- | -| `number` | `1200` | No | - ### `buildArgs` Specify build arguments to use when building the container image. diff --git a/docs/reference/module-types/openfaas.md b/docs/reference/module-types/openfaas.md index 4425c9186a..8d102a7ada 100644 --- a/docs/reference/module-types/openfaas.md +++ b/docs/reference/module-types/openfaas.md @@ -22,7 +22,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -33,6 +33,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -86,21 +102,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # The names of services/functions that this function depends on at runtime. dependencies: [] @@ -148,7 +165,7 @@ tests: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -188,6 +205,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -272,73 +357,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` - -Specify how to build the module. Note that plugins may define additional keys on this object. - -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | - -### `build.dependencies[]` - -[build](#build) > dependencies - -A list of modules that must be built before this module is built. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` +### `generateFiles[]` -### `build.dependencies[].name` +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -[build](#build) > [dependencies](#builddependencies) > name +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -Module name to build ahead of this module. +### `generateFiles[].sourcePath` -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` +[generateFiles](#generatefiles) > sourcePath -[build](#build) > [dependencies](#builddependencies) > copy +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `dependencies[]` diff --git a/docs/reference/module-types/persistentvolumeclaim.md b/docs/reference/module-types/persistentvolumeclaim.md index 649e97fc7d..45c540edc8 100644 --- a/docs/reference/module-types/persistentvolumeclaim.md +++ b/docs/reference/module-types/persistentvolumeclaim.md @@ -23,7 +23,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -34,6 +34,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -87,21 +103,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # List of services and tasks to deploy/run before deploying this PVC. dependencies: [] @@ -170,7 +187,7 @@ spec: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -210,6 +227,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -294,73 +379,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` +### `generateFiles[]` -Specify how to build the module. Note that plugins may define additional keys on this object. +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -### `build.dependencies[]` +### `generateFiles[].sourcePath` -[build](#build) > dependencies +[generateFiles](#generatefiles) > sourcePath -A list of modules that must be built before this module is built. +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name - -Module name to build ahead of this module. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` - -[build](#build) > [dependencies](#builddependencies) > copy - -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `dependencies[]` diff --git a/docs/reference/module-types/templated.md b/docs/reference/module-types/templated.md new file mode 100644 index 0000000000..624c0a6ae3 --- /dev/null +++ b/docs/reference/module-types/templated.md @@ -0,0 +1,435 @@ +--- +title: "`templated` Module Type" +tocTitle: "`templated`" +--- + +# `templated` Module Type + +## Description + +A special module type, for rendering [module templates](../../using-garden/module-templates.md). See the [Module Templates guide](../../using-garden/module-templates.md) for more information. + +Specify the name of a ModuleTemplate with the `template` field, and provide any expected inputs using the `inputs` field. The generated modules becomes sub-modules of this module. + +Note that the following common Module configuration fields are disallowed for this module type: +`build`, `description`, `include`, `exclude`, `repositoryUrl`, `allowPublish` and `generateFiles` + +Below is the full schema reference. For an introduction to configuring Garden modules, please look at our [Configuration +guide](../../using-garden/configuration-overview.md). + +The [first section](#complete-yaml-schema) contains the complete YAML schema, and the [second section](#configuration-keys) describes each schema key. + +`templated` modules also export values that are available in template strings. See the [Outputs](#outputs) section below for details. + +## Complete YAML Schema + +The values in the schema below are the default values. + +```yaml +# The schema version of this config (currently not used). +apiVersion: garden.io/v0 + +kind: Module + +# The type of this module. +type: + +# The name of this module. +name: + +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + +# A description of the module. +description: + +# Set this to `true` to disable the module. You can use this with conditional template strings to disable modules +# based on, for example, the current environment or other variables (e.g. `disabled: \${environment.name == "prod"}`). +# This can be handy when you only need certain modules for specific environments, e.g. only for development. +# +# Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also +# means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in which +# case building this module is necessary for the dependant to be built). +# +# If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden will +# automatically ignore those dependency declarations. Note however that template strings referencing the module's +# service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you need to make +# sure to provide alternate values for those if you're using them, using conditional expressions. +disabled: false + +# Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files that +# do *not* match these paths or globs are excluded when computing the version of the module, when responding to +# filesystem watch events, and when staging builds. +# +# Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your source +# tree, which use the same format as `.gitignore` files. See the [Configuration Files +# guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for +# details. +# +# Also note that specifying an empty list here means _no sources_ should be included. +include: + +# Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match these +# paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, +# and when staging builds. +# +# Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` +# field, the files/patterns specified here are filtered from the files matched by `include`. See the [Configuration +# Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) +# for details. +# +# Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files and +# directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have large +# directories that should not be watched for changes. +exclude: + +# A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific +# branch or tag, with the format: # +# +# Garden will import the repository source code into this module, but read the module's config from the local +# garden.yml file. +repositoryUrl: + +# When false, disables pushing this module to remote registries. +allowPublish: true + +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: + + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: + + # The desired file contents as a string. + value: + +# The ModuleTemplate to use to generate the sub-modules of this module. +template: + +# A map of inputs to pass to the ModuleTemplate. These must match the inputs schema of the ModuleTemplate. +inputs: +``` + +## Configuration Keys + +### `apiVersion` + +The schema version of this config (currently not used). + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------------- | -------- | +| `string` | "garden.io/v0" | `"garden.io/v0"` | Yes | + +### `kind` + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------- | -------- | +| `string` | "Module" | `"Module"` | Yes | + +### `type` + +The type of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +type: "container" +``` + +### `name` + +The name of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +name: "my-sweet-module" +``` + +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + +### `description` + +A description of the module. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `disabled` + +Set this to `true` to disable the module. You can use this with conditional template strings to disable modules based on, for example, the current environment or other variables (e.g. `disabled: \${environment.name == "prod"}`). This can be handy when you only need certain modules for specific environments, e.g. only for development. + +Disabling a module means that any services, tasks and tests contained in it will not be deployed or run. It also means that the module is not built _unless_ it is declared as a build dependency by another enabled module (in which case building this module is necessary for the dependant to be built). + +If you disable the module, and its services, tasks or tests are referenced as _runtime_ dependencies, Garden will automatically ignore those dependency declarations. Note however that template strings referencing the module's service or task outputs (i.e. runtime outputs) will fail to resolve when the module is disabled, so you need to make sure to provide alternate values for those if you're using them, using conditional expressions. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `false` | No | + +### `include[]` + +Specify a list of POSIX-style paths or globs that should be regarded as the source files for this module. Files that do *not* match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + +Note that you can also _exclude_ files using the `exclude` field or by placing `.gardenignore` files in your source tree, which use the same format as `.gitignore` files. See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +Also note that specifying an empty list here means _no sources_ should be included. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +include: + - Dockerfile + - my-app.js +``` + +### `exclude[]` + +Specify a list of POSIX-style paths or glob patterns that should be excluded from the module. Files that match these paths or globs are excluded when computing the version of the module, when responding to filesystem watch events, and when staging builds. + +Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` field, the files/patterns specified here are filtered from the files matched by `include`. See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +Unlike the `modules.exclude` field in the project config, the filters here have _no effect_ on which files and directories are watched for changes. Use the project `modules.exclude` field to affect those, if you have large directories that should not be watched for changes. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +exclude: + - tmp/**/* + - '*.log' +``` + +### `repositoryUrl` + +A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific branch or tag, with the format: # + +Garden will import the repository source code into this module, but read the module's config from the local garden.yml file. + +| Type | Required | +| ----------------- | -------- | +| `gitUrl | string` | No | + +Example: + +```yaml +repositoryUrl: "git+https://github.com/org/repo.git#v2.0" +``` + +### `allowPublish` + +When false, disables pushing this module to remote registries. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `true` | No | + +### `generateFiles[]` + +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `generateFiles[].sourcePath` + +[generateFiles](#generatefiles) > sourcePath + +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + +### `generateFiles[].targetPath` + +[generateFiles](#generatefiles) > targetPath + +POSIX-style filename to write the resolved file contents to, relative to the path of the module. + +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `generateFiles[].value` + +[generateFiles](#generatefiles) > value + +The desired file contents as a string. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `template` + +The ModuleTemplate to use to generate the sub-modules of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `inputs` + +A map of inputs to pass to the ModuleTemplate. These must match the inputs schema of the ModuleTemplate. + +| Type | Required | +| -------- | -------- | +| `object` | No | + + +## Outputs + +### Module Outputs + +The following keys are available via the `${modules.}` template string key for `templated` +modules. + +### `${modules..buildPath}` + +The build path of the module. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${modules.my-module.buildPath} +``` + +### `${modules..path}` + +The local path of the module. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${modules.my-module.path} +``` + +### `${modules..version}` + +The current version of the module. + +| Type | +| -------- | +| `string` | + +Example: + +```yaml +my-variable: ${modules.my-module.version} +``` + diff --git a/docs/reference/module-types/terraform.md b/docs/reference/module-types/terraform.md index 96398c98c9..30786f71d3 100644 --- a/docs/reference/module-types/terraform.md +++ b/docs/reference/module-types/terraform.md @@ -29,7 +29,7 @@ The [first section](#complete-yaml-schema) contains the complete YAML schema, an The values in the schema below are the default values. ```yaml -# The schema version of this module's config (currently not used). +# The schema version of this config (currently not used). apiVersion: garden.io/v0 kind: Module @@ -40,6 +40,22 @@ type: # The name of this module. name: +# Specify how to build the module. Note that plugins may define additional keys on this object. +build: + # A list of modules that must be built before this module is built. + dependencies: + - # Module name to build ahead of this module. + name: + + # Specify one or more files or directories to copy from the built dependency to this module. + copy: + - # POSIX-style path or filename of the directory or file(s) to copy to the target. + source: + + # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. + # Defaults to to same as source path. + target: '' + # A description of the module. description: @@ -93,21 +109,22 @@ repositoryUrl: # When false, disables pushing this module to remote registries. allowPublish: true -# Specify how to build the module. Note that plugins may define additional keys on this object. -build: - # A list of modules that must be built before this module is built. - dependencies: - - # Module name to build ahead of this module. - name: +# A list of files to write to the module directory when resolving this module. This is useful to automatically +# generate (and template) any supporting files needed for the module. +generateFiles: + - # POSIX-style filename to read the source file contents from, relative to the path of the module (or the + # ModuleTemplate configuration file if one is being applied). + # This file may contain template strings, much like any other field in the configuration. + sourcePath: - # Specify one or more files or directories to copy from the built dependency to this module. - copy: - - # POSIX-style path or filename of the directory or file(s) to copy to the target. - source: + # POSIX-style filename to write the resolved file contents to, relative to the path of the module. + # + # Note that any existing file with the same name will be overwritten. If the path contains one or more + # directories, they will be automatically created if missing. + targetPath: - # POSIX-style path or filename to copy the directory or file(s), relative to the build directory. - # Defaults to to same as source path. - target: '' + # The desired file contents as a string. + value: # If set to true, Garden will run `terraform destroy` on the stack when calling `garden delete env` or `garden delete # service `. @@ -145,7 +162,7 @@ version: ### `apiVersion` -The schema version of this module's config (currently not used). +The schema version of this config (currently not used). | Type | Allowed Values | Default | Required | | -------- | -------------- | ---------------- | -------- | @@ -185,6 +202,74 @@ Example: name: "my-sweet-module" ``` +### `build` + +Specify how to build the module. Note that plugins may define additional keys on this object. + +| Type | Default | Required | +| -------- | --------------------- | -------- | +| `object` | `{"dependencies":[]}` | No | + +### `build.dependencies[]` + +[build](#build) > dependencies + +A list of modules that must be built before this module is built. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +Example: + +```yaml +build: + ... + dependencies: + - name: some-other-module-name +``` + +### `build.dependencies[].name` + +[build](#build) > [dependencies](#builddependencies) > name + +Module name to build ahead of this module. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `build.dependencies[].copy[]` + +[build](#build) > [dependencies](#builddependencies) > copy + +Specify one or more files or directories to copy from the built dependency to this module. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `build.dependencies[].copy[].source` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source + +POSIX-style path or filename of the directory or file(s) to copy to the target. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | Yes | + +### `build.dependencies[].copy[].target` + +[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target + +POSIX-style path or filename to copy the directory or file(s), relative to the build directory. +Defaults to to same as source path. + +| Type | Default | Required | +| ----------- | ------- | -------- | +| `posixPath` | `""` | No | + ### `description` A description of the module. @@ -269,73 +354,46 @@ When false, disables pushing this module to remote registries. | --------- | ------- | -------- | | `boolean` | `true` | No | -### `build` +### `generateFiles[]` -Specify how to build the module. Note that plugins may define additional keys on this object. +A list of files to write to the module directory when resolving this module. This is useful to automatically generate (and template) any supporting files needed for the module. -| Type | Default | Required | -| -------- | --------------------- | -------- | -| `object` | `{"dependencies":[]}` | No | +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | -### `build.dependencies[]` +### `generateFiles[].sourcePath` -[build](#build) > dependencies +[generateFiles](#generatefiles) > sourcePath -A list of modules that must be built before this module is built. +POSIX-style filename to read the source file contents from, relative to the path of the module (or the ModuleTemplate configuration file if one is being applied). +This file may contain template strings, much like any other field in the configuration. -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -Example: - -```yaml -build: - ... - dependencies: - - name: some-other-module-name -``` - -### `build.dependencies[].name` - -[build](#build) > [dependencies](#builddependencies) > name - -Module name to build ahead of this module. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `build.dependencies[].copy[]` - -[build](#build) > [dependencies](#builddependencies) > copy - -Specify one or more files or directories to copy from the built dependency to this module. +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | +### `generateFiles[].targetPath` -### `build.dependencies[].copy[].source` +[generateFiles](#generatefiles) > targetPath -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > source +POSIX-style filename to write the resolved file contents to, relative to the path of the module. -POSIX-style path or filename of the directory or file(s) to copy to the target. +Note that any existing file with the same name will be overwritten. If the path contains one or more directories, they will be automatically created if missing. | Type | Required | | ----------- | -------- | | `posixPath` | Yes | -### `build.dependencies[].copy[].target` +### `generateFiles[].value` -[build](#build) > [dependencies](#builddependencies) > [copy](#builddependenciescopy) > target +[generateFiles](#generatefiles) > value -POSIX-style path or filename to copy the directory or file(s), relative to the build directory. -Defaults to to same as source path. +The desired file contents as a string. -| Type | Default | Required | -| ----------- | ------- | -------- | -| `posixPath` | `""` | No | +| Type | Required | +| -------- | -------- | +| `string` | No | ### `allowDestroy` diff --git a/docs/reference/project-config.md b/docs/reference/project-config.md new file mode 100644 index 0000000000..50d2a6273f --- /dev/null +++ b/docs/reference/project-config.md @@ -0,0 +1,664 @@ +--- +order: 40 +title: Project Configuration +--- + +# Project Configuration Reference + +Below is the schema reference for [Project](../using-garden/projects.md) configuration files. For an introduction to configuring a Garden project, please look at our [configuration guide](../using-garden/configuration-overview.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the Project config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for Project configuration files. + +Note that individual providers, e.g. `kubernetes`, add their own project level configuration keys. The provider types are listed on the [Providers page](../reference/providers/README.md). + +Please refer to those for more details on provider configuration. + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +# Indicate what kind of config this is. +kind: Project + +# The name of the project. +name: + +# A list of environments to configure for the project. +environments: + - # The name of the environment. + name: + + # Set the default namespace to use. This can be templated to be user-specific, or to use an environment variable + # (e.g. in CI). + # + # You can also set this to `null`, in order to require an explicit namespace to be set on usage. This may be + # advisable for shared environments, but you may also be able to achieve the desired result by templating this + # field, as mentioned above. + defaultNamespace: default + + # Flag the environment as a production environment. + # + # Setting this flag to `true` will activate the protection on the `deploy`, `test`, `task`, `build`, + # and `dev` commands. A protected command will ask for a user confirmation every time is run against + # an environment marked as production. + # Run the command with the "--yes" flag to skip the check (e.g. when running Garden in CI). + # + # This flag is also passed on to every provider, and may affect how certain providers behave. + # For more details please check the documentation for the providers in use. + production: false + + # Specify a path (relative to the project root) to a file containing variables, that we apply on top of the + # _environment-specific_ `variables` field. + # + # The format of the files is determined by the configured file's extension: + # + # * `.env` - Standard "dotenv" format, as defined by [dotenv](https://github.com/motdotla/dotenv#rules). + # * `.yaml`/`.yml` - YAML. The file must consist of a YAML document, which must be a map (dictionary). Keys may + # contain any value type. + # * `.json` - JSON. Must contain a single JSON _object_ (not an array). + # + # _NOTE: The default varfile format will change to YAML in Garden v0.13, since YAML allows for definition of + # nested objects and arrays._ + # + # If you don't set the field and the `garden..env` file does not exist, + # we simply ignore it. If you do override the default value and the file doesn't exist, an error will be thrown. + varfile: + + # A key/value map of variables that modules can reference when using this environment. These take precedence over + # variables defined in the top-level `variables` field, but may also reference the top-level variables in template + # strings. + variables: {} + +# A list of providers that should be used for this project, and their configuration. Please refer to individual +# plugins/providers for details on how to configure them. +providers: + - # The name of the provider plugin to use. + name: + + # List other providers that should be resolved before this one. + dependencies: [] + + # If specified, this provider will only be used in the listed environments. Note that an empty array effectively + # disables the provider. To use a provider in all environments, omit this field. + environments: + +# The default environment to use when calling commands without the `--env` parameter. May include a namespace name, in +# the format `.`. Defaults to the first configured environment, with no namespace set. +defaultEnvironment: '' + +# Specify a list of filenames that should be used as ".ignore" files across the project, using the same syntax and +# semantics as `.gitignore` files. By default, patterns matched in `.gardenignore` files, found anywhere in the +# project, are ignored when scanning for modules and module sources (Note: prior to version 0.12.0, `.gitignore` files +# were also used by default). +# Note that these take precedence over the project `module.include` field, and module `include` fields, so any paths +# matched by the .ignore files will be ignored even if they are explicitly specified in those fields. +# See the [Configuration Files +# guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for +# details. +dotIgnoreFiles: + - .gardenignore + +# Control where to scan for modules in the project. +modules: + # Specify a list of POSIX-style paths or globs that should be scanned for Garden modules. + # + # Note that you can also _exclude_ path using the `exclude` field or by placing `.gardenignore` files in your source + # tree, which use the same format as `.gitignore` files. See the [Configuration Files + # guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for + # details. + # + # Unlike the `exclude` field, the paths/globs specified here have _no effect_ on which files and directories Garden + # watches for changes. Use the `exclude` field to affect those, if you have large directories that should not be + # watched for changes. + # + # Also note that specifying an empty list here means _no paths_ should be included. + include: + + # Specify a list of POSIX-style paths or glob patterns that should be excluded when scanning for modules. + # + # The filters here also affect which files and directories are watched for changes. So if you have a large number of + # directories in your project that should not be watched, you should specify them here. + # + # For example, you might want to exclude large vendor directories in your project from being scanned and watched, by + # setting `exclude: [node_modules/**/*, vendor/**/*]`. + # + # Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` + # field, the paths/patterns specified here are filtered from the files matched by `include`. + # + # The `include` field does _not_ affect which files are watched. + # + # See the [Configuration Files + # guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for + # details. + exclude: + +# A list of output values that the project should export. These are exported by the `garden get outputs` command, as +# well as when referencing a project as a sub-project within another project. +# +# You may use any template strings to specify the values, including references to provider outputs, module +# outputs and runtime outputs. For a full reference, see the [Output configuration +# context](https://docs.garden.io/reference/template-strings#output-configuration-context) section in the Template +# String Reference. +# +# Note that if any runtime outputs are referenced, the referenced services and tasks will be deployed and run if +# necessary when resolving the outputs. +outputs: + - # The name of the output value. + name: + + # The value for the output. Must be a primitive (string, number, boolean or null). May also be any valid template + # string. + value: + +# A list of remote sources to import into project. +sources: + - # The name of the source to import + name: + + # A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific + # branch or tag, with the format: # + repositoryUrl: + +# Specify a path (relative to the project root) to a file containing variables, that we apply on top of the +# project-wide `variables` field. +# +# The format of the files is determined by the configured file's extension: +# +# * `.env` - Standard "dotenv" format, as defined by [dotenv](https://github.com/motdotla/dotenv#rules). +# * `.yaml`/`.yml` - YAML. The file must consist of a YAML document, which must be a map (dictionary). Keys may +# contain any value type. +# * `.json` - JSON. Must contain a single JSON _object_ (not an array). +# +# _NOTE: The default varfile format will change to YAML in Garden v0.13, since YAML allows for definition of nested +# objects and arrays._ +# +# If you don't set the field and the `garden.env` file does not exist, we simply ignore it. +# If you do override the default value and the file doesn't exist, an error will be thrown. +# +# _Note that in many cases it is advisable to only use environment-specific var files, instead of combining +# multiple ones. See the `environments[].varfile` field for this option._ +varfile: garden.env + +# Key/value map of variables to configure for all environments. Keys may contain letters and numbers. Any values are +# permitted, including arrays and objects of any nesting. +variables: {} +``` + +## Configuration Keys + + +### `kind` + +Indicate what kind of config this is. + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ----------- | -------- | +| `string` | "Project" | `"Project"` | Yes | + +### `name` + +The name of the project. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +name: "my-sweet-project" +``` + +### `environments[]` + +A list of environments to configure for the project. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `environments[].name` + +[environments](#environments) > name + +The name of the environment. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +environments: + - name: "dev" +``` + +### `environments[].defaultNamespace` + +[environments](#environments) > defaultNamespace + +Set the default namespace to use. This can be templated to be user-specific, or to use an environment variable (e.g. in CI). + +You can also set this to `null`, in order to require an explicit namespace to be set on usage. This may be advisable for shared environments, but you may also be able to achieve the desired result by templating this field, as mentioned above. + +| Type | Default | Required | +| -------- | ----------- | -------- | +| `string` | `"default"` | No | + +Example: + +```yaml +environments: + - defaultNamespace: "user-${local.username}" +``` + +### `environments[].production` + +[environments](#environments) > production + +Flag the environment as a production environment. + +Setting this flag to `true` will activate the protection on the `deploy`, `test`, `task`, `build`, +and `dev` commands. A protected command will ask for a user confirmation every time is run against +an environment marked as production. +Run the command with the "--yes" flag to skip the check (e.g. when running Garden in CI). + +This flag is also passed on to every provider, and may affect how certain providers behave. +For more details please check the documentation for the providers in use. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `false` | No | + +Example: + +```yaml +environments: + - production: true +``` + +### `environments[].providers[]` + +[environments](#environments) > providers + +DEPRECATED - Please use the top-level `providers` field instead, and if needed use the `environments` key on the provider configurations to limit them to specific environments. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `environments[].providers[].name` + +[environments](#environments) > [providers](#environmentsproviders) > name + +The name of the provider plugin to use. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +environments: +``` + +### `environments[].providers[].dependencies[]` + +[environments](#environments) > [providers](#environmentsproviders) > dependencies + +List other providers that should be resolved before this one. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[string]` | `[]` | No | + +Example: + +```yaml +environments: +``` + +### `environments[].providers[].environments[]` + +[environments](#environments) > [providers](#environmentsproviders) > environments + +If specified, this provider will only be used in the listed environments. Note that an empty array effectively disables the provider. To use a provider in all environments, omit this field. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +Example: + +```yaml +environments: +``` + +### `environments[].varfile` + +[environments](#environments) > varfile + +Specify a path (relative to the project root) to a file containing variables, that we apply on top of the +_environment-specific_ `variables` field. + +The format of the files is determined by the configured file's extension: + +* `.env` - Standard "dotenv" format, as defined by [dotenv](https://github.com/motdotla/dotenv#rules). +* `.yaml`/`.yml` - YAML. The file must consist of a YAML document, which must be a map (dictionary). Keys may contain any value type. +* `.json` - JSON. Must contain a single JSON _object_ (not an array). + +_NOTE: The default varfile format will change to YAML in Garden v0.13, since YAML allows for definition of nested objects and arrays._ + +If you don't set the field and the `garden..env` file does not exist, +we simply ignore it. If you do override the default value and the file doesn't exist, an error will be thrown. + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + +Example: + +```yaml +environments: + - varfile: "custom.env" +``` + +### `environments[].variables` + +[environments](#environments) > variables + +A key/value map of variables that modules can reference when using this environment. These take precedence over variables defined in the top-level `variables` field, but may also reference the top-level variables in template strings. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `object` | `{}` | No | + +### `providers[]` + +A list of providers that should be used for this project, and their configuration. Please refer to individual plugins/providers for details on how to configure them. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `providers[].name` + +[providers](#providers) > name + +The name of the provider plugin to use. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +providers: + - name: "local-kubernetes" +``` + +### `providers[].dependencies[]` + +[providers](#providers) > dependencies + +List other providers that should be resolved before this one. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[string]` | `[]` | No | + +Example: + +```yaml +providers: + - dependencies: + - exec +``` + +### `providers[].environments[]` + +[providers](#providers) > environments + +If specified, this provider will only be used in the listed environments. Note that an empty array effectively disables the provider. To use a provider in all environments, omit this field. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +Example: + +```yaml +providers: + - environments: + - dev + - stage +``` + +### `defaultEnvironment` + +The default environment to use when calling commands without the `--env` parameter. May include a namespace name, in the format `.`. Defaults to the first configured environment, with no namespace set. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `string` | `""` | No | + +Example: + +```yaml +defaultEnvironment: "dev" +``` + +### `dotIgnoreFiles[]` + +Specify a list of filenames that should be used as ".ignore" files across the project, using the same syntax and semantics as `.gitignore` files. By default, patterns matched in `.gardenignore` files, found anywhere in the project, are ignored when scanning for modules and module sources (Note: prior to version 0.12.0, `.gitignore` files were also used by default). +Note that these take precedence over the project `module.include` field, and module `include` fields, so any paths matched by the .ignore files will be ignored even if they are explicitly specified in those fields. +See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +| Type | Default | Required | +| ------------------ | ------------------- | -------- | +| `array[posixPath]` | `[".gardenignore"]` | No | + +Example: + +```yaml +dotIgnoreFiles: + - .gardenignore + - .gitignore +``` + +### `modules` + +Control where to scan for modules in the project. + +| Type | Required | +| -------- | -------- | +| `object` | No | + +### `modules.include[]` + +[modules](#modules) > include + +Specify a list of POSIX-style paths or globs that should be scanned for Garden modules. + +Note that you can also _exclude_ path using the `exclude` field or by placing `.gardenignore` files in your source tree, which use the same format as `.gitignore` files. See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +Unlike the `exclude` field, the paths/globs specified here have _no effect_ on which files and directories Garden watches for changes. Use the `exclude` field to affect those, if you have large directories that should not be watched for changes. + +Also note that specifying an empty list here means _no paths_ should be included. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +modules: + ... + include: + - modules/**/* +``` + +### `modules.exclude[]` + +[modules](#modules) > exclude + +Specify a list of POSIX-style paths or glob patterns that should be excluded when scanning for modules. + +The filters here also affect which files and directories are watched for changes. So if you have a large number of directories in your project that should not be watched, you should specify them here. + +For example, you might want to exclude large vendor directories in your project from being scanned and watched, by setting `exclude: [node_modules/**/*, vendor/**/*]`. + +Note that you can also explicitly _include_ files using the `include` field. If you also specify the `include` field, the paths/patterns specified here are filtered from the files matched by `include`. + +The `include` field does _not_ affect which files are watched. + +See the [Configuration Files guide](https://docs.garden.io/using-garden/configuration-overview#including-excluding-files-and-directories) for details. + +| Type | Required | +| ------------------ | -------- | +| `array[posixPath]` | No | + +Example: + +```yaml +modules: + ... + exclude: + - public/**/* + - tmp/**/* +``` + +### `outputs[]` + +A list of output values that the project should export. These are exported by the `garden get outputs` command, as well as when referencing a project as a sub-project within another project. + +You may use any template strings to specify the values, including references to provider outputs, module +outputs and runtime outputs. For a full reference, see the [Output configuration context](https://docs.garden.io/reference/template-strings#output-configuration-context) section in the Template String Reference. + +Note that if any runtime outputs are referenced, the referenced services and tasks will be deployed and run if necessary when resolving the outputs. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `outputs[].name` + +[outputs](#outputs) > name + +The name of the output value. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +outputs: + - name: "my-output-key" +``` + +### `outputs[].value` + +[outputs](#outputs) > value + +The value for the output. Must be a primitive (string, number, boolean or null). May also be any valid template +string. + +| Type | Required | +| --------------------------- | -------- | +| `number | string | boolean` | Yes | + +Example: + +```yaml +outputs: + - value: "${modules.my-module.outputs.some-output}" +``` + +### `sources[]` + +A list of remote sources to import into project. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `sources[].name` + +[sources](#sources) > name + +The name of the source to import + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +sources: + - name: "my-external-repo" +``` + +### `sources[].repositoryUrl` + +[sources](#sources) > repositoryUrl + +A remote repository URL. Currently only supports git servers. Must contain a hash suffix pointing to a specific branch or tag, with the format: # + +| Type | Required | +| ----------------- | -------- | +| `gitUrl | string` | Yes | + +Example: + +```yaml +sources: + - repositoryUrl: "git+https://github.com/org/repo.git#v2.0" +``` + +### `varfile` + +Specify a path (relative to the project root) to a file containing variables, that we apply on top of the +project-wide `variables` field. + +The format of the files is determined by the configured file's extension: + +* `.env` - Standard "dotenv" format, as defined by [dotenv](https://github.com/motdotla/dotenv#rules). +* `.yaml`/`.yml` - YAML. The file must consist of a YAML document, which must be a map (dictionary). Keys may contain any value type. +* `.json` - JSON. Must contain a single JSON _object_ (not an array). + +_NOTE: The default varfile format will change to YAML in Garden v0.13, since YAML allows for definition of nested objects and arrays._ + +If you don't set the field and the `garden.env` file does not exist, we simply ignore it. +If you do override the default value and the file doesn't exist, an error will be thrown. + +_Note that in many cases it is advisable to only use environment-specific var files, instead of combining +multiple ones. See the `environments[].varfile` field for this option._ + +| Type | Default | Required | +| ----------- | -------------- | -------- | +| `posixPath` | `"garden.env"` | No | + +Example: + +```yaml +varfile: "custom.env" +``` + +### `variables` + +Key/value map of variables to configure for all environments. Keys may contain letters and numbers. Any values are permitted, including arrays and objects of any nesting. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `object` | `{}` | No | + diff --git a/docs/reference/template-strings.md b/docs/reference/template-strings.md index c9524889b5..57f664012f 100644 --- a/docs/reference/template-strings.md +++ b/docs/reference/template-strings.md @@ -1,5 +1,5 @@ --- -order: 5 +order: 50 title: Template Strings --- @@ -783,6 +783,52 @@ The task output value. Refer to individual [module type references](https://docs | --------------------------- | | `number | string | boolean` | +### `${parent.*}` + +Information about the parent module (if the module is a submodule, e.g. generated in a templated module). + +| Type | +| -------- | +| `object` | + +### `${parent.name}` + +The name of the parent module. + +| Type | +| -------- | +| `string` | + +### `${template.*}` + +Information about the ModuleTemplate used when generating the module. + +| Type | +| -------- | +| `object` | + +### `${template.name}` + +The name of the ModuleTemplate being resolved. + +| Type | +| -------- | +| `string` | + +### `${inputs.*}` + +The inputs provided to the module through a ModuleTemplate, if applicable. + +| Type | Default | +| -------- | ------- | +| `object` | `{}` | + +### `${inputs.}` + +| Type | +| ------------------------------------------------ | +| `number | string | boolean | link | array[link]` | + ## Output configuration context @@ -1124,6 +1170,52 @@ The task output value. Refer to individual [module type references](https://docs | --------------------------- | | `number | string | boolean` | +### `${parent.*}` + +Information about the parent module (if the module is a submodule, e.g. generated in a templated module). + +| Type | +| -------- | +| `object` | + +### `${parent.name}` + +The name of the parent module. + +| Type | +| -------- | +| `string` | + +### `${template.*}` + +Information about the ModuleTemplate used when generating the module. + +| Type | +| -------- | +| `object` | + +### `${template.name}` + +The name of the ModuleTemplate being resolved. + +| Type | +| -------- | +| `string` | + +### `${inputs.*}` + +The inputs provided to the module through a ModuleTemplate, if applicable. + +| Type | Default | +| -------- | ------- | +| `object` | `{}` | + +### `${inputs.}` + +| Type | +| ------------------------------------------------ | +| `number | string | boolean | link | array[link]` | + ## Workflow configuration context diff --git a/docs/reference/workflow-config.md b/docs/reference/workflow-config.md new file mode 100644 index 0000000000..1ddbd67788 --- /dev/null +++ b/docs/reference/workflow-config.md @@ -0,0 +1,470 @@ +--- +order: 45 +title: Workflow Configuration +--- + +# Workflow Configuration Reference + +Below is the schema reference for [Workflow](../using-garden/workflows.md) configuration files. For an introduction to configuring a Garden project, please look at our [configuration guide](../using-garden/configuration-overview.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for the configuration files. + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +# The schema version of this workflow's config (currently not used). +apiVersion: garden.io/v0 + +kind: Workflow + +# The name of this workflow. +name: + +# A description of the workflow. +description: + +# A list of files to write before starting the workflow. +# +# This is useful to e.g. create files required for provider authentication, and can be created from data stored in +# secrets or templated strings. +# +# Note that you cannot reference provider configuration in template strings within this field, since they are resolved +# after these files are generated. This means you can reference the files specified here in your provider +# configurations. +files: + - # POSIX-style path to write the file to, relative to the project root (or absolute). If the path contains one + # or more directories, they are created automatically if necessary. + # If any of those directories conflict with existing file paths, or if the file path conflicts with an existing + # directory path, an error will be thrown. + # **Any existing file with the same path will be overwritten, so be careful not to accidentally accidentally + # overwrite files unrelated to your workflow.** + path: + + # The file data as a string. + data: + + # The name of a Garden secret to copy the file data from (Garden Enterprise only). + secretName: + +# The number of hours to keep the workflow pod running after completion. +keepAliveHours: 48 + +limits: + # The maximum amount of CPU the workflow pod can use, in millicpus (i.e. 1000 = 1 CPU) + cpu: 1000 + + # The maximum amount of RAM the workflow pod can use, in megabytes (i.e. 1024 = 1 GB) + memory: 1024 + +# The steps the workflow should run. At least one step is required. Steps are run sequentially. If a step fails, +# subsequent steps are skipped. +steps: + - # An identifier to assign to this step. If none is specified, this defaults to "step-", where + # is the sequential number of the step (first step being number 1). + # + # This identifier is useful when referencing command outputs in following steps. For example, if you set this + # to "my-step", following steps can reference the \${steps.my-step.outputs.*} key in the `script` or `command` + # fields. + name: + + # A Garden command this step should run, followed by any required or optional arguments and flags. + # Arguments and options for the commands may be templated, including references to previous steps, but for now + # the commands themselves (as listed below) must be hard-coded. + # + # Supported commands: + # + # `[build]` + # `[delete, environment]` + # `[delete, service]` + # `[deploy]` + # `[exec]` + # `[get, config]` + # `[get, outputs]` + # `[get, status]` + # `[get, task-result]` + # `[get, test-result]` + # `[link, module]` + # `[link, source]` + # `[publish]` + # `[run, task]` + # `[run, test]` + # `[test]` + # `[update-remote, all]` + # `[update-remote, modules]` + # `[update-remote, sources]` + # + # + command: + + # A description of the workflow step. + description: + + # 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. + # + # The script may include template strings, including references to previous steps. + script: + + # Set to true to skip this step. Use this with template conditionals to skip steps for certain environments or + # scenarios. + skip: false + +# A list of triggers that determine when the workflow should be run, and which environment should be used (Garden +# Enterprise only). +triggers: + - # The environment name (from your project configuration) to use for the workflow when matched by this trigger. + environment: + + # The namespace to use for the workflow when matched by this trigger. Follows the namespacing setting used for + # this trigger's environment, as defined in your project's environment configs. + namespace: + + # A list of [GitHub events](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads) + # that should trigger this workflow. + # + # Supported events: + # + # `create`, `pull-request`, `pull-request-closed`, `pull-request-created`, `pull-request-opened`, + # `pull-request-updated`, `push`, `release`, `release-created`, `release-deleted`, `release-edited`, + # `release-prereleased`, `release-published`, `release-unpublished` + # + # + events: + + # If specified, only run the workflow for branches matching one of these filters. + branches: + + # If specified, only run the workflow for tags matching one of these filters. + tags: + + # If specified, do not run the workflow for branches matching one of these filters. + ignoreBranches: + + # If specified, do not run the workflow for tags matching one of these filters. + ignoreTags: +``` + +## Configuration Keys + + +### `apiVersion` + +The schema version of this workflow's config (currently not used). + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ---------------- | -------- | +| `string` | "garden.io/v0" | `"garden.io/v0"` | Yes | + +### `kind` + +| Type | Allowed Values | Default | Required | +| -------- | -------------- | ------------ | -------- | +| `string` | "Workflow" | `"Workflow"` | Yes | + +### `name` + +The name of this workflow. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +Example: + +```yaml +name: "my-workflow" +``` + +### `description` + +A description of the workflow. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `files[]` + +A list of files to write before starting the workflow. + +This is useful to e.g. create files required for provider authentication, and can be created from data stored in secrets or templated strings. + +Note that you cannot reference provider configuration in template strings within this field, since they are resolved after these files are generated. This means you can reference the files specified here in your provider configurations. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `files[].path` + +[files](#files) > path + +POSIX-style path to write the file to, relative to the project root (or absolute). If the path contains one +or more directories, they are created automatically if necessary. +If any of those directories conflict with existing file paths, or if the file path conflicts with an existing directory path, an error will be thrown. +**Any existing file with the same path will be overwritten, so be careful not to accidentally accidentally overwrite files unrelated to your workflow.** + +| Type | Required | +| ----------- | -------- | +| `posixPath` | No | + +Example: + +```yaml +files: + - path: ".auth/kubeconfig.yaml" +``` + +### `files[].data` + +[files](#files) > data + +The file data as a string. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `files[].secretName` + +[files](#files) > secretName + +The name of a Garden secret to copy the file data from (Garden Enterprise only). + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `keepAliveHours` + +The number of hours to keep the workflow pod running after completion. + +| Type | Default | Required | +| -------- | ------- | -------- | +| `number` | `48` | No | + +### `limits` + +| Type | Default | Required | +| -------- | ---------------------------- | -------- | +| `object` | `{"cpu":1000,"memory":1024}` | No | + +### `limits.cpu` + +[limits](#limits) > cpu + +The maximum amount of CPU the workflow pod can use, in millicpus (i.e. 1000 = 1 CPU) + +| Type | Default | Required | +| -------- | ------- | -------- | +| `number` | `1000` | No | + +### `limits.memory` + +[limits](#limits) > memory + +The maximum amount of RAM the workflow pod can use, in megabytes (i.e. 1024 = 1 GB) + +| Type | Default | Required | +| -------- | ------- | -------- | +| `number` | `1024` | No | + +### `steps[]` + +The steps the workflow should run. At least one step is required. Steps are run sequentially. If a step fails, subsequent steps are skipped. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | Yes | + +### `steps[].name` + +[steps](#steps) > name + +An identifier to assign to this step. If none is specified, this defaults to "step-", where + is the sequential number of the step (first step being number 1). + +This identifier is useful when referencing command outputs in following steps. For example, if you set this +to "my-step", following steps can reference the \${steps.my-step.outputs.*} key in the `script` or `command` +fields. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `steps[].command[]` + +[steps](#steps) > command + +A Garden command this step should run, followed by any required or optional arguments and flags. +Arguments and options for the commands may be templated, including references to previous steps, but for now +the commands themselves (as listed below) must be hard-coded. + +Supported commands: + +`[build]` +`[delete, environment]` +`[delete, service]` +`[deploy]` +`[exec]` +`[get, config]` +`[get, outputs]` +`[get, status]` +`[get, task-result]` +`[get, test-result]` +`[link, module]` +`[link, source]` +`[publish]` +`[run, task]` +`[run, test]` +`[test]` +`[update-remote, all]` +`[update-remote, modules]` +`[update-remote, sources]` + + + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +Example: + +```yaml +steps: + - command: + - run + - task + - my-task +``` + +### `steps[].description` + +[steps](#steps) > description + +A description of the workflow step. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `steps[].script` + +[steps](#steps) > script + +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. + +The script may include template strings, including references to previous steps. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `steps[].skip` + +[steps](#steps) > skip + +Set to true to skip this step. Use this with template conditionals to skip steps for certain environments or scenarios. + +| Type | Default | Required | +| --------- | ------- | -------- | +| `boolean` | `false` | No | + +Example: + +```yaml +steps: + - skip: "${environment.name != 'prod'}" +``` + +### `triggers[]` + +A list of triggers that determine when the workflow should be run, and which environment should be used (Garden Enterprise only). + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `triggers[].environment` + +[triggers](#triggers) > environment + +The environment name (from your project configuration) to use for the workflow when matched by this trigger. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `triggers[].namespace` + +[triggers](#triggers) > namespace + +The namespace to use for the workflow when matched by this trigger. Follows the namespacing setting used for this trigger's environment, as defined in your project's environment configs. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `triggers[].events[]` + +[triggers](#triggers) > events + +A list of [GitHub events](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads) that should trigger this workflow. + +Supported events: + +`create`, `pull-request`, `pull-request-closed`, `pull-request-created`, `pull-request-opened`, `pull-request-updated`, `push`, `release`, `release-created`, `release-deleted`, `release-edited`, `release-prereleased`, `release-published`, `release-unpublished` + + + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `triggers[].branches[]` + +[triggers](#triggers) > branches + +If specified, only run the workflow for branches matching one of these filters. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `triggers[].tags[]` + +[triggers](#triggers) > tags + +If specified, only run the workflow for tags matching one of these filters. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `triggers[].ignoreBranches[]` + +[triggers](#triggers) > ignoreBranches + +If specified, do not run the workflow for branches matching one of these filters. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `triggers[].ignoreTags[]` + +[triggers](#triggers) > ignoreTags + +If specified, do not run the workflow for tags matching one of these filters. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + diff --git a/docs/using-garden/README.md b/docs/using-garden/README.md index 66d0cda65f..29ddad988c 100644 --- a/docs/using-garden/README.md +++ b/docs/using-garden/README.md @@ -37,6 +37,10 @@ This guide shows you how Garden can run your tests for you. This guide shows you how Garden can run tasks for you, for example database migrations. +## [Module Templates](./module-templates.md) + +This guide shows you how to create custom templates and define templated modules. + ## [Workflows](./workflows.md) This guide introduces _workflows_, which are simple sequences of Garden commands and/or custom scripts. You can use workflows in CI, as well as diff --git a/docs/using-garden/configuration-overview.md b/docs/using-garden/configuration-overview.md index 3ccdcdb616..b469626429 100644 --- a/docs/using-garden/configuration-overview.md +++ b/docs/using-garden/configuration-overview.md @@ -12,6 +12,8 @@ The [project configuration](./projects.md) file should be located in the top-lev In addition, each of the project's [modules](../reference/glossary.md#module)' should be located in that module's top-level directory. Modules define all the individual components of your project, including [services](./services.md), [tasks](./tasks.md) and [tests](./tests.md). +You can define [module templates](./module-templates.md) to create your own abstractions, both within a project and across multiple projects. + Lastly, you can define [workflows](./workflows.md), to codify sequences of Garden commands and custom scripts. We suggest placing those in a `workflows.garden.yml` file in your project root. The other docs under the _Using Garden_ go into more details, and we highly recommend reading through all of them. @@ -95,4 +97,4 @@ We highly recommend reading all the other docs in this section to learn about th The [Variables and Templating guide](./variables-and-templating.md) explains how you can reference across different providers and modules, as well as how to supply secret values to your configuration. -Also, be sure to look at the [Config Files Reference](../reference/config.md) for more details on each of the available configuration fields, and the [Template Strings Reference](../reference/template-strings.md) for the keys available in template strings. +Also, be sure to look at the [Reference section](../reference/README.md) for more details on each of the available configuration fields, and the [Template Strings Reference](../reference/template-strings.md) for the keys available in template strings. diff --git a/docs/using-garden/module-templates.md b/docs/using-garden/module-templates.md new file mode 100644 index 0000000000..d2e5d0a51d --- /dev/null +++ b/docs/using-garden/module-templates.md @@ -0,0 +1,159 @@ +--- +order: 90 +title: Module Templates +--- + +# Module Templates + +You can create customized templates for modules or sets of modules, and render them using `templated` modules. These templates allow you to define your own schemas and abstractions, that are then translated at runtime to one or more modules, even including any supporting files (such as Kubernetes manifests, common configuration files, Dockerfiles etc.). + +This provides a powerful yet easy-to-use mechanism to tailor Garden's functionality to your needs, improve governance, reduce boilerplate, and to provide higher-level abstractions to application developers. + +These templates can be defined within a project, or in a separate repository that can be shared across multiple projects (using remote sources). + +{% hint style="info" %} +This feature was introduced in Garden 0.12.7. Please make sure you have an up-to-date version installed. +{% endhint %} + +## How it works + +We'll use the [`templated-k8s-container example`](https://github.com/garden-io/garden/tree/master/examples/templated-k8s-container) to illustrate how module templates work. This example has a `k8s-container` template, that generates one `container` module for building an image, and one `kubernetes` module for deploying that image. A template like this is useful to customize the Kubernetes manifests for your services, but of course it's just one simple example of what you could do. + +The template is defined like this: + +```yaml +kind: ModuleTemplate +name: k8s-container +inputsSchemaPath: module-templates.json +modules: + - type: container + name: ${parent.name}-image + description: ${parent.name} image + - type: kubernetes + name: ${parent.name}-manifests + build: + dependencies: ["${parent.name}-image"] + files: [.manifests.yml] + generateFiles: + - sourcePath: manifests.yml + targetPath: .manifests.yml +``` + +And it's used like this: + +```yaml +kind: Module +type: +template: k8s-container +name: my-service +inputs: + containerPort: 8080 + servicePort: 80 +``` + +First off, notice that we have a `kind: ModuleTemplate`, which defines the template, and then a module with `type: templated` which references and uses the `ModuleTemplate` via the `template` field. You can have any number of modules referencing the same template. + +The sections below describe the example in more detail. + +### Defining modules + +Each template should include one or more modules under the `modules` key. The schema for each module is exactly the same as for normal [Modules](./modules.md) with just a couple of differences: + +- In addition to any other template strings available when defining modules, you additionally have `${parent.name}`, `${template.name}` and `${inputs.*}` (more on inputs in the next section). **It's important that you use one of these for the names of the modules, so that every generated module has a unique name.**. +- You can set a `path` field on the module to any subdirectory relative to the templated module directory. The module directory will be created if necessary. + +### Defining and referencing inputs + +On the `ModuleTemplate`, the `inputsSchemaPath` field points to a standard [JSON Schema](https://json-schema.org/) file, which describes the schema for the `inputs` field on every module that references the template. In our example, it looks like this: + +```json +{ + "type": "object", + "properties": { + "containerPort": { + "type": "integer" + }, + "servicePort": { + "type": "integer" + }, + "replicas": { + "type": "integer", + "default": 3 + } + }, + "required": [ + "containerPort", + "servicePort" + ] +} +``` + +This simple schema says the `containerPort` and `servicePort` inputs are required, and that you can optionally set a `replicas` value as well. Any JSON Schema with `"type": "object"` is supported, and users can add any parameters that templated modules should specify. These could be ingress hostnames, paths, or really any flags that need to be customizable per module. + +These values can then be referenced using `${inputs.*}` template strings, anywhere under the `modules` field, as well as in any files specified under `modules[].generateFiles[].sourcePath`. + +### Generating files + +You can specify files that should be generated as modules are resolved, using the `modules[].generateFiles` field. These files can include any of the same template strings as when [defining modules](#defining-modules). + +_Note: It's usually advisable to add the generated files to your `.gitignore`, since they'll be dynamically generated._ + +In our example, we render a set of Kubernetes manifests. Here's the relevant section in the template: + +```yaml +... + generateFiles: + - sourcePath: manifests.yml + targetPath: .manifests.yml +``` + +This reads a source file from `template/manifests.yml` (the `sourcePath` is relative to the location of the _template_), and writes it to `module/.manifests.yml` (`targetPath` is relative to the _templated module_). + +Instead of specifying `sourcePath`, you can also specify `value` to provide the file contents directly as a string. + +### Module references within a templated module + +In many cases, it's important for the different modules in a single template to depend on one another, and to reference outputs from one another. You do this basically the same way as in normal modules, but because module names in a template are generally templated themselves, it's helpful to look at how to use templates in module references. + +Here's a section from the manifests file in our example: + +```yaml +... + containers: + - name: main + image: ${modules["${parent.name}-image"].outputs.deployment-image-id} + imagePullPolicy: "Always" + ports: + - name: http + containerPort: ${inputs.containerPort} +``` + +Notice the `image` field above. We use bracket notation to template the module name, whose outputs we want to reference: `${modules["${parent.name}-image"].outputs.deployment-image-id}`. Here we're using that to get the built image ID of the `${parent.name}-image` module in the same template. + +_Note that for a reference like this to work, that module also needs to be specified as a build dependency._ + +### Sharing templates + +If you have multiple projects it can be useful to have a central repository containing module templates, that can then be used in all your projects. + +To do that, simply place your `ModuleTemplate` configs in a repository (called something like `garden-templates`) and reference it as a remote source in your projects: + +```yaml +kind: Project +... +sources: + - name: templates + repositoryUrl: https://github.com/my-org/garden-templates:stable +``` + +Garden will then scan that repo when starting up, and you can reference the templates from it across your project. + +## Further reading + +- [ModuleTemplate reference docs](../reference/module-template-config.md). +- [`templated` module type reference docs](../reference/module-types/templated.md). +- [`templated-k8s-container example`](https://github.com/garden-io/garden/tree/master/examples/templated-k8s-container). + +## Next steps + +Take a look at our [Guides section](../guides/README.md) for more of an in-depth discussion on Garden concepts and capabilities. diff --git a/docs/using-garden/projects.md b/docs/using-garden/projects.md index a29613e1a9..105164fc09 100644 --- a/docs/using-garden/projects.md +++ b/docs/using-garden/projects.md @@ -60,7 +60,7 @@ Another option there would be to set `defaultNamespace: null` and require users For the other environments we leave `defaultNamespace` set to the default, which is simply `default`. So when you run Garden with `--env=staging`, that automatically expands to `--env=default.staging`. -The `staging` and `prod` environments have an additional flag set, the `production` flag. This flag changes some default behavior and turns on protection for certain Garden commands that might be destructive, e.g. `garden deploy`, requiring you to explicitly confirm that you want to execute them. See more details on that in [the reference](../reference/config.md#environmentsproduction). +The `staging` and `prod` environments have an additional flag set, the `production` flag. This flag changes some default behavior and turns on protection for certain Garden commands that might be destructive, e.g. `garden deploy`, requiring you to explicitly confirm that you want to execute them. See more details on that in [the reference](../reference/project-config.md#environmentsproduction). The current environment and namespace are frequently used in template strings. `${environment.name}` resolves to the environment name (in the above example, `local`, `dev`, `staging` or `prod`), `${environment.namespace}` resolves to the namespace, and `${environment.fullName}` resolves to the two combined with a DNS-style notation, e.g. `my-namespace.dev`. @@ -160,7 +160,7 @@ services: ## Further Reading -* [Full project config reference](../reference/config.md). +* [Full project config reference](../reference/project-config.md). * [A guide on template strings and setting project wide variables](../using-garden/variables-and-templating.md). * [Template string reference](../reference/template-strings.md). diff --git a/docs/using-garden/tasks.md b/docs/using-garden/tasks.md index 125580247a..62f90d21cd 100644 --- a/docs/using-garden/tasks.md +++ b/docs/using-garden/tasks.md @@ -173,4 +173,4 @@ For full task configuration by module type, please take a look at our [reference ## Next Steps -Take a look at our [Guides section](../guides/README.md) for more of an in-depth discussion on Garden concepts and capabilities. +Take a look at our [Workflows section](./workflows.md) to learn how to define sequences of Garden commands and custom scripts. diff --git a/docs/using-garden/variables-and-templating.md b/docs/using-garden/variables-and-templating.md index f0368be6c1..5693e177dc 100644 --- a/docs/using-garden/variables-and-templating.md +++ b/docs/using-garden/variables-and-templating.md @@ -206,7 +206,7 @@ This is useful when you don't want to provide _any_ value unless one is explicit A common use case for templating is to define variables in the project/environment configuration, and to use template strings to propagate values to modules in the project. -You can define them in your project configuration using the [`variables` key](../reference/config.md#variables), as well as the [`environment[].variables` key](../reference/config.md#environmentsvariables) for environment-specific values. +You can define them in your project configuration using the [`variables` key](../reference/project-config.md#variables), as well as the [`environment[].variables` key](../reference/project-config.md#environmentsvariables) for environment-specific values. You might, for example, define project defaults using the `variables` key, and then provide environment-specific overrides in the `environment[].variables` key for each environment. When merging the environment-specific variables and project-wide variables, we use a [JSON Merge Patch](https://tools.ietf.org/html/rfc7396). @@ -270,10 +270,10 @@ The format of the files is determined by the configured file extension: * `.yaml`/`.yml` - YAML. Must be a single document in the file, and must be a key/value map (but keys may contain any value types). * `.json` - JSON. Must contain a single JSON _object_ (not an array). -{% hint style="info" } +{% hint style="info" %} The default varfile format will change to YAML in Garden v0.13, since YAML allows for definition of nested objects and arrays. -In the meantime, to use YAML or JSON files, you must explicitly set the varfile name(s) in your project configuration, via the [`varfile`](../reference/config.md#varfile) and/or [`environments[].varfile`](../reference/config.md#environmentsvarfile)) fields. +In the meantime, to use YAML or JSON files, you must explicitly set the varfile name(s) in your project configuration, via the [`varfile`](../reference/project-config.md#varfile) and/or [`environments[].varfile`](../reference/project-config.md#environmentsvarfile)) fields. {% endhint %} You can also set variables on the command line, with `--var` flags. Note that while this is handy for ad-hoc invocations, we don't generally recommend relying on this for normal operations, since you lose a bit of visibility within your configuration. But here's one practical example: diff --git a/docs/using-garden/workflows.md b/docs/using-garden/workflows.md index 4e6aefbb93..ca339d4e82 100644 --- a/docs/using-garden/workflows.md +++ b/docs/using-garden/workflows.md @@ -125,7 +125,7 @@ triggers: branches: [feature/*] ``` -For a full description of how to configure triggers, check out the [workflows reference](../reference/config.md#triggers). +For a full description of how to configure triggers, check out the [workflows reference](../reference/workflow-config.md#triggers). ## Workflows and the Stack Graph diff --git a/examples/demo-project/backend/.gitignore b/examples/demo-project/backend/.gitignore index eb086d61c3..a365e5b54c 100644 --- a/examples/demo-project/backend/.gitignore +++ b/examples/demo-project/backend/.gitignore @@ -25,3 +25,6 @@ _testmain.go .vscode/settings.json webserver/*server* + +# Generated files +.*.yml diff --git a/examples/templated-k8s-container/README.md b/examples/templated-k8s-container/README.md new file mode 100644 index 0000000000..6dc158957c --- /dev/null +++ b/examples/templated-k8s-container/README.md @@ -0,0 +1,11 @@ +# templated-k8s-container example + +This example demonstrates the new module templating feature, which allows users to define templates that generate multiple templated modules—and even supporting files—by defining a ModuleTemplate and a `templated` module that references that template. + +In this example we define a `ModuleTemplate` in `template/garden.yml`, and use that template in `module/garden.yml` to generate a `container` module to build a container image, and a `kubernetes` module that deploys that image. + +This allows teams to reduce the boilerplate in their projects, as well as to tailor deployments to their specific needs. + +To see the generated modules in detail, you can run `garden get modules --output=yaml`. After running this you'll find a `.manifest.yml` file in the `module` directory, generated from the source template in `template/manifests.yml`. You can take a look at that to see the Kubernetes manifests that will be used for deployment. + +To test the example with a local Kubernetes cluster, simply run `garden deploy`. diff --git a/examples/templated-k8s-container/module/.dockerignore b/examples/templated-k8s-container/module/.dockerignore new file mode 100644 index 0000000000..4810c6a100 --- /dev/null +++ b/examples/templated-k8s-container/module/.dockerignore @@ -0,0 +1,3 @@ +Dockerfile +.* +*.yml diff --git a/examples/templated-k8s-container/module/.gardenignore b/examples/templated-k8s-container/module/.gardenignore new file mode 100644 index 0000000000..eb086d61c3 --- /dev/null +++ b/examples/templated-k8s-container/module/.gardenignore @@ -0,0 +1,27 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.vscode/settings.json +webserver/*server* diff --git a/examples/templated-k8s-container/module/.gitignore b/examples/templated-k8s-container/module/.gitignore new file mode 100644 index 0000000000..83bee0a283 --- /dev/null +++ b/examples/templated-k8s-container/module/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.vscode/settings.json +webserver/*server* + +.manifests.yml diff --git a/examples/templated-k8s-container/module/Dockerfile b/examples/templated-k8s-container/module/Dockerfile new file mode 100644 index 0000000000..56b4c70bdd --- /dev/null +++ b/examples/templated-k8s-container/module/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.8.3-alpine + +ENV PORT=8080 +EXPOSE ${PORT} +WORKDIR /app + +COPY main.go . + +RUN go build -o main . + +ENTRYPOINT ["./main"] diff --git a/examples/templated-k8s-container/module/garden.yml b/examples/templated-k8s-container/module/garden.yml new file mode 100644 index 0000000000..838f0ba277 --- /dev/null +++ b/examples/templated-k8s-container/module/garden.yml @@ -0,0 +1,7 @@ +kind: Module +type: templated +template: k8s-container +name: my-service +inputs: + containerPort: 8080 + servicePort: 80 diff --git a/examples/templated-k8s-container/module/main.go b/examples/templated-k8s-container/module/main.go new file mode 100644 index 0000000000..34ae7d9838 --- /dev/null +++ b/examples/templated-k8s-container/module/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello from Go!") +} + +func main() { + http.HandleFunc("/hello-backend", handler) + fmt.Println("Server running...") + + http.ListenAndServe(":8080", nil) +} diff --git a/examples/templated-k8s-container/project.garden.yml b/examples/templated-k8s-container/project.garden.yml new file mode 100644 index 0000000000..7c66592b63 --- /dev/null +++ b/examples/templated-k8s-container/project.garden.yml @@ -0,0 +1,15 @@ +kind: Project +name: templated-k8s-container +environments: + - name: local + - name: testing +providers: + - name: local-kubernetes + environments: [local] + - name: kubernetes + environments: [testing] + # Replace these values as appropriate + context: gke_garden-dev-200012_europe-west1-b_garden-dev-1 + namespace: templated-k8s-container-testing-${local.username} + defaultHostname: ${local.username}-templated-k8s-container.dev-1.sys.garden + buildMode: cluster-docker diff --git a/examples/templated-k8s-container/template/garden.yml b/examples/templated-k8s-container/template/garden.yml new file mode 100644 index 0000000000..0c2a1c59a2 --- /dev/null +++ b/examples/templated-k8s-container/template/garden.yml @@ -0,0 +1,15 @@ +kind: ModuleTemplate +name: k8s-container +inputsSchemaPath: schema.json +modules: + - type: container + name: ${parent.name}-image + description: ${parent.name} image + - type: kubernetes + name: ${parent.name}-manifests + build: + dependencies: ["${parent.name}-image"] + files: [.manifests.yml] + generateFiles: + - sourcePath: manifests.yml + targetPath: .manifests.yml diff --git a/examples/templated-k8s-container/template/manifests.yml b/examples/templated-k8s-container/template/manifests.yml new file mode 100644 index 0000000000..d51cf93587 --- /dev/null +++ b/examples/templated-k8s-container/template/manifests.yml @@ -0,0 +1,39 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: ${parent.name} + labels: + service: ${parent.name} +spec: + type: ClusterIP + ports: + - name: http + port: ${inputs.servicePort} + targetPort: http + selector: + service: ${parent.name} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${parent.name} + labels: + service: ${parent.name} +spec: + replicas: ${inputs.replicas || 3} + selector: + matchLabels: + service: ${parent.name} + template: + metadata: + labels: + service: ${parent.name} + spec: + containers: + - name: main + image: ${modules["${parent.name}-image"].outputs.deployment-image-id} + imagePullPolicy: "Always" + ports: + - name: http + containerPort: ${inputs.containerPort} diff --git a/examples/templated-k8s-container/template/schema.json b/examples/templated-k8s-container/template/schema.json new file mode 100644 index 0000000000..94c0a17eea --- /dev/null +++ b/examples/templated-k8s-container/template/schema.json @@ -0,0 +1,19 @@ +{ + "type": "object", + "properties": { + "containerPort": { + "type": "integer" + }, + "servicePort": { + "type": "integer" + }, + "replicas": { + "type": "integer", + "default": 3 + } + }, + "required": [ + "containerPort", + "servicePort" + ] +} \ No newline at end of file diff --git a/static/docs/templates/base-config.hbs b/static/docs/templates/base-config.hbs deleted file mode 100644 index bc7df1b0d4..0000000000 --- a/static/docs/templates/base-config.hbs +++ /dev/null @@ -1,52 +0,0 @@ ---- -order: 4 -title: Config Files ---- - -# garden.yml reference - -Below is the schema reference for the [Project](#project-configuration-keys), [Module](#module-configuration-keys) and [Workflow](#workflow-configuration-keys) `garden.yml` configuration files. For an introduction to configuring a Garden project, please look at our [configuration guide](../using-garden/configuration-overview.md). - -The reference is divided into a few sections: -* [Project YAML schema](#project-yaml-schema) contains the Project config YAML schema -* [Project configuration keys](#project-configuration-keys) describes each individual schema key for Project configuration files. -* [Module YAML schema](#module-yaml-schema) contains the Module config YAML schema -* [Module configuration keys](#module-configuration-keys) describes each individual schema key for Module configuration files. -* [Workflow YAML schema](#workflow-yaml-schema) contains the Workflow config YAML schema -* [Workflow configuration keys](#module-configuration-keys) describes each individual schema key for Workflow configuration files. - -Note that individual providers, e.g. `kubernetes`, add their own project level configuration keys. The provider types are listed on the [Providers page](../reference/providers/README.md). - -Likewise, individual module types, e.g. `container`, add additional configuration keys at the module level. Module types are listed on the [Module Types page](../reference/module-types/README.md). - -Please refer to those for more details on provider and module configuration. - -## Project YAML schema - -The values in the schema below are the default values. - -```yaml -{{{projectYaml}}} -``` - -## Project configuration keys - -{{{projectMarkdownReference}}} - -## Module YAML schema -```yaml -{{{moduleYaml}}} -``` - -## Module configuration keys - -{{{moduleMarkdownReference}}} - -## Workflow YAML schema -```yaml -{{{workflowYaml}}} -``` - -## Workflow configuration keys - -{{{workflowMarkdownReference}}} diff --git a/static/docs/templates/commands.hbs b/static/docs/templates/commands.hbs index 3c8efcb3ca..f1e1e007ee 100644 --- a/static/docs/templates/commands.hbs +++ b/static/docs/templates/commands.hbs @@ -1,5 +1,5 @@ --- -order: 3 +order: 30 title: Commands --- diff --git a/static/docs/templates/module-template-config.hbs b/static/docs/templates/module-template-config.hbs new file mode 100644 index 0000000000..aa9f2b1996 --- /dev/null +++ b/static/docs/templates/module-template-config.hbs @@ -0,0 +1,26 @@ +--- +order: 43 +title: Module Template Configuration +--- + +# Module Template Configuration Reference + +Below is the schema reference for `ModuleTemplate` configuration files. To learn more about module templates, see the [Module Templates guide](../using-garden/module-templates.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for the configuration files. + +Also check out the [`templated` module type reference](./module-types/templated.md). + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +{{{yaml}}} +``` + +## Configuration Keys + +{{{markdownReference}}} diff --git a/static/docs/templates/project-config.hbs b/static/docs/templates/project-config.hbs new file mode 100644 index 0000000000..55c8867ef8 --- /dev/null +++ b/static/docs/templates/project-config.hbs @@ -0,0 +1,28 @@ +--- +order: 40 +title: Project Configuration +--- + +# Project Configuration Reference + +Below is the schema reference for [Project](../using-garden/projects.md) configuration files. For an introduction to configuring a Garden project, please look at our [configuration guide](../using-garden/configuration-overview.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the Project config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for Project configuration files. + +Note that individual providers, e.g. `kubernetes`, add their own project level configuration keys. The provider types are listed on the [Providers page](../reference/providers/README.md). + +Please refer to those for more details on provider configuration. + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +{{{yaml}}} +``` + +## Configuration Keys + +{{{markdownReference}}} diff --git a/static/docs/templates/template-strings.hbs b/static/docs/templates/template-strings.hbs index f32185f577..cc75b06e67 100644 --- a/static/docs/templates/template-strings.hbs +++ b/static/docs/templates/template-strings.hbs @@ -1,5 +1,5 @@ --- -order: 5 +order: 50 title: Template Strings --- diff --git a/static/docs/templates/workflow-config.hbs b/static/docs/templates/workflow-config.hbs new file mode 100644 index 0000000000..23ab6f890f --- /dev/null +++ b/static/docs/templates/workflow-config.hbs @@ -0,0 +1,24 @@ +--- +order: 45 +title: Workflow Configuration +--- + +# Workflow Configuration Reference + +Below is the schema reference for [Workflow](../using-garden/workflows.md) configuration files. For an introduction to configuring a Garden project, please look at our [configuration guide](../using-garden/configuration-overview.md). + +The reference is divided into two sections: +* [YAML Schema](#yaml-schema) contains the config YAML schema +* [Configuration keys](#configuration-keys) describes each individual schema key for the configuration files. + +## YAML Schema + +The values in the schema below are the default values. + +```yaml +{{{yaml}}} +``` + +## Configuration Keys + +{{{markdownReference}}}