Skip to content

Commit

Permalink
fix(template): support template strings in ConfigTemplate.configs (#…
Browse files Browse the repository at this point in the history
…5796)

* fix(template): support template strings in `ConfigTemplate.configs`

* test(template): fix some test assertions

* test(template): fix more test assertions

* test(template): add new test case

* fix(template): pass config source to string resolver
  • Loading branch information
vvagaytsev committed Mar 4, 2024
1 parent ada699a commit bf51aa0
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 11 deletions.
10 changes: 9 additions & 1 deletion core/src/config/render-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { Garden } from "../garden.js"
import { ConfigurationError, GardenError } from "../exceptions.js"
import { resolve, posix } from "path"
import fsExtra from "fs-extra"

const { ensureDir } = fsExtra
import type { TemplatedModuleConfig } from "../plugins/templated.js"
import { omit } from "lodash-es"
Expand Down Expand Up @@ -273,9 +274,16 @@ async function renderConfigs({
renderConfig: RenderTemplateConfig
}): Promise<TemplatableConfig[]> {
const templateDescription = `${configTemplateKind} '${template.name}'`
const templateConfigs = template.configs || []
const partiallyResolvedTemplateConfigs = resolveTemplateStrings({
value: templateConfigs,
context,
contextOpts: { allowPartial: true },
source: { yamlDoc: template.internal.yamlDoc, basePath: ["inputs"] },
})

return Promise.all(
(template.configs || []).map(async (m) => {
partiallyResolvedTemplateConfigs.map(async (m) => {
// Resolve just the name, which must be immediately resolvable
let resolvedName = m.name

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: garden.io/v1
kind: Project
name: config-templates-with-templating
environments:
- name: local
providers:
- name: local-kubernetes
environments: [local]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: RenderTemplate
template: template-runs
name: my-runs
inputs:
names: ["run-a", "run-b"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "object",
"properties": {
"names": {
"type": "array"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
kind: ConfigTemplate
name: template-runs
inputsSchemaPath: schema.json

configs:
- $concat:
$forEach: ${inputs.names}
$return:
kind: Run
type: exec
name: "${item.value}"
spec:
command: ["echo", "${item.value}"]
4 changes: 2 additions & 2 deletions core/test/unit/src/config/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ describe("resolveWorkflowConfig", () => {
}

expect(workflow).to.exist
expect(workflow.steps[0].script).to.equal('echo "${inputs.envName}"') // <- resolved later
expect(omit(workflow.internal, "yamlDoc")).to.eql(internal)
expect(workflow.steps[0].script).to.equal('echo "${environment.name}"') // <- resolved later
expect(omit(workflow.internal, "yamlDoc")).to.eql(internal) // <- `inputs.envName` should be resolved
})

describe("populateNamespaceForTriggers", () => {
Expand Down
66 changes: 58 additions & 8 deletions core/test/unit/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import { fileURLToPath } from "node:url"
import { resolveMsg } from "../../../src/logger/log-entry.js"
import { getCloudDistributionName } from "../../../src/util/cloud.js"
import { styles } from "../../../src/logger/styles.js"
import type { RunActionConfig } from "../../../src/actions/run.js"

const moduleDirName = dirname(fileURLToPath(import.meta.url))

Expand Down Expand Up @@ -2991,8 +2992,8 @@ describe("Garden", () => {
templateName: "combo",
inputs: {
name: "test",
envName: "${environment.name}", // <- resolved later
providerKey: "${providers.test-plugin.outputs.testKey}", // <- resolved later
envName: "${environment.name}", // <- should be resolved to itself
providerKey: "${providers.test-plugin.outputs.testKey}", // <- should be resolved to itself
},
}

Expand All @@ -3001,17 +3002,66 @@ describe("Garden", () => {
expect(test).to.exist

expect(build.type).to.equal("test")
expect(build.spec.command).to.include("${inputs.name}") // <- resolved later
expect(build.spec.command).to.include(internal.inputs.name) // <- should be resolved
expect(omit(build.internal, "yamlDoc")).to.eql(internal)

expect(deploy["build"]).to.equal("${parent.name}-${inputs.name}") // <- resolved later
expect(deploy["build"]).to.equal(`${internal.parentName}-${internal.inputs.name}`) // <- should be resolved
expect(omit(deploy.internal, "yamlDoc")).to.eql(internal)

expect(test.dependencies).to.eql(["build.${parent.name}-${inputs.name}"]) // <- resolved later
expect(test.spec.command).to.eql(["echo", "${inputs.envName}", "${inputs.providerKey}"]) // <- resolved later
expect(test.dependencies).to.eql([`build.${internal.parentName}-${internal.inputs.name}`]) // <- should be resolved
expect(test.spec.command).to.eql(["echo", internal.inputs.envName, internal.inputs.providerKey]) // <- should be resolved
expect(omit(test.internal, "yamlDoc")).to.eql(internal)
})

it("should resolve actions from templated config templates", async () => {
const garden = await makeTestGarden(getDataDir("test-projects", "config-templates-with-templating"))
await garden.scanAndAddConfigs()

const configs = await garden.getRawActionConfigs()
const runs = configs.Run
expect(runs).to.be.not.empty

const runNameA = "run-a"
const runA = runs[runNameA] as RunActionConfig
expect(runA).to.exist

const runNameB = "run-b"
const runB = runs[runNameB] as RunActionConfig
expect(runA).to.exist

const internal = {
basePath: garden.projectRoot,
configFilePath: join(garden.projectRoot, "runs.garden.yml"),
parentName: "my-runs",
templateName: "template-runs",
inputs: { names: [runNameA, runNameB] },
}

const expectedRunA: Partial<RunActionConfig> = {
kind: "Run",
type: "exec",
name: runNameA,
spec: {
command: ["echo", runNameA],
},
internal,
}
expect(omit(runA, "internal")).to.eql(omit(expectedRunA, "internal"))
expect(omit(runA.internal, "yamlDoc")).to.eql(expectedRunA.internal)

const expectedRunB: Partial<RunActionConfig> = {
kind: "Run",
type: "exec",
name: runNameB,
spec: {
command: ["echo", runNameB],
},
internal,
}
expect(omit(runB, "internal")).to.eql(omit(expectedRunB, "internal"))
expect(omit(runB.internal, "yamlDoc")).to.eql(expectedRunB.internal)
})

it("should resolve a workflow from a template", async () => {
const garden = await makeTestGarden(getDataDir("test-projects", "config-templates"))
await garden.scanAndAddConfigs()
Expand All @@ -3025,12 +3075,12 @@ describe("Garden", () => {
templateName: "workflows",
inputs: {
name: "test",
envName: "${environment.name}", // <- resolved later
envName: "${environment.name}", // <- should be resolved to itself
},
}

expect(workflow).to.exist
expect(workflow.steps).to.eql([{ script: 'echo "${inputs.envName}"' }]) // <- resolved later
expect(workflow.steps).to.eql([{ script: `echo "${internal.inputs.envName}"` }]) // <- should be resolved
expect(omit(workflow.internal, "yamlDoc")).to.eql(internal)
})

Expand Down

0 comments on commit bf51aa0

Please sign in to comment.