diff --git a/core/src/config/template-contexts/variables.ts b/core/src/config/template-contexts/variables.ts index 2d354d48dc..bf86c4e24b 100644 --- a/core/src/config/template-contexts/variables.ts +++ b/core/src/config/template-contexts/variables.ts @@ -30,6 +30,7 @@ import { ActionSpecContext } from "./actions.js" import type { CustomCommandContext } from "./custom-command.js" import type { CommandResource } from "../command.js" import type { GroupConfig } from "../group.js" +import { parseTemplateCollection } from "../../template/templated-collections.js" export class VariablesContext extends LayeredContext { /** @@ -124,12 +125,15 @@ export class VariablesContext extends LayeredContext { variableOverrides: DeepPrimitiveMap, context: ProjectConfigContext ) { - // TODO: parse template strings in varfiles? - const projectVarfileVars = await loadVarfile({ + const rawProjectVarfileVars = await loadVarfile({ configRoot: projectConfig.path, path: projectConfig.varfile, defaultPath: defaultProjectVarfilePath, }) + const projectVarfileVars = parseTemplateCollection({ + value: rawProjectVarfileVars, + source: { path: ["varfile"], yamlDoc: projectConfig.internal.yamlDoc }, + }) return new this(`project ${projectConfig.name}`, { context, @@ -145,11 +149,15 @@ export class VariablesContext extends LayeredContext { variableOverrides: DeepPrimitiveMap, context: ProjectConfigContext ) { - const envVarfileVars = await loadVarfile({ + const rawEnvVarfileVars = await loadVarfile({ configRoot: projectConfig.path, path: environmentConfig.varfile, defaultPath: defaultEnvVarfilePath(environment), }) + const envVarfileVars = parseTemplateCollection({ + value: rawEnvVarfileVars, + source: { path: ["varfile"], yamlDoc: projectConfig.internal.yamlDoc }, + }) return new this(`environment ${environmentConfig.name}`, { variablePrecedence: [environmentConfig.variables, envVarfileVars], @@ -197,17 +205,25 @@ export class VariablesContext extends LayeredContext { group?: GroupConfig ) { const effectiveConfigFileLocation = getEffectiveConfigFileLocation(config) - const actionVarfileVars = await loadVarfiles(garden, effectiveConfigFileLocation, config.varfiles || []) + const rawActionVarfileVars = await loadVarfiles(garden, effectiveConfigFileLocation, config.varfiles || []) + const actionVarfileVars = parseTemplateCollection({ + value: rawActionVarfileVars, + source: { path: ["varfiles"], yamlDoc: config.internal.yamlDoc }, + }) const actionVariables = [config.variables, ...actionVarfileVars] let groupVarfileVars: ParsedTemplate[] = [] let groupVariables: ParsedTemplate[] = [] if (group) { - groupVarfileVars = await loadVarfiles(garden, group.path, group.varfiles || []) + const rawGroupVarfileVars = await loadVarfiles(garden, group.path, group.varfiles || []) + groupVarfileVars = parseTemplateCollection({ + value: rawGroupVarfileVars, + source: { path: [] }, + }) groupVariables = [group.variables, ...groupVarfileVars] } - return new this(describeConfig(config) + (!group ? " (without group variables" : ""), { + return new this(describeConfig(config) + (!group ? " (without group variables)" : ""), { variablePrecedence: [...groupVariables, ...actionVariables], context, variableOverrides: garden.variableOverrides, diff --git a/core/test/data/test-projects/varfiles-with-templates/garden.yml b/core/test/data/test-projects/varfiles-with-templates/garden.yml new file mode 100644 index 0000000000..4f17c9f6bb --- /dev/null +++ b/core/test/data/test-projects/varfiles-with-templates/garden.yml @@ -0,0 +1,20 @@ +apiVersion: garden.io/v1 +kind: Project +name: varfiles-with-templates + +varfile: "vars-project.yml" + +environments: + - name: default + varfile: "vars-env.yml" + +--- + +kind: Run +type: exec +name: echo + +varfiles: [ "vars-action.yml" ] + +spec: + command: [ echo, 'PROJECT_VAR=${var.PROJECT_VAR}', 'ENV_VAR=${var.ENV_VAR}', 'ACTION_VAR=${var.ACTION_VAR}' ] diff --git a/core/test/data/test-projects/varfiles-with-templates/vars-action.yml b/core/test/data/test-projects/varfiles-with-templates/vars-action.yml new file mode 100644 index 0000000000..f26ed0ca77 --- /dev/null +++ b/core/test/data/test-projects/varfiles-with-templates/vars-action.yml @@ -0,0 +1 @@ +ACTION_VAR: "${project.name}" diff --git a/core/test/data/test-projects/varfiles-with-templates/vars-env.yml b/core/test/data/test-projects/varfiles-with-templates/vars-env.yml new file mode 100644 index 0000000000..30a7d62c81 --- /dev/null +++ b/core/test/data/test-projects/varfiles-with-templates/vars-env.yml @@ -0,0 +1 @@ +ENV_VAR: "${project.name}" diff --git a/core/test/data/test-projects/varfiles-with-templates/vars-project.yml b/core/test/data/test-projects/varfiles-with-templates/vars-project.yml new file mode 100644 index 0000000000..6c1f40e045 --- /dev/null +++ b/core/test/data/test-projects/varfiles-with-templates/vars-project.yml @@ -0,0 +1 @@ +PROJECT_VAR: "${project.name}" diff --git a/core/test/unit/src/actions/action-configs-to-graph.ts b/core/test/unit/src/actions/action-configs-to-graph.ts index 082f7d3027..85fcf6b1f8 100644 --- a/core/test/unit/src/actions/action-configs-to-graph.ts +++ b/core/test/unit/src/actions/action-configs-to-graph.ts @@ -542,15 +542,15 @@ describe("actionConfigsToGraph", () => { }) const action = graph.getBuild("foo") - const vars = action["variables"] - const resolved = deepResolveContext("action variables", vars, garden.getProjectConfigContext()) + const varContext = action.getVariablesContext() + const resolved = deepResolveContext("action variables", varContext, garden.getProjectConfigContext()) expect(resolved).to.eql({ projectName: garden.projectName, }) }) - it("loads varfiles for the action", async () => { + it("loads varfiles for the action and resolve template strings in varfile", async () => { const varfilePath = join(tmpDir.path, "varfile.yml") await dumpYaml(varfilePath, { projectName: "${project.name}", @@ -579,17 +579,19 @@ describe("actionConfigsToGraph", () => { }) const action = graph.getBuild("foo") - const vars = action["variables"] + const varContext = action.getVariablesContext() - expect(vars.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() })).to.eql({ + expect( + varContext.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() }) + ).to.eql({ found: true, resolved: { - projectName: "${project.name}", + projectName: "test", }, }) }) - it("loads optional varfiles for the action", async () => { + it("loads optional varfiles for the action and resolve template strings in varfile", async () => { const varfilePath = join(tmpDir.path, "varfile.yml") await dumpYaml(varfilePath, { projectName: "${project.name}", @@ -618,11 +620,13 @@ describe("actionConfigsToGraph", () => { }) const action = graph.getBuild("foo") - const vars = action["variables"] + const varContext = action.getVariablesContext() - expect(vars.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() })).to.eql({ + expect( + varContext.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() }) + ).to.eql({ found: true, - resolved: { projectName: "${project.name}" }, + resolved: { projectName: "test" }, }) }) @@ -660,9 +664,11 @@ describe("actionConfigsToGraph", () => { }) const action = graph.getBuild("foo") - const vars = action["variables"] + const varContext = action.getVariablesContext() - expect(vars.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() })).to.eql({ + expect( + varContext.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() }) + ).to.eql({ found: true, resolved: { foo: "FOO", bar: "BAR", baz: "baz" }, }) @@ -718,9 +724,11 @@ describe("actionConfigsToGraph", () => { }) const action = graph.getBuild("foo") - const vars = action["variables"] + const varContext = action.getVariablesContext() - expect(vars.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() })).to.eql({ + expect( + varContext.resolve({ nodePath: [], key: [], opts: {}, rootContext: garden.getProjectConfigContext() }) + ).to.eql({ found: true, resolved: { foo: "NEW_FOO", diff --git a/core/test/unit/src/config/template-contexts/variables.ts b/core/test/unit/src/config/template-contexts/variables.ts index e61447d4e3..d3e545bd7c 100644 --- a/core/test/unit/src/config/template-contexts/variables.ts +++ b/core/test/unit/src/config/template-contexts/variables.ts @@ -14,13 +14,40 @@ import { TestContext } from "./base.js" import { deepResolveContext } from "../../../../../src/config/template-contexts/base.js" import { resolveAction } from "../../../../../src/graph/actions.js" -/* - * Copyright (C) 2018-2024 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/. - */ +describe("varfiles", () => { + let garden: TestGarden + + beforeEach(async () => { + garden = await makeTestGarden(getDataDir("test-projects", "varfiles-with-templates")) + }) + + afterEach(() => { + garden.close() + }) + + it("should parse template strings in varfiles", async () => { + const graph = await garden.getConfigGraph({ log: garden.log, emit: false }) + const runAction = graph.getRun("echo") + + const varContext = runAction.getVariablesContext() + const output = varContext.resolve({ + nodePath: [], + key: [], + opts: {}, + rootContext: garden.getProjectConfigContext(), + }) + + expect(output).to.eql({ + found: true, + resolved: { + ACTION_VAR: "varfiles-with-templates", + ENV_VAR: "varfiles-with-templates", + PROJECT_VAR: "varfiles-with-templates", + }, + }) + }) +}) + describe("VariablesContext", () => { let garden: TestGarden