mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-19 19:14:30 +02:00
feat: state values (#647)
This adds `values` to state files as proposed in #640. ```yaml values: - key1: val1 - defaults.yaml environments: default: - values: - environments/default.yaml production: - values: - environments/production.yaml ``` `{{ .Valuese.key1 }}` evaluates to `val1` if and only if it is not overrode via the production or the default env, or command-line args. Resolves #640
This commit is contained in:
@@ -900,6 +900,130 @@ bar: "bar1"
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisitDesiredStatesWithReleasesFiltered_StateValueOverrides(t *testing.T) {
|
||||
envTmplExpr := "{{ .Values.foo }}-{{ .Values.bar }}-{{ .Values.baz }}-{{ .Values.hoge }}-{{ .Values.fuga }}-{{ .Values.a | first | pluck \"b\" | first | first | pluck \"c\" | first }}"
|
||||
relTmplExpr := "\"{{`{{ .Values.foo }}-{{ .Values.bar }}-{{ .Values.baz }}-{{ .Values.hoge }}-{{ .Values.fuga }}-{{ .Values.a | first | pluck \\\"b\\\" | first | first | pluck \\\"c\\\" | first }}`}}\""
|
||||
|
||||
testcases := []struct {
|
||||
expr, env, expected string
|
||||
}{
|
||||
{
|
||||
expr: envTmplExpr,
|
||||
env: "default",
|
||||
expected: "foo-bar_default-baz_override-hoge_set-fuga_set-C",
|
||||
},
|
||||
{
|
||||
expr: envTmplExpr,
|
||||
env: "production",
|
||||
expected: "foo-bar_production-baz_override-hoge_set-fuga_set-C",
|
||||
},
|
||||
{
|
||||
expr: relTmplExpr,
|
||||
env: "default",
|
||||
expected: "foo-bar_default-baz_override-hoge_set-fuga_set-C",
|
||||
},
|
||||
{
|
||||
expr: relTmplExpr,
|
||||
env: "production",
|
||||
expected: "foo-bar_production-baz_override-hoge_set-fuga_set-C",
|
||||
},
|
||||
}
|
||||
for i := range testcases {
|
||||
testcase := testcases[i]
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": fmt.Sprintf(`
|
||||
# The top-level "values" are "base" values has inherited to state values with the lowest priority.
|
||||
# The lowest priority results in environment-specific values to override values defined in the base.
|
||||
values:
|
||||
- values.yaml
|
||||
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- default.yaml
|
||||
production:
|
||||
values:
|
||||
- production.yaml
|
||||
---
|
||||
releases:
|
||||
- name: %s
|
||||
chart: %s
|
||||
namespace: %s
|
||||
`, testcase.expr, testcase.expr, testcase.expr),
|
||||
"/path/to/values.yaml": `
|
||||
foo: foo
|
||||
bar: bar
|
||||
baz: baz
|
||||
hoge: hoge
|
||||
fuga: fuga
|
||||
|
||||
a: []
|
||||
`,
|
||||
"/path/to/default.yaml": `
|
||||
bar: "bar_default"
|
||||
baz: "baz_default"
|
||||
|
||||
a:
|
||||
- b: []
|
||||
`,
|
||||
"/path/to/production.yaml": `
|
||||
bar: "bar_production"
|
||||
baz: "baz_production"
|
||||
|
||||
a:
|
||||
- b: []
|
||||
`,
|
||||
"/path/to/overrides.yaml": `
|
||||
baz: baz_override
|
||||
hoge: hoge_override
|
||||
|
||||
a:
|
||||
- b:
|
||||
- c: C
|
||||
`,
|
||||
}
|
||||
|
||||
actual := []state.ReleaseSpec{}
|
||||
|
||||
collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||
for _, r := range st.Releases {
|
||||
actual = append(actual, r)
|
||||
}
|
||||
return []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
KubeContext: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
Reverse: false,
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: testcase.env,
|
||||
ValuesFiles: []string{"overrides.yaml"},
|
||||
Set: map[string]interface{}{"hoge": "hoge_set", "fuga": "fuga_set"},
|
||||
}, files)
|
||||
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||
"helmfile.yaml", collectReleases,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(actual) != 1 {
|
||||
t.Errorf("unexpected number of processed releases: expected=1, got=%d", len(actual))
|
||||
}
|
||||
if actual[0].Name != testcase.expected {
|
||||
t.Errorf("unexpected name: expected=%s, got=%s", testcase.expected, actual[0].Name)
|
||||
}
|
||||
if actual[0].Chart != testcase.expected {
|
||||
t.Errorf("unexpected chart: expected=%s, got=%s", testcase.expected, actual[0].Chart)
|
||||
}
|
||||
if actual[0].Namespace != testcase.expected {
|
||||
t.Errorf("unexpected namespace: expected=%s, got=%s", testcase.expected, actual[0].Namespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
|
||||
yamlFile := "example/path/to/yaml/file"
|
||||
yamlContent := []byte(`releases:
|
||||
|
||||
@@ -83,6 +83,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
func (ld *desiredStateLoader) loadFile(inheritedEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*state.HelmState, error) {
|
||||
return ld.loadFileWithOverrides(inheritedEnv, nil, baseDir, file, evaluateBases)
|
||||
}
|
||||
|
||||
func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*state.HelmState, error) {
|
||||
var f string
|
||||
if filepath.IsAbs(file) {
|
||||
|
||||
@@ -19,8 +19,12 @@ func prependLineNumbers(text string) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *desiredStateLoader) renderEnvironment(firstPassEnv *environment.Environment, baseDir, filename string, content []byte) *environment.Environment {
|
||||
tmplData := state.EnvironmentTemplateData{Environment: *firstPassEnv, Namespace: r.namespace}
|
||||
func (r *desiredStateLoader) renderPrestate(firstPassEnv *environment.Environment, baseDir, filename string, content []byte) (*environment.Environment, *state.HelmState) {
|
||||
tmplData := state.EnvironmentTemplateData{
|
||||
Environment: *firstPassEnv,
|
||||
Namespace: r.namespace,
|
||||
Values: map[string]interface{}{},
|
||||
}
|
||||
firstPassRenderer := tmpl.NewFirstPassRenderer(baseDir, tmplData)
|
||||
|
||||
// parse as much as we can, tolerate errors, this is a preparse
|
||||
@@ -29,7 +33,7 @@ func (r *desiredStateLoader) renderEnvironment(firstPassEnv *environment.Environ
|
||||
r.logger.Debugf("first-pass rendering input of \"%s\":\n%s", filename, prependLineNumbers(string(content)))
|
||||
if yamlBuf == nil { // we have a template syntax error, let the second parse report
|
||||
r.logger.Debugf("template syntax error: %v", err)
|
||||
return firstPassEnv
|
||||
return firstPassEnv, nil
|
||||
}
|
||||
}
|
||||
yamlData := yamlBuf.String()
|
||||
@@ -57,7 +61,8 @@ func (r *desiredStateLoader) renderEnvironment(firstPassEnv *environment.Environ
|
||||
if prestate != nil {
|
||||
firstPassEnv = &prestate.Env
|
||||
}
|
||||
return firstPassEnv
|
||||
|
||||
return firstPassEnv, prestate
|
||||
}
|
||||
|
||||
type RenderOpts struct {
|
||||
@@ -88,13 +93,18 @@ func (r *desiredStateLoader) twoPassRenderTemplateToYaml(inherited, overrode *en
|
||||
r.logger.Debugf("first-pass uses: %v", initEnv)
|
||||
}
|
||||
|
||||
renderedEnv := r.renderEnvironment(initEnv, baseDir, filename, content)
|
||||
renderedEnv, prestate := r.renderPrestate(initEnv, baseDir, filename, content)
|
||||
|
||||
if r.logger != nil {
|
||||
r.logger.Debugf("first-pass produced: %v", renderedEnv)
|
||||
}
|
||||
|
||||
finalEnv, err := renderedEnv.Merge(overrode)
|
||||
finalEnv, err := inherited.Merge(renderedEnv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalEnv, err = finalEnv.Merge(overrode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,7 +113,23 @@ func (r *desiredStateLoader) twoPassRenderTemplateToYaml(inherited, overrode *en
|
||||
r.logger.Debugf("first-pass rendering result of \"%s\": %v", filename, *finalEnv)
|
||||
}
|
||||
|
||||
tmplData := state.EnvironmentTemplateData{Environment: *finalEnv, Namespace: r.namespace}
|
||||
vals := map[string]interface{}{}
|
||||
if prestate != nil {
|
||||
prestate.Env = *finalEnv
|
||||
vals, err = prestate.Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if prestate != nil {
|
||||
r.logger.Debugf("vals:\n%v\ndefaultVals:%v", vals, prestate.DefaultValues)
|
||||
}
|
||||
|
||||
tmplData := state.EnvironmentTemplateData{
|
||||
Environment: *finalEnv,
|
||||
Namespace: r.namespace,
|
||||
Values: vals,
|
||||
}
|
||||
secondPassRenderer := tmpl.NewFileRenderer(r.readFile, baseDir, tmplData)
|
||||
yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user