Skip to content

Commit

Permalink
feat(config): add ${command.name} and ${command.params} for templating
Browse files Browse the repository at this point in the history
This allows users to template by the current command context in some
interesting ways. A simple example would be template based on whether
the test command is being run.

A more complex example would be something like
`${command.params contains 'hot' && command.params.hot contains 'my-service'}`,
basically checking for a specific parameter.

Later I'd like to make this sort of thing more elegant in terms of
syntax, but it does already work nicely if used with a bit of care.
  • Loading branch information
edvald authored and thsig committed Feb 22, 2021
1 parent 6919bc5 commit d31922d
Show file tree
Hide file tree
Showing 38 changed files with 665 additions and 240 deletions.
2 changes: 1 addition & 1 deletion cli/src/add-version-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ try {
* Write .garden-version files for modules in garden-system/static.
*/
async function addVersionFiles() {
const garden = await Garden.factory(STATIC_DIR)
const garden = await Garden.factory(STATIC_DIR, { commandInfo: { name: "add-version-files", args: {}, opts: {} } })

const moduleConfigs = await garden.getRawModuleConfigs()

Expand Down
2 changes: 1 addition & 1 deletion core/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { GardenPluginCallback } from "../types/plugin/plugin"
import { renderError } from "../logger/renderers"
import { EnterpriseApi } from "../enterprise/api"

export async function makeDummyGarden(root: string, gardenOpts: GardenOpts = {}) {
export async function makeDummyGarden(root: string, gardenOpts: GardenOpts) {
const environments = gardenOpts.environmentName
? [{ name: parseEnvironment(gardenOpts.environmentName).environment, defaultNamespace, variables: {} }]
: defaultEnvironments
Expand Down
16 changes: 15 additions & 1 deletion core/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { pathExists, readFile } from "fs-extra"
import { resolve, basename, relative } from "path"
import chalk = require("chalk")
import { safeLoad } from "js-yaml"
import { CommandInfo } from "../plugin-context"

export const defaultVarfilePath = "garden.env"
export const defaultEnvVarfilePath = (environmentName: string) => `garden.${environmentName}.env`
Expand Down Expand Up @@ -402,13 +403,15 @@ export function resolveProjectConfig({
branch,
username,
secrets,
commandInfo,
}: {
defaultEnvironment: string
config: ProjectConfig
artifactsPath: string
branch: string
username: string
secrets: PrimitiveMap
commandInfo: CommandInfo
}): ProjectConfig {
// Resolve template strings for non-environment-specific fields
const { environments = [], name } = config
Expand All @@ -421,7 +424,15 @@ export function resolveProjectConfig({
variables: config.variables,
environments: [],
},
new ProjectConfigContext({ projectName: name, projectRoot: config.path, artifactsPath, branch, username, secrets })
new ProjectConfigContext({
projectName: name,
projectRoot: config.path,
artifactsPath,
branch,
username,
secrets,
commandInfo,
})
)

// Validate after resolving global fields
Expand Down Expand Up @@ -500,13 +511,15 @@ export async function pickEnvironment({
branch,
username,
secrets,
commandInfo,
}: {
projectConfig: ProjectConfig
envString: string
artifactsPath: string
branch: string
username: string
secrets: PrimitiveMap
commandInfo: CommandInfo
}) {
const { environments, name: projectName, path: projectRoot } = projectConfig

Expand Down Expand Up @@ -539,6 +552,7 @@ export async function pickEnvironment({
username,
variables: projectVariables,
secrets,
commandInfo,
})
)

Expand Down
87 changes: 60 additions & 27 deletions core/src/config/template-contexts/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PrimitiveMap, joiIdentifierMap, joiStringMap, joiPrimitive, DeepPrimiti
import { joi } from "../common"
import { deline, dedent } from "../../util/string"
import { schema, ConfigContext } from "./base"
import { CommandInfo } from "../../plugin-context"

class LocalContext extends ConfigContext {
@schema(
Expand Down Expand Up @@ -112,6 +113,52 @@ class GitContext extends ConfigContext {
}
}

const commandHotExample = "${command.params contains 'hot-reload' && command.params.hot-reload contains 'my-service'}"

class CommandContext extends ConfigContext {
@schema(
joi
.string()
.description(
dedent`
The currently running Garden CLI command, without positional arguments or option flags. This can be handy to e.g. change some variables based on whether you're running \`garden test\` or some other specific command.
Note that this will currently always resolve to \`"run workflow"\` when running Workflows, as opposed to individual workflow step commands. This may be revisited at a later time, but currently all configuration is resolved once for all workflow steps.
`
)
.example("deploy")
)
public name: string

@schema(
joiStringMap(joi.any())
.description(
dedent`
A map of all parameters set when calling the current command. This includes both positional arguments and option flags, and includes any default values set by the framework or specific command. This can be powerful if used right, but do take care since different parameters are only available in certain commands, some have array values etc.
For example, to see if a service is in hot-reload mode, you might do something like \`${commandHotExample}\`. Notice that you currently need to check both for the existence of the parameter, and also to correctly handle the array value.
`
)
.example({ force: true, hot: ["my-service"] })
)
public params: DeepPrimitiveMap

constructor(root: ConfigContext, commandInfo: CommandInfo) {
super(root)
this.name = commandInfo.name
this.params = { ...commandInfo.args, ...commandInfo.opts }
}
}

interface DefaultEnvironmentContextParams {
projectName: string
projectRoot: string
artifactsPath: string
branch: string
username?: string
commandInfo: CommandInfo
}

/**
* This context is available for template strings in the `defaultEnvironment` field in project configs.
*/
Expand All @@ -123,6 +170,9 @@ export class DefaultEnvironmentContext extends ConfigContext {
)
public local: LocalContext

@schema(CommandContext.getSchema().description("Information about the currently running command and its arguments."))
public command: CommandContext

@schema(ProjectContext.getSchema().description("Information about the Garden project."))
public project: ProjectContext

Expand All @@ -137,26 +187,17 @@ export class DefaultEnvironmentContext extends ConfigContext {
artifactsPath,
branch,
username,
}: {
projectName: string
projectRoot: string
artifactsPath: string
branch: string
username?: string
}) {
commandInfo,
}: DefaultEnvironmentContextParams) {
super()
this.local = new LocalContext(this, artifactsPath, projectRoot, username)
this.git = new GitContext(this, branch)
this.project = new ProjectContext(this, projectName)
this.command = new CommandContext(this, commandInfo)
}
}

export interface ProjectConfigContextParams {
projectName: string
projectRoot: string
artifactsPath: string
branch: string
username?: string
export interface ProjectConfigContextParams extends DefaultEnvironmentContextParams {
secrets: PrimitiveMap
}

Expand All @@ -178,9 +219,9 @@ export class ProjectConfigContext extends DefaultEnvironmentContext {
)
public secrets: PrimitiveMap

constructor({ projectName, projectRoot, artifactsPath, branch, username, secrets }: ProjectConfigContextParams) {
super({ projectName, projectRoot, artifactsPath, branch, username })
this.secrets = secrets
constructor(params: ProjectConfigContextParams) {
super(params)
this.secrets = params.secrets
}
}

Expand Down Expand Up @@ -212,16 +253,8 @@ export class EnvironmentConfigContext extends ProjectConfigContext {
)
public secrets: PrimitiveMap

constructor({
projectName,
projectRoot,
artifactsPath,
branch,
username,
variables,
secrets,
}: EnvironmentConfigContextParams) {
super({ projectName, projectRoot, artifactsPath, branch, username, secrets })
this.variables = this.var = variables
constructor(params: EnvironmentConfigContextParams) {
super(params)
this.variables = this.var = params.variables
}
}
1 change: 1 addition & 0 deletions core/src/config/template-contexts/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class WorkflowConfigContext extends EnvironmentConfigContext {
username: garden.username,
variables: garden.variables,
secrets: garden.secrets,
commandInfo: garden.commandInfo,
})

const fullEnvName = garden.namespace ? `${garden.namespace}.${garden.environmentName}` : garden.environmentName
Expand Down
1 change: 1 addition & 0 deletions core/src/docs/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function writeConfigReferenceDocs(docsRoot: string, plugins: Garden
{ name: "terraform" },
]
const garden = await Garden.factory(__dirname, {
commandInfo: { name: "generate-docs", args: {}, opts: {} },
config: {
path: __dirname,
apiVersion: "garden.io/v0",
Expand Down
Loading

0 comments on commit d31922d

Please sign in to comment.