From a0d79f70d76447105294e18c6aa63ff12dc60a3e Mon Sep 17 00:00:00 2001 From: Veeti Haapsamo Date: Wed, 31 May 2023 18:14:05 +0300 Subject: [PATCH] fix: pulumi module validation (#4497) * fix: pulumi module validation taking inspiration from the terraform plugin, and how the module and action validation is handled in there. * chore: yarn run generate-docs --- docs/reference/module-types/pulumi.md | 12 +++ plugins/pulumi/config.ts | 128 ++++++++++++++------------ plugins/pulumi/index.ts | 31 +++++-- 3 files changed, 106 insertions(+), 65 deletions(-) diff --git a/docs/reference/module-types/pulumi.md b/docs/reference/module-types/pulumi.md index b7cb312d38..ae2fbdc8f2 100644 --- a/docs/reference/module-types/pulumi.md +++ b/docs/reference/module-types/pulumi.md @@ -156,6 +156,10 @@ variables: # varfiles exist). varfile: +# 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. +dependencies: [] + # If set to true, Garden will destroy the stack when calling `garden cleanup namespace` or `garden cleanup deploy # `. # This is useful to prevent unintentional destroys in production or shared environments. @@ -524,6 +528,14 @@ Example: varfile: "my-module.env" ``` +### `dependencies[]` + +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. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[string]` | `[]` | No | + ### `allowDestroy` If set to true, Garden will destroy the stack when calling `garden cleanup namespace` or `garden cleanup deploy `. diff --git a/plugins/pulumi/config.ts b/plugins/pulumi/config.ts index 88d3af46a9..ea99c86c22 100644 --- a/plugins/pulumi/config.ts +++ b/plugins/pulumi/config.ts @@ -12,6 +12,9 @@ import { DeployAction, DeployActionConfig } from "@garden-io/core/build/src/acti import { GardenModule } from "@garden-io/sdk/types" import { dedent } from "@garden-io/sdk/util/string" import { defaultPulumiVersion, supportedVersions } from "./cli" +import { baseBuildSpecSchema } from "@garden-io/core/build/src/config/module" +import { dependenciesSchema } from "@garden-io/core/build/src/config/service" +import { createSchema } from "@garden-io/core/build/src/config/common" type PulumiProviderConfig = GenericProviderConfig & { version: string | null @@ -91,58 +94,57 @@ export interface PulumiModule extends GardenModule {} // Validate that the path ends in .yaml or .yml const yamlFileRegex = /(\.yaml)|(\.yml)$/ -export const pulumiDeploySpecSchema = () => - joi.object().keys({ - allowDestroy: joi.boolean().default(true).description(dedent` - If set to true, Garden will destroy the stack when calling \`garden cleanup namespace\` or \`garden cleanup deploy \`. - This is useful to prevent unintentional destroys in production or shared environments. +export const pulumiDeploySchemaKeys = () => ({ + allowDestroy: joi.boolean().default(true).description(dedent` + If set to true, Garden will destroy the stack when calling \`garden cleanup namespace\` or \`garden cleanup deploy \`. + This is useful to prevent unintentional destroys in production or shared environments. `), - autoApply: joi.boolean().default(true).description(dedent` - If set to false, deployments will fail unless a \`planPath\` is provided for this deploy action. This is useful when deploying to - production or shared environments, or when the action deploys infrastructure that you don't want to unintentionally update/create. + autoApply: joi.boolean().default(true).description(dedent` + If set to false, deployments will fail unless a \`planPath\` is provided for this deploy action. This is useful when deploying to + production or shared environments, or when the action deploys infrastructure that you don't want to unintentionally update/create. `), - createStack: joi.boolean().default(false).description(dedent` - If set to true, Garden will automatically create the stack if it doesn't already exist. + createStack: joi.boolean().default(false).description(dedent` + If set to true, Garden will automatically create the stack if it doesn't already exist. `), - root: joi.posixPath().subPathOnly().default(".").description(dedent` - Specify the path to the Pulumi project root, relative to the deploy action's root. + root: joi.posixPath().subPathOnly().default(".").description(dedent` + Specify the path to the Pulumi project root, relative to the deploy action's root. `), - pulumiVariables: joiVariables().default({}).description(dedent` - A map of config variables to use when applying the stack. These are merged with the contents of any \`pulumiVarfiles\` provided - for this deploy action. The deploy action's stack config will be overwritten with the resulting merged config. - Variables declared here override any conflicting config variables defined in this deploy action's \`pulumiVarfiles\`. + pulumiVariables: joiVariables().default({}).description(dedent` + A map of config variables to use when applying the stack. These are merged with the contents of any \`pulumiVarfiles\` provided + for this deploy action. The deploy action's stack config will be overwritten with the resulting merged config. + Variables declared here override any conflicting config variables defined in this deploy action's \`pulumiVarfiles\`. - Note: \`pulumiVariables\` should not include action outputs from other pulumi deploy actions when \`cacheStatus\` is set to true, since - the outputs may change from the time the stack status of the dependency action is initially queried to when it's been deployed. + Note: \`pulumiVariables\` should not include action outputs from other pulumi deploy actions when \`cacheStatus\` is set to true, since + the outputs may change from the time the stack status of the dependency action is initially queried to when it's been deployed. - Instead, use pulumi stack references when using the \`cacheStatus\` config option. + Instead, use pulumi stack references when using the \`cacheStatus\` config option. `), - pulumiVarfiles: joiSparseArray(joi.posixPath().pattern(yamlFileRegex)).description( - dedent` - Specify one or more paths (relative to the deploy action's root) to YAML files containing pulumi config variables. + pulumiVarfiles: joiSparseArray(joi.posixPath().pattern(yamlFileRegex)).description( + dedent` + Specify one or more paths (relative to the deploy action's root) to YAML files containing pulumi config variables. - Templated paths that resolve to \`null\`, \`undefined\` or an empty string are ignored. + Templated paths that resolve to \`null\`, \`undefined\` or an empty string are ignored. - Any Garden template strings in these varfiles will be resolved when the files are loaded. + Any Garden template strings in these varfiles will be resolved when the files are loaded. - Each file must consist of a single YAML document, which must be a map (dictionary). Keys may contain any - value type. + Each file must consist of a single YAML document, which must be a map (dictionary). Keys may contain any + value type. - If one or more varfiles is not found, no error is thrown (that varfile path is simply ignored). + If one or more varfiles is not found, no error is thrown (that varfile path is simply ignored). - Note: There is no need to nest the variables under a \`config\` field as is done in a pulumi - config. Simply specify all the config variables at the top level. + Note: There is no need to nest the variables under a \`config\` field as is done in a pulumi + config. Simply specify all the config variables at the top level. ` - ), - orgName: joi.string().optional().empty(["", null]).description(dedent` - The name of the pulumi organization to use. Overrides the \`orgName\` set on the pulumi provider (if any). - To use the default org, set to null. + ), + orgName: joi.string().optional().empty(["", null]).description(dedent` + The name of the pulumi organization to use. Overrides the \`orgName\` set on the pulumi provider (if any). + To use the default org, set to null. `), - cacheStatus: joi - .boolean() - .default(false) - .description( - dedent` + cacheStatus: joi + .boolean() + .default(false) + .description( + dedent` When set to true, the pulumi stack will be tagged with the Garden service version when deploying. The tag will then be used for service status checks for this service. If the version doesn't change between deploys, the subsequent deploy is skipped. @@ -155,26 +157,26 @@ export const pulumiDeploySpecSchema = () => \`cacheStatus: true\` is not supported for self-managed state backends. ` - ), - stackReferences: joiSparseArray(joi.string()) - .description( - dedent` + ), + stackReferences: joiSparseArray(joi.string()) + .description( + dedent` When setting \`cacheStatus\` to true for this deploy action, you should include all stack references used by this deploy action's pulumi stack in this field. This lets Garden know to redeploy the pulumi stack if the output values of one or more of these stack references have changed since the last deployment. ` - ) - .example([ - "${actions.deploy.some-pulumi-deploy-action.outputs.ip-address}", - "${actions.deploy.some-other-pulumi-deploy-action.outputs.database-url}", - ]), - deployFromPreview: joi - .boolean() - .default(false) - .description( - dedent` + ) + .example([ + "${actions.deploy.some-pulumi-deploy-action.outputs.ip-address}", + "${actions.deploy.some-other-pulumi-deploy-action.outputs.database-url}", + ]), + deployFromPreview: joi + .boolean() + .default(false) + .description( + dedent` When set to true, will use pulumi plans generated by the \`garden plugins pulumi preview\` command when deploying, and will fail if no plan exists locally for the deploy action. @@ -185,9 +187,21 @@ export const pulumiDeploySpecSchema = () => This option is intended for two-phase pulumi deployments, where pulumi preview diffs are first reviewed (e.g. during code review). ` - ), - stack: joi - .string() - .allow(null) - .description("The name of the pulumi stack to use. Defaults to the current environment name."), + ), + stack: joi + .string() + .allow(null) + .description("The name of the pulumi stack to use. Defaults to the current environment name."), +}) + +export const pulumiDeploySchema = createSchema({ + name: "pulumi:Deploy", + keys: pulumiDeploySchemaKeys, +}) + +export const pulumiModuleSchema = () => + joi.object().keys({ + build: baseBuildSpecSchema(), + dependencies: dependenciesSchema(), + ...pulumiDeploySchemaKeys(), }) diff --git a/plugins/pulumi/index.ts b/plugins/pulumi/index.ts index 35b3fbfd8e..72164d9955 100644 --- a/plugins/pulumi/index.ts +++ b/plugins/pulumi/index.ts @@ -13,7 +13,13 @@ import { getPulumiCommands } from "./commands" import { joiVariables } from "@garden-io/core/build/src/config/common" import { pulumiCliSPecs } from "./cli" -import { PulumiDeployConfig, pulumiDeploySpecSchema, PulumiModule, pulumiProviderConfigSchema } from "./config" +import { + PulumiDeployConfig, + pulumiDeploySchema, + PulumiModule, + pulumiModuleSchema, + pulumiProviderConfigSchema, +} from "./config" import { ExecBuildConfig } from "@garden-io/core/build/src/plugins/exec/config" import { join } from "path" import { pathExists } from "fs-extra" @@ -52,7 +58,7 @@ export const gardenPlugin = () => Stack outputs are made available as action outputs. These can then be referenced by other actions under \`${actionOutputsTemplateString}\`. You can template in those values as e.g. command arguments or environment variables for other services. `, - schema: pulumiDeploySpecSchema(), + schema: pulumiDeploySchema(), runtimeOutputsSchema: outputsSchema(), handlers: { validate: async ({ action }) => { @@ -88,7 +94,7 @@ export const gardenPlugin = () => Stack outputs are made available as service outputs. These can then be referenced by other actions under \`${moduleOutputsTemplateString}\`. You can template in those values as e.g. command arguments or environment variables for other services. `, - schema: pulumiDeploySpecSchema(), + schema: pulumiModuleSchema(), needsBuild: false, handlers: { configure: configurePulumiModule, @@ -101,20 +107,29 @@ export const gardenPlugin = () => actions.push(dummyBuild) } - actions.push({ + const deployAction: PulumiDeployConfig = { kind: "Deploy", type: "pulumi", name: module.name, ...params.baseFields, - - build: dummyBuild?.name, dependencies: prepareRuntimeDependencies(module.spec.dependencies, dummyBuild), timeout: defaultPulumiTimeoutSec, spec: { - ...omit(module.spec, ["dependencies"]), + allowDestroy: module.spec.allowDestroy || true, + autoApply: module.spec.autoApply || true, + createStack: module.spec.createStack || false, + pulumiVariables: module.spec.pulumiVariables || {}, + pulumiVarfiles: module.spec.pulumiVarfiles || [], + cacheStatus: module.spec.cacheStatus || false, + stackReferences: module.spec.stackReferences || [], + deployFromPreview: module.spec.deployFromPreview || false, + root: module.spec.root || ".", + ...omit(module.spec, ["build", "dependencies"]), }, - }) + } + + actions.push(deployAction) return { group: {