feat: specify env values from the parent to the nested state (#622)

* feat: specify env values from the parent to the nested state

Adds the `helmfiles[].environment.values` that accepts a mix of file pathes and inline dictes:

```yaml
helmfiles:
- path: path/to/nested/helmfile.yaml
  environment:
    values:
    - key1: val1
    - values.yaml
```

The values files are loaded in the context of the parent state file. For example, in case the above state file is located at `/path/to/helmfile.yaml`,
`values.yaml` is located at `/path/to/values.yaml` instead of `/path/to/nested/values.yaml`.

Resolves #523

* fix: multiple "bases" declarations yields duplicate releases

Fixes #615

* fix regression in double-rendering with env value overrides

The latest commit broke any state files like the below to NOT pass env value overrides at all:

```
helmfiles:
- path: nested/state.yaml
  environment:
    values:
    - overrides.yaml
```

This fixes the issue.
This commit is contained in:
KUOKA Yusuke
2019-05-29 19:08:51 +09:00
committed by GitHub
parent 681c866ce1
commit 1226ea6d1a
12 changed files with 610 additions and 223 deletions
+29 -11
View File
@@ -83,7 +83,7 @@ func (a *App) within(dir string, do func() error) error {
return appErr
}
func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error {
func (a *App) visitStateFiles(fileOrDir string, do func(string, string) error) error {
desiredStateFiles, err := a.findDesiredStateFiles(fileOrDir)
if err != nil {
return appError("", err)
@@ -103,7 +103,12 @@ func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error {
a.Logger.Debugf("processing file \"%s\" in directory \"%s\"", file, dir)
err := a.within(dir, func() error {
return do(file)
absd, err := a.abs(dir)
if err != nil {
return err
}
return do(file, absd)
})
if err != nil {
return appError(fmt.Sprintf("in %s/%s", dir, file), err)
@@ -113,7 +118,7 @@ func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error {
return nil
}
func (a *App) loadDesiredStateFromYaml(file string) (*state.HelmState, error) {
func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.HelmState, error) {
ld := &desiredStateLoader{
readFile: a.readFile,
fileExists: a.fileExists,
@@ -126,14 +131,20 @@ func (a *App) loadDesiredStateFromYaml(file string) (*state.HelmState, error) {
KubeContext: a.KubeContext,
glob: a.glob,
}
return ld.Load(file)
var op LoadOpts
if len(opts) > 0 {
op = opts[0]
}
return ld.Load(file, op)
}
func (a *App) VisitDesiredStates(fileOrDir string, selector []string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
func (a *App) VisitDesiredStates(fileOrDir string, opts LoadOpts, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
noMatchInHelmfiles := true
err := a.visitStateFiles(fileOrDir, func(f string) error {
st, err := a.loadDesiredStateFromYaml(f)
err := a.visitStateFiles(fileOrDir, func(f, d string) error {
st, err := a.loadDesiredStateFromYaml(f, opts)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@@ -169,16 +180,22 @@ func (a *App) VisitDesiredStates(fileOrDir string, selector []string, converge f
return ctx.wrapErrs(err)
}
}
st.Selectors = selector
st.Selectors = opts.Selectors
if len(st.Helmfiles) > 0 {
noMatchInSubHelmfiles := true
for i, m := range st.Helmfiles {
optsForNestedState := LoadOpts{
CalleePath: filepath.Join(d, f),
Environment: m.Environment,
}
//assign parent selector to sub helm selector in legacy mode or do not inherit in experimental mode
if (m.Selectors == nil && !isExplicitSelectorInheritanceEnabled()) || m.SelectorsInherited {
m.Selectors = selector
optsForNestedState.Selectors = opts.Selectors
} else {
optsForNestedState.Selectors = m.Selectors
}
if err := a.VisitDesiredStates(m.Path, m.Selectors, converge); err != nil {
if err := a.VisitDesiredStates(m.Path, optsForNestedState, converge); err != nil {
switch err.(type) {
case *NoMatchingHelmfileError:
@@ -213,8 +230,9 @@ func (a *App) VisitDesiredStates(fileOrDir string, selector []string, converge f
}
func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error) error {
opts := LoadOpts{Selectors: a.Selectors}
err := a.VisitDesiredStates(fileOrDir, a.Selectors, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
err := a.VisitDesiredStates(fileOrDir, opts, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
if len(st.Selectors) > 0 {
err := st.FilterReleases()
if err != nil {
+193 -32
View File
@@ -156,7 +156,7 @@ releases:
t.Fatal("expected error did not occur")
}
expected := "in ./helmfile.yaml: failed to read helmfile.yaml: environment values file matching \"env.*.yaml\" does not exist"
expected := "in ./helmfile.yaml: failed to read helmfile.yaml: environment values file matching \"env.*.yaml\" does not exist in \".\""
if err.Error() != expected {
t.Errorf("unexpected error: expected=%s, got=%v", expected, err)
}
@@ -659,6 +659,122 @@ func runFilterSubHelmFilesTests(testcases []struct {
}
func TestVisitDesiredStatesWithReleasesFiltered_EmbeddedNestedStateAdditionalEnvValues(t *testing.T) {
files := map[string]string{
"/path/to/helmfile.yaml": `
helmfiles:
- path: helmfile.d/a*.yaml
environment:
values:
- env.values.yaml
- helmfile.d/b*.yaml
- path: helmfile.d/c*.yaml
environment:
values:
- env.values.yaml
- tillerNs: INLINE_TILLER_NS_3
`,
"/path/to/helmfile.d/a1.yaml": `
environments:
default:
values:
- tillerNs: INLINE_TILLER_NS
ns: INLINE_NS
releases:
- name: foo
chart: stable/zipkin
tillerNamespace: {{ .Environment.Values.tillerNs }}
namespace: {{ .Environment.Values.ns }}
`,
"/path/to/helmfile.d/b.yaml": `
environments:
default:
values:
- tillerNs: INLINE_TILLER_NS
ns: INLINE_NS
releases:
- name: bar
chart: stable/grafana
tillerNamespace: {{ .Environment.Values.tillerNs }}
namespace: {{ .Environment.Values.ns }}
`,
"/path/to/helmfile.d/c.yaml": `
environments:
default:
values:
- tillerNs: INLINE_TILLER_NS
ns: INLINE_NS
releases:
- name: baz
chart: stable/envoy
tillerNamespace: {{ .Environment.Values.tillerNs }}
namespace: {{ .Environment.Values.ns }}
`,
"/path/to/env.values.yaml": `
tillerNs: INLINE_TILLER_NS_2
`,
}
app := appWithFs(&App{
KubeContext: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"),
Namespace: "",
Selectors: []string{},
Env: "default",
}, files)
processed := []state.ReleaseSpec{}
collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
for _, r := range st.Releases {
processed = append(processed, r)
}
return []error{}
}
err := app.VisitDesiredStatesWithReleasesFiltered(
"helmfile.yaml", collectReleases,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
type release struct {
chart string
tillerNs string
ns string
}
expectedReleases := map[string]release{
"foo": {"stable/zipkin", "INLINE_TILLER_NS_2", "INLINE_NS"},
"bar": {"stable/grafana", "INLINE_TILLER_NS", "INLINE_NS"},
"baz": {"stable/envoy", "INLINE_TILLER_NS_3", "INLINE_NS"},
}
for name := range processed {
actual := processed[name]
t.Run(actual.Name, func(t *testing.T) {
expected, ok := expectedReleases[actual.Name]
if !ok {
t.Fatalf("unexpected release processed: %v", actual)
}
if expected.chart != actual.Chart {
t.Errorf("unexpected chart: expected=%s, got=%s", expected.chart, actual.Chart)
}
if expected.tillerNs != actual.TillerNamespace {
t.Errorf("unexpected tiller namespace: expected=%s, got=%s", expected.tillerNs, actual.TillerNamespace)
}
if expected.ns != actual.Namespace {
t.Errorf("unexpected namespace: expected=%s, got=%s", expected.ns, actual.Namespace)
}
})
}
}
// See https://github.com/roboll/helmfile/issues/312
func TestVisitDesiredStatesWithReleasesFiltered_ReverseOrder(t *testing.T) {
files := map[string]string{
@@ -897,25 +1013,23 @@ helmDefaults:
if st.HelmDefaults.TillerNamespace != "TILLER_NS" {
t.Errorf("unexpected helmDefaults.tillerNamespace: expected=TILLER_NS, got=%s", st.HelmDefaults.TillerNamespace)
}
if st.Releases[0].Name != "myrelease0" {
t.Errorf("unexpected releases[0].name: expected=myrelease0, got=%s", st.Releases[0].Name)
firstRelease := st.Releases[0]
if firstRelease.Name != "myrelease1" {
t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", firstRelease.Name)
}
if st.Releases[1].Name != "myrelease1" {
t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", st.Releases[1].Name)
secondRelease := st.Releases[1]
if secondRelease.Name != "myrelease1" {
t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", secondRelease.Name)
}
if st.Releases[2].Name != "myrelease1" {
t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", st.Releases[2].Name)
if secondRelease.Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", firstRelease.Values[0])
}
if st.Releases[2].Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", st.Releases[1].Values[0])
}
if *st.Releases[2].MissingFileHandler != "Warn" {
t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *st.Releases[1].MissingFileHandler)
if *secondRelease.MissingFileHandler != "Warn" {
t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *firstRelease.MissingFileHandler)
}
if st.Releases[2].Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", st.Releases[1].Values[0])
if secondRelease.Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", firstRelease.Values[0])
}
if st.HelmDefaults.KubeContext != "FOO" {
@@ -1114,24 +1228,23 @@ helmDefaults:
t.Errorf("unexpected helmDefaults.tillerNamespace: expected=TILLER_NS, got=%s", st.HelmDefaults.TillerNamespace)
}
if st.Releases[0].Name != "myrelease0" {
t.Errorf("unexpected releases[0].name: expected=myrelease0, got=%s", st.Releases[0].Name)
firstRelease := st.Releases[0]
if firstRelease.Name != "myrelease1" {
t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", firstRelease.Name)
}
if st.Releases[1].Name != "myrelease1" {
t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", st.Releases[1].Name)
secondRelease := st.Releases[1]
if secondRelease.Name != "myrelease1" {
t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", secondRelease.Name)
}
if st.Releases[2].Name != "myrelease1" {
t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", st.Releases[2].Name)
if secondRelease.Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", firstRelease.Values[0])
}
if st.Releases[2].Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", st.Releases[1].Values[0])
}
if *st.Releases[2].MissingFileHandler != "Warn" {
t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *st.Releases[1].MissingFileHandler)
if *secondRelease.MissingFileHandler != "Warn" {
t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *firstRelease.MissingFileHandler)
}
if st.Releases[2].Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", st.Releases[1].Values[0])
if secondRelease.Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
t.Errorf("unexpected releases[2].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", firstRelease.Values[0])
}
if st.HelmDefaults.KubeContext != "FOO" {
@@ -1188,10 +1301,58 @@ releases:
if st.Releases[1].Name != "myrelease2" {
t.Errorf("unexpected releases[0].name: expected=myrelease2, got=%s", st.Releases[1].Name)
}
if st.Releases[2].Name != "myrelease1" {
t.Errorf("unexpected releases[0].name: expected=myrelease1, got=%s", st.Releases[2].Name)
if len(st.Releases) != 2 {
t.Errorf("unexpected number of releases: expected=2, got=%d", len(st.Releases))
}
if st.Releases[3].Name != "myrelease0" {
t.Errorf("unexpected releases[0].name: expected=myrelease0, got=%s", st.Releases[3].Name)
}
// See https://github.com/roboll/helmfile/issues/615
func TestLoadDesiredStateFromYaml_MultiPartTemplate_NoMergeArrayInEnvVal(t *testing.T) {
statePath := "/path/to/helmfile.yaml"
stateContent := `
environments:
default:
values:
- foo: ["foo"]
---
environments:
default:
values:
- foo: ["FOO"]
- 1.yaml
---
environments:
default:
values:
- 2.yaml
---
releases:
- name: {{ .Environment.Values.foo | quote }}
chart: {{ .Environment.Values.bar | quote }}
`
testFs := state.NewTestFs(map[string]string{
statePath: stateContent,
"/path/to/1.yaml": `bar: ["bar"]`,
"/path/to/2.yaml": `bar: ["BAR"]`,
})
app := &App{
readFile: testFs.ReadFile,
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"),
Reverse: true,
}
st, err := app.loadDesiredStateFromYaml(statePath)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if st.Releases[0].Name != "[FOO]" {
t.Errorf("unexpected releases[0].name: expected=FOO, got=%s", st.Releases[0].Name)
}
if st.Releases[0].Chart != "[BAR]" {
t.Errorf("unexpected releases[0].chart: expected=BAR, got=%s", st.Releases[0].Chart)
}
}
+50 -22
View File
@@ -27,8 +27,36 @@ type desiredStateLoader struct {
logger *zap.SugaredLogger
}
func (ld *desiredStateLoader) Load(f string) (*state.HelmState, error) {
st, err := ld.loadFile(nil, filepath.Dir(f), filepath.Base(f), true)
type LoadOpts struct {
Selectors []string
Environment state.SubhelmfileEnvironmentSpec
CalleePath string
}
func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, error) {
var overrodeEnv *environment.Environment
args := opts.Environment.OverrideValues
if len(args) > 0 {
if opts.CalleePath == "" {
return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts)
}
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob)
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile)
handler := state.MissingFileHandlerError
vals, err := envld.LoadEnvironmentValues(&handler, args)
if err != nil {
return nil, err
}
overrodeEnv = &environment.Environment{
Name: ld.env,
Values: vals,
}
}
st, err := ld.loadFileWithOverrides(nil, overrodeEnv, filepath.Dir(f), filepath.Base(f), true)
if err != nil {
return nil, err
}
@@ -59,6 +87,9 @@ func (ld *desiredStateLoader) Load(f string) (*state.HelmState, error) {
}
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) {
f = file
@@ -78,6 +109,7 @@ func (ld *desiredStateLoader) loadFile(inheritedEnv *environment.Environment, ba
if !experimentalModeEnabled() || ext == ".gotmpl" {
self, err = ld.renderAndLoad(
inheritedEnv,
overrodeEnv,
baseDir,
f,
fileBytes,
@@ -90,6 +122,7 @@ func (ld *desiredStateLoader) loadFile(inheritedEnv *environment.Environment, ba
file,
evaluateBases,
inheritedEnv,
overrodeEnv,
)
}
@@ -102,33 +135,27 @@ func (a *desiredStateLoader) underlying() *state.StateCreator {
return c
}
func (a *desiredStateLoader) load(yaml []byte, baseDir, file string, evaluateBases bool, env *environment.Environment) (*state.HelmState, error) {
st, err := a.underlying().ParseAndLoad(yaml, baseDir, file, a.env, evaluateBases, env)
func (a *desiredStateLoader) load(yaml []byte, baseDir, file string, evaluateBases bool, env, overrodeEnv *environment.Environment) (*state.HelmState, error) {
merged, err := env.Merge(overrodeEnv)
if err != nil {
return nil, err
}
helmfiles := []state.SubHelmfileSpec{}
for _, hf := range st.Helmfiles {
matches, err := st.ExpandPaths(hf.Path)
if err != nil {
return nil, err
}
if len(matches) == 0 {
return nil, fmt.Errorf("no file matching %s found", hf.Path)
}
for _, match := range matches {
newHelmfile := hf
newHelmfile.Path = match
helmfiles = append(helmfiles, newHelmfile)
}
st, err := a.underlying().ParseAndLoad(yaml, baseDir, file, a.env, evaluateBases, merged)
if err != nil {
return nil, err
}
helmfiles, err := st.ExpandedHelmfiles()
if err != nil {
return nil, err
}
st.Helmfiles = helmfiles
return st, nil
}
func (ld *desiredStateLoader) renderAndLoad(env *environment.Environment, baseDir, filename string, content []byte, evaluateBases bool) (*state.HelmState, error) {
func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Environment, baseDir, filename string, content []byte, evaluateBases bool) (*state.HelmState, error) {
parts := bytes.Split(content, []byte("\n---\n"))
var finalState *state.HelmState
@@ -139,13 +166,13 @@ func (ld *desiredStateLoader) renderAndLoad(env *environment.Environment, baseDi
id := fmt.Sprintf("%s.part.%d", filename, i)
if env == nil {
if env == nil && overrodeEnv == nil {
yamlBuf, err = ld.renderTemplatesToYaml(baseDir, id, part)
if err != nil {
return nil, fmt.Errorf("error during %s parsing: %v", id, err)
}
} else {
yamlBuf, err = ld.renderTemplatesToYaml(baseDir, id, part, *env)
yamlBuf, err = ld.renderTemplatesToYamlWithEnv(baseDir, id, part, env, overrodeEnv)
if err != nil {
return nil, fmt.Errorf("error during %s parsing: %v", id, err)
}
@@ -157,6 +184,7 @@ func (ld *desiredStateLoader) renderAndLoad(env *environment.Environment, baseDi
filename,
evaluateBases,
env,
overrodeEnv,
)
if err != nil {
return nil, err
@@ -165,7 +193,7 @@ func (ld *desiredStateLoader) renderAndLoad(env *environment.Environment, baseDi
if finalState == nil {
finalState = currentState
} else {
if err := mergo.Merge(finalState, currentState, mergo.WithAppendSlice); err != nil {
if err := mergo.Merge(finalState, currentState, mergo.WithOverride); err != nil {
return nil, err
}
}
+38 -19
View File
@@ -18,8 +18,8 @@ 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) renderEnvironment(firstPassEnv *environment.Environment, baseDir, filename string, content []byte) *environment.Environment {
tmplData := state.EnvironmentTemplateData{Environment: *firstPassEnv, Namespace: r.namespace}
firstPassRenderer := tmpl.NewFirstPassRenderer(baseDir, tmplData)
// parse as much as we can, tolerate errors, this is a preparse
@@ -34,7 +34,7 @@ func (r *desiredStateLoader) renderEnvironment(firstPassEnv environment.Environm
c := r.underlying()
c.Strict = false
// create preliminary state, as we may have an environment. Tolerate errors.
prestate, err := c.ParseAndLoad(yamlBuf.Bytes(), baseDir, filename, r.env, false, &firstPassEnv)
prestate, err := c.ParseAndLoad(yamlBuf.Bytes(), baseDir, filename, r.env, false, firstPassEnv)
if err != nil && r.logger != nil {
switch err.(type) {
case *state.StateLoadError:
@@ -44,36 +44,55 @@ func (r *desiredStateLoader) renderEnvironment(firstPassEnv environment.Environm
}
if prestate != nil {
firstPassEnv = prestate.Env
firstPassEnv = &prestate.Env
}
return firstPassEnv
}
func (r *desiredStateLoader) renderTemplatesToYaml(baseDir, filename string, content []byte, context ...environment.Environment) (*bytes.Buffer, error) {
var env environment.Environment
if len(context) > 0 {
env = context[0]
} else {
env = environment.Environment{Name: r.env, Values: map[string]interface{}(nil)}
}
return r.twoPassRenderTemplateToYaml(env, baseDir, filename, content)
type RenderOpts struct {
}
func (r *desiredStateLoader) twoPassRenderTemplateToYaml(initEnv environment.Environment, baseDir, filename string, content []byte) (*bytes.Buffer, error) {
func (r *desiredStateLoader) renderTemplatesToYaml(baseDir, filename string, content []byte) (*bytes.Buffer, error) {
env := &environment.Environment{Name: r.env, Values: map[string]interface{}(nil)}
return r.renderTemplatesToYamlWithEnv(baseDir, filename, content, env, nil)
}
func (r *desiredStateLoader) renderTemplatesToYamlWithEnv(baseDir, filename string, content []byte, inherited, overrode *environment.Environment) (*bytes.Buffer, error) {
return r.twoPassRenderTemplateToYaml(inherited, overrode, baseDir, filename, content)
}
func (r *desiredStateLoader) twoPassRenderTemplateToYaml(inherited, overrode *environment.Environment, baseDir, filename string, content []byte) (*bytes.Buffer, error) {
// try a first pass render. This will always succeed, but can produce a limited env
if r.logger != nil {
r.logger.Debugf("first-pass rendering input of \"%s\": %v", filename, initEnv)
r.logger.Debugf("first-pass rendering starting for \"%s\": inherited=%v, overrode=%v", filename, inherited, overrode)
}
firstPassEnv := r.renderEnvironment(initEnv, baseDir, filename, content)
initEnv, err := inherited.Merge(overrode)
if err != nil {
return nil, err
}
if r.logger != nil {
r.logger.Debugf("first-pass rendering result of \"%s\": %v", filename, firstPassEnv)
r.logger.Debugf("first-pass uses: %v", initEnv)
}
tmplData := state.EnvironmentTemplateData{Environment: firstPassEnv, Namespace: r.namespace}
renderedEnv := r.renderEnvironment(initEnv, baseDir, filename, content)
if r.logger != nil {
r.logger.Debugf("first-pass produced: %v", initEnv)
}
finalEnv, err := renderedEnv.Merge(overrode)
if err != nil {
return nil, err
}
if r.logger != nil {
r.logger.Debugf("first-pass rendering result of \"%s\": %v", filename, *finalEnv)
}
tmplData := state.EnvironmentTemplateData{Environment: *finalEnv, Namespace: r.namespace}
secondPassRenderer := tmpl.NewFileRenderer(r.readFile, baseDir, tmplData)
yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content)
if err != nil {