diff --git a/pkg/state/state_exec_tmpl.go b/pkg/state/state_exec_tmpl.go index 2a8e3877..346aee3d 100644 --- a/pkg/state/state_exec_tmpl.go +++ b/pkg/state/state_exec_tmpl.go @@ -3,6 +3,7 @@ package state import ( "errors" "fmt" + "path/filepath" "reflect" "strings" @@ -110,27 +111,33 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) { release.KubeVersion = st.KubeVersion } - successFlag := false - for it, prev := 0, release; it < 6; it++ { - tmplData := st.createReleaseTemplateData(prev, vals) - renderer := tmpl.NewFileRenderer(st.fs, st.basePath, tmplData) - r, err := release.ExecuteTemplateExpressions(renderer) - if err != nil { - return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err) - } - if reflect.DeepEqual(prev, r) { - successFlag = true - if err := updateBoolTemplatedValues(r); err != nil { + // Only process templates if the file has .gotmpl extension + if filepath.Ext(st.FilePath) == ".gotmpl" { + successFlag := false + for it, prev := 0, release; it < 6; it++ { + tmplData := st.createReleaseTemplateData(prev, vals) + renderer := tmpl.NewFileRenderer(st.fs, st.basePath, tmplData) + r, err := release.ExecuteTemplateExpressions(renderer) + if err != nil { return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err) } - st.Releases[i] = *r - break + if reflect.DeepEqual(prev, r) { + successFlag = true + if err := updateBoolTemplatedValues(r); err != nil { + return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err) + } + st.Releases[i] = *r + break + } + prev = r } - prev = r - } - if !successFlag { - return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %s", st.FilePath, release.Name, - "recursive references can't be resolved") + if !successFlag { + return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %s", st.FilePath, release.Name, + "recursive references can't be resolved") + } + } else { + // For non-.gotmpl files, skip template processing entirely + st.Releases[i] = *release } if st.Releases[i].Chart == "" { diff --git a/pkg/state/state_exec_tmpl_test.go b/pkg/state/state_exec_tmpl_test.go index 33a0a47c..d204d21e 100644 --- a/pkg/state/state_exec_tmpl_test.go +++ b/pkg/state/state_exec_tmpl_test.go @@ -172,6 +172,7 @@ func TestHelmState_executeTemplates(t *testing.T) { fs: &filesystem.FileSystem{ Glob: func(s string) ([]string, error) { return nil, nil }}, basePath: ".", + FilePath: "helmfile.yaml.gotmpl", // Set to .gotmpl to enable template processing ReleaseSetSpec: ReleaseSetSpec{ HelmDefaults: HelmSpec{ KubeContext: "test_context", @@ -269,6 +270,7 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) { t.Run(tt.name, func(t *testing.T) { state := &HelmState{ basePath: ".", + FilePath: "helmfile.yaml.gotmpl", // Set to .gotmpl to enable template processing for error tests fs: &filesystem.FileSystem{ Glob: func(s string) ([]string, error) { return nil, nil }, }, @@ -294,3 +296,97 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) { }) } } + +func TestHelmState_executeTemplates_yaml_vs_gotmpl(t *testing.T) { + tests := []struct { + name string + filePath string + input ReleaseSpec + want ReleaseSpec + }{ + { + name: "YAML file should NOT process templates", + filePath: "helmfile.yaml", + input: ReleaseSpec{ + Chart: "test-charts/{{ .Release.Name }}", + Version: "{{ .Release.Name }}-0.1", + Name: "test-app", + Namespace: "test-namespace-{{ .Release.Name }}", + Labels: map[string]string{"id": "{{ .Release.Name }}"}, + }, + want: ReleaseSpec{ + Chart: "test-charts/{{ .Release.Name }}", // Template NOT processed + Version: "{{ .Release.Name }}-0.1", // Template NOT processed + Name: "test-app", + Namespace: "test-namespace-{{ .Release.Name }}", // Template NOT processed + Labels: map[string]string{"id": "{{ .Release.Name }}"}, // Template NOT processed + }, + }, + { + name: "GOTMPL file should process templates", + filePath: "helmfile.yaml.gotmpl", + input: ReleaseSpec{ + Chart: "test-charts/{{ .Release.Name }}", + Version: "{{ .Release.Name }}-0.1", + Name: "test-app", + Namespace: "test-namespace-{{ .Release.Name }}", + Labels: map[string]string{"id": "{{ .Release.Name }}"}, + }, + want: ReleaseSpec{ + Chart: "test-charts/test-app", // Template processed + Version: "test-app-0.1", // Template processed + Name: "test-app", + Namespace: "test-namespace-test-app", // Template processed + Labels: map[string]string{"id": "test-app"}, // Template processed + }, + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + state := &HelmState{ + fs: &filesystem.FileSystem{ + Glob: func(s string) ([]string, error) { return nil, nil }}, + basePath: ".", + FilePath: tt.filePath, // This is the key difference + ReleaseSetSpec: ReleaseSetSpec{ + HelmDefaults: HelmSpec{ + KubeContext: "test_context", + }, + Env: environment.Environment{Name: "test_env"}, + OverrideNamespace: "test-namespace_", + Repositories: nil, + Releases: []ReleaseSpec{ + tt.input, + }, + }, + RenderedValues: map[string]any{}, + } + + r, err := state.ExecuteTemplates() + if err != nil { + t.Errorf("Unexpected error: %v", err) + t.FailNow() + } + + actual := r.Releases[0] + + if !reflect.DeepEqual(actual.Name, tt.want.Name) { + t.Errorf("expected Name %+v, got %+v", tt.want.Name, actual.Name) + } + if !reflect.DeepEqual(actual.Chart, tt.want.Chart) { + t.Errorf("expected Chart %+v, got %+v", tt.want.Chart, actual.Chart) + } + if !reflect.DeepEqual(actual.Namespace, tt.want.Namespace) { + t.Errorf("expected Namespace %+v, got %+v", tt.want.Namespace, actual.Namespace) + } + if !reflect.DeepEqual(actual.Version, tt.want.Version) { + t.Errorf("expected Version %+v, got %+v", tt.want.Version, actual.Version) + } + if diff := deep.Equal(actual.Labels, tt.want.Labels); diff != nil && len(actual.Labels) > 0 { + t.Errorf("Labels differs \n%+v", strings.Join(diff, "\n")) + } + }) + } +}