731 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			731 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
| package state
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"go.uber.org/zap"
 | |
| 
 | |
| 	"github.com/helmfile/helmfile/pkg/environment"
 | |
| 	"github.com/helmfile/helmfile/pkg/filesystem"
 | |
| 	"github.com/helmfile/helmfile/pkg/remote"
 | |
| 	"github.com/helmfile/helmfile/pkg/testhelper"
 | |
| )
 | |
| 
 | |
| func createFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) {
 | |
| 	c := &StateCreator{
 | |
| 		logger: logger,
 | |
| 		fs:     filesystem.DefaultFileSystem(),
 | |
| 		Strict: true,
 | |
| 	}
 | |
| 	return c.ParseAndLoad(content, filepath.Dir(file), file, env, false, true, nil, nil)
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   chart: mychart
 | |
| `)
 | |
| 	state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if state.Releases[0].Name != "myrelease" {
 | |
| 		t.Errorf("unexpected release name: expected=myrelease actual=%s", state.Releases[0].Name)
 | |
| 	}
 | |
| 	if state.Releases[0].Namespace != "mynamespace" {
 | |
| 		t.Errorf("unexpected chart namespace: expected=mynamespace actual=%s", state.Releases[0].Chart)
 | |
| 	}
 | |
| 	if state.Releases[0].Chart != "mychart" {
 | |
| 		t.Errorf("unexpected chart name: expected=mychart actual=%s", state.Releases[0].Chart)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_NonexistentEnv(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   chart: mychart
 | |
| `)
 | |
| 	_, err := createFromYaml(yamlContent, yamlFile, "production", logger)
 | |
| 	// This does not produce an error because the environment existence check if done
 | |
| 	// outside of the ParseAndLoad function since
 | |
| 	// https://github.com/helmfile/helmfile/pull/885
 | |
| 	require.NoError(t, err)
 | |
| }
 | |
| 
 | |
| type stateTestEnv struct {
 | |
| 	Files   map[string]string
 | |
| 	WorkDir string
 | |
| }
 | |
| 
 | |
| func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState {
 | |
| 	return testEnv.MustLoadStateWithEnableLiveOutput(t, file, envName, false)
 | |
| }
 | |
| 
 | |
| func (testEnv stateTestEnv) MustLoadStateWithEnableLiveOutput(t *testing.T, file, envName string, enableLiveOutput bool) *HelmState {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	testFs := testhelper.NewTestFs(testEnv.Files)
 | |
| 
 | |
| 	if testFs.Cwd == "" {
 | |
| 		testFs.Cwd = "/"
 | |
| 	}
 | |
| 
 | |
| 	yamlContent, ok := testEnv.Files[file]
 | |
| 	if !ok {
 | |
| 		t.Fatalf("no file named %q registered", file)
 | |
| 	}
 | |
| 
 | |
| 	r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
 | |
| 	state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, enableLiveOutput, "").
 | |
| 		ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, true, nil, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return state
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_NonDefaultEnv(t *testing.T) {
 | |
| 	yamlFile := "/example/path/to/helmfile.yaml"
 | |
| 	yamlContent := []byte(`environments:
 | |
|   production:
 | |
|     values:
 | |
|     - foo.yaml
 | |
|     - bar.yaml.gotmpl
 | |
| 
 | |
| releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   chart: mychart
 | |
|   values:
 | |
|   - values.yaml.gotmpl
 | |
| `)
 | |
| 
 | |
| 	fooYamlFile := "/example/path/to/foo.yaml"
 | |
| 	fooYamlContent := []byte(`foo: foo
 | |
| # As this file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
 | |
| baz: "{{ readFile \"baz.txt\" }}"`)
 | |
| 
 | |
| 	barYamlFile := "/example/path/to/bar.yaml.gotmpl"
 | |
| 	barYamlContent := []byte(`foo: FOO
 | |
| bar: {{ readFile "bar.txt" }}
 | |
| env: {{ .Environment.Name }}
 | |
| `)
 | |
| 
 | |
| 	barTextFile := "/example/path/to/bar.txt"
 | |
| 	barTextContent := []byte("BAR")
 | |
| 
 | |
| 	expected := map[string]any{
 | |
| 		"foo": "FOO",
 | |
| 		"bar": "BAR",
 | |
| 		// As the file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
 | |
| 		"baz": "{{ readFile \"baz.txt\" }}",
 | |
| 		"env": "production",
 | |
| 	}
 | |
| 
 | |
| 	valuesFile := "/example/path/to/values.yaml.gotmpl"
 | |
| 	valuesContent := []byte(`env: {{ .Environment.Name }}
 | |
| releaseName: {{ .Release.Name }}
 | |
| releaseNamespace: {{ .Release.Namespace }}
 | |
| `)
 | |
| 
 | |
| 	expectedValues := `env: production
 | |
| releaseName: myrelease
 | |
| releaseNamespace: mynamespace
 | |
| `
 | |
| 
 | |
| 	testFs := testhelper.NewTestFs(map[string]string{
 | |
| 		fooYamlFile: string(fooYamlContent),
 | |
| 		barYamlFile: string(barYamlContent),
 | |
| 		barTextFile: string(barTextContent),
 | |
| 		valuesFile:  string(valuesContent),
 | |
| 	})
 | |
| 	testFs.Cwd = "/example/path/to"
 | |
| 
 | |
| 	r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
 | |
| 	env := environment.Environment{
 | |
| 		Name: "production",
 | |
| 	}
 | |
| 	state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "").
 | |
| 		ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, true, &env, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	actual := state.Env.Values
 | |
| 	if !reflect.DeepEqual(actual, expected) {
 | |
| 		t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
 | |
| 	}
 | |
| 
 | |
| 	release := state.Releases[0]
 | |
| 
 | |
| 	state.ApplyOverrides(&release)
 | |
| 
 | |
| 	actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	actualValues := string(actualValuesData)
 | |
| 
 | |
| 	if !reflect.DeepEqual(expectedValues, actualValues) {
 | |
| 		t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_OverrideNamespace(t *testing.T) {
 | |
| 	yamlFile := "/example/path/to/helmfile.yaml"
 | |
| 	yamlContent := []byte(`environments:
 | |
|   production:
 | |
|     values:
 | |
|     - foo.yaml
 | |
|     - bar.yaml.gotmpl
 | |
| 
 | |
| # A.k.a helmfile apply --namespace myns
 | |
| namespace: myns
 | |
| 
 | |
| releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   chart: mychart
 | |
|   values:
 | |
|   - values.yaml.gotmpl
 | |
| `)
 | |
| 
 | |
| 	fooYamlFile := "/example/path/to/foo.yaml"
 | |
| 	fooYamlContent := []byte(`foo: foo
 | |
| # As this file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
 | |
| baz: "{{ readFile \"baz.txt\" }}"`)
 | |
| 
 | |
| 	barYamlFile := "/example/path/to/bar.yaml.gotmpl"
 | |
| 	barYamlContent := []byte(`foo: FOO
 | |
| bar: {{ readFile "bar.txt" }}
 | |
| `)
 | |
| 
 | |
| 	barTextFile := "/example/path/to/bar.txt"
 | |
| 	barTextContent := []byte("BAR")
 | |
| 
 | |
| 	expected := map[string]any{
 | |
| 		"foo": "FOO",
 | |
| 		"bar": "BAR",
 | |
| 		// As the file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
 | |
| 		"baz": "{{ readFile \"baz.txt\" }}",
 | |
| 	}
 | |
| 
 | |
| 	valuesFile := "/example/path/to/values.yaml.gotmpl"
 | |
| 	valuesContent := []byte(`env: {{ .Environment.Name }}
 | |
| releaseName: {{ .Release.Name }}
 | |
| releaseNamespace: {{ .Release.Namespace }}
 | |
| overrideNamespace: {{ .Namespace }}
 | |
| `)
 | |
| 
 | |
| 	expectedValues := `env: production
 | |
| releaseName: myrelease
 | |
| releaseNamespace: myns
 | |
| overrideNamespace: myns
 | |
| `
 | |
| 
 | |
| 	testFs := testhelper.NewTestFs(map[string]string{
 | |
| 		fooYamlFile: string(fooYamlContent),
 | |
| 		barYamlFile: string(barYamlContent),
 | |
| 		barTextFile: string(barTextContent),
 | |
| 		valuesFile:  string(valuesContent),
 | |
| 	})
 | |
| 	testFs.Cwd = "/example/path/to"
 | |
| 
 | |
| 	r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
 | |
| 	state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "").
 | |
| 		ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, true, nil, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	actual := state.Env.Values
 | |
| 	if !reflect.DeepEqual(actual, expected) {
 | |
| 		t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
 | |
| 	}
 | |
| 
 | |
| 	release := state.Releases[0]
 | |
| 
 | |
| 	state.ApplyOverrides(&release)
 | |
| 
 | |
| 	actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	actualValues := string(actualValuesData)
 | |
| 
 | |
| 	if !reflect.DeepEqual(expectedValues, actualValues) {
 | |
| 		t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_StrictUnmarshalling(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   releases: mychart
 | |
| `)
 | |
| 	_, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
 | |
| 	if err == nil {
 | |
| 		t.Error("expected an error for wrong key 'releases' which is not in struct")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_ConflictingReleasesConfig(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`charts:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
| releases:
 | |
| - name: myrelease2
 | |
|   chart: mychart2
 | |
| `)
 | |
| 	_, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
 | |
| 	if err == nil {
 | |
| 		t.Error("expected error")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_FilterReleasesOnLabels(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     tier: frontend
 | |
|     foo: bar
 | |
| - name: myrelease2
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     tier: frontend
 | |
| - name: myrelease3
 | |
|   chart: mychart3
 | |
|   labels:
 | |
|     tier: backend
 | |
| `)
 | |
| 	cases := []struct {
 | |
| 		filter  LabelFilter
 | |
| 		results []bool
 | |
| 	}{
 | |
| 		{LabelFilter{positiveLabels: [][]string{{"tier", "frontend"}}},
 | |
| 			[]bool{true, true, false}},
 | |
| 		{LabelFilter{positiveLabels: [][]string{{"tier", "frontend"}, {"foo", "bar"}}},
 | |
| 			[]bool{true, false, false}},
 | |
| 		{LabelFilter{negativeLabels: [][]string{{"tier", "frontend"}}},
 | |
| 			[]bool{false, false, true}},
 | |
| 		{LabelFilter{positiveLabels: [][]string{{"tier", "frontend"}}, negativeLabels: [][]string{{"foo", "bar"}}},
 | |
| 			[]bool{false, true, false}},
 | |
| 	}
 | |
| 	state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	for idx, c := range cases {
 | |
| 		for idx2, expected := range c.results {
 | |
| 			if f := c.filter.Match(state.Releases[idx2]); f != expected {
 | |
| 				t.Errorf("[case: %d][outcome: %d] Unexpected outcome wanted %t, got %t", idx, idx2, expected, f)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_FilterNegatives(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     stage: pre
 | |
|     foo: bar
 | |
| - name: myrelease2
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     stage: post
 | |
| - name: myrelease3
 | |
|   chart: mychart3
 | |
| `)
 | |
| 	cases := []struct {
 | |
| 		filter  LabelFilter
 | |
| 		results []bool
 | |
| 	}{
 | |
| 		{LabelFilter{positiveLabels: [][]string{{"stage", "pre"}}},
 | |
| 			[]bool{true, false, false}},
 | |
| 		{LabelFilter{positiveLabels: [][]string{{"stage", "post"}}},
 | |
| 			[]bool{false, true, false}},
 | |
| 		{LabelFilter{negativeLabels: [][]string{{"stage", "pre"}, {"stage", "post"}}},
 | |
| 			[]bool{false, false, true}},
 | |
| 		{LabelFilter{negativeLabels: [][]string{{"foo", "bar"}}},
 | |
| 			[]bool{false, true, true}},
 | |
| 	}
 | |
| 	state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	for idx, c := range cases {
 | |
| 		for idx2, expected := range c.results {
 | |
| 			if f := c.filter.Match(state.Releases[idx2]); f != expected {
 | |
| 				t.Errorf("[case: %d][outcome: %d] Unexpected outcome wanted %t, got %t", idx, idx2, expected, f)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_Helmfiles_Selectors(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		path      string
 | |
| 		content   []byte
 | |
| 		wantErr   bool
 | |
| 		helmfiles []SubHelmfileSpec
 | |
| 	}{
 | |
| 		{
 | |
| 			path: "working/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - simple/helmfile.yaml
 | |
| - path: path/prefix/selector.yaml
 | |
|   selectors:
 | |
|     - name=zorba
 | |
|     - foo=bar
 | |
| - path: path/prefix/empty/selector.yaml
 | |
|   selectors: []
 | |
| - path: path/prefix/inherits/selector.yaml
 | |
|   selectorsInherited: true
 | |
| `),
 | |
| 			wantErr: false,
 | |
| 			helmfiles: []SubHelmfileSpec{{Path: "simple/helmfile.yaml", Selectors: nil, SelectorsInherited: false},
 | |
| 				{Path: "path/prefix/selector.yaml", Selectors: []string{"name=zorba", "foo=bar"}, SelectorsInherited: false},
 | |
| 				{Path: "path/prefix/empty/selector.yaml", Selectors: []string{}, SelectorsInherited: false},
 | |
| 				{Path: "path/prefix/inherits/selector.yaml", Selectors: nil, SelectorsInherited: true},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing2/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - path: failing2/helmfile.yaml
 | |
|     wrongkey:
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing3/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - path: failing3/helmfile.yaml
 | |
|     selectors: foo
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing4/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - path: failing4/helmfile.yaml
 | |
|     selectors:
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing4/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - path: failing4/helmfile.yaml
 | |
|     selectors:
 | |
|       - colon: not-authorized
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing6/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - selectors:
 | |
|     - whatever
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			path: "failing7/selector",
 | |
| 			content: []byte(`helmfiles:
 | |
| - path: foo/bar
 | |
|   selectors:
 | |
|   - foo=bar
 | |
|   selectorsInherited: true
 | |
| `),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range tests {
 | |
| 		st, err := createFromYaml(test.content, test.path, DefaultEnv, logger)
 | |
| 		if err != nil {
 | |
| 			if test.wantErr {
 | |
| 				continue
 | |
| 			} else {
 | |
| 				t.Error("unexpected error:", err)
 | |
| 			}
 | |
| 		}
 | |
| 		require.Equalf(t, test.helmfiles, st.Helmfiles, "for path %s", test.path)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadFromYaml_EnvironmentContext(t *testing.T) {
 | |
| 	yamlFile := "/example/path/to/helmfile.yaml"
 | |
| 	yamlContent := []byte(`environments:
 | |
|   production:
 | |
|     values: []
 | |
|     kubeContext: myCtx
 | |
| 
 | |
| releases:
 | |
| - name: myrelease
 | |
|   namespace: mynamespace
 | |
|   chart: mychart
 | |
|   values:
 | |
|   - values.yaml.gotmpl
 | |
| `)
 | |
| 
 | |
| 	valuesFile := "/example/path/to/values.yaml.gotmpl"
 | |
| 	valuesContent := []byte(`envName: {{ .Environment.Name }}
 | |
| envContext: {{ .Environment.KubeContext }}
 | |
| releaseName: {{ .Release.Name }}
 | |
| releaseContext: {{ .Release.KubeContext }}
 | |
| `)
 | |
| 
 | |
| 	expectedValues := `envName: production
 | |
| envContext: myCtx
 | |
| releaseName: myrelease
 | |
| releaseContext: 
 | |
| `
 | |
| 
 | |
| 	testFs := testhelper.NewTestFs(map[string]string{
 | |
| 		valuesFile: string(valuesContent),
 | |
| 	})
 | |
| 	testFs.Cwd = "/example/path/to"
 | |
| 
 | |
| 	r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
 | |
| 	state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "").
 | |
| 		ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, true, nil, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	release := state.Releases[0]
 | |
| 
 | |
| 	state.ApplyOverrides(&release)
 | |
| 
 | |
| 	actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	actualValues := string(actualValuesData)
 | |
| 
 | |
| 	if !reflect.DeepEqual(expectedValues, actualValues) {
 | |
| 		t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestHelmBinaryInBases tests that helmBinary and kustomizeBinary settings
 | |
| // from bases are properly merged with later values overriding earlier ones
 | |
| func TestHelmBinaryInBases(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name                    string
 | |
| 		files                   map[string]string
 | |
| 		mainFile                string
 | |
| 		expectedHelmBinary      string
 | |
| 		expectedKustomizeBinary string
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "helmBinary in second base should be used",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/env.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/repos.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/releases.yaml
 | |
| `,
 | |
| 				"/path/to/bases/env.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - key: value1
 | |
| `,
 | |
| 				"/path/to/bases/repos.yaml": `repositories:
 | |
|   - name: stable
 | |
|     url: https://charts.helm.sh/stable
 | |
| helmBinary: /path/to/custom/helm
 | |
| `,
 | |
| 				"/path/to/bases/releases.yaml": `releases:
 | |
|   - name: myapp
 | |
|     chart: stable/nginx
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      "/path/to/custom/helm",
 | |
| 			expectedKustomizeBinary: DefaultKustomizeBinary,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "helmBinary in main file after bases should override",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/env.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/repos.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/releases.yaml
 | |
| helmBinary: /path/to/main/helm
 | |
| `,
 | |
| 				"/path/to/bases/env.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - key: value1
 | |
| `,
 | |
| 				"/path/to/bases/repos.yaml": `repositories:
 | |
|   - name: stable
 | |
|     url: https://charts.helm.sh/stable
 | |
| helmBinary: /path/to/base/helm
 | |
| `,
 | |
| 				"/path/to/bases/releases.yaml": `releases:
 | |
|   - name: myapp
 | |
|     chart: stable/nginx
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      "/path/to/main/helm",
 | |
| 			expectedKustomizeBinary: DefaultKustomizeBinary,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "helmBinary in main file between bases should override earlier bases",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/env.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/repos.yaml
 | |
| helmBinary: /path/to/middle/helm
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/releases.yaml
 | |
| `,
 | |
| 				"/path/to/bases/env.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - key: value1
 | |
| `,
 | |
| 				"/path/to/bases/repos.yaml": `repositories:
 | |
|   - name: stable
 | |
|     url: https://charts.helm.sh/stable
 | |
| helmBinary: /path/to/base/helm
 | |
| `,
 | |
| 				"/path/to/bases/releases.yaml": `releases:
 | |
|   - name: myapp
 | |
|     chart: stable/nginx
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      "/path/to/middle/helm",
 | |
| 			expectedKustomizeBinary: DefaultKustomizeBinary,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "kustomizeBinary in base should be used",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/base.yaml
 | |
| `,
 | |
| 				"/path/to/bases/base.yaml": `kustomizeBinary: /path/to/custom/kustomize
 | |
| releases:
 | |
|   - name: myapp
 | |
|     chart: mychart
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      DefaultHelmBinary,
 | |
| 			expectedKustomizeBinary: "/path/to/custom/kustomize",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "both helmBinary and kustomizeBinary in different bases",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/helm.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/kustomize.yaml
 | |
| `,
 | |
| 				"/path/to/bases/helm.yaml": `helmBinary: /path/to/custom/helm
 | |
| `,
 | |
| 				"/path/to/bases/kustomize.yaml": `kustomizeBinary: /path/to/custom/kustomize
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      "/path/to/custom/helm",
 | |
| 			expectedKustomizeBinary: "/path/to/custom/kustomize",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "later base overrides earlier base for helmBinary",
 | |
| 			files: map[string]string{
 | |
| 				"/path/to/helmfile.yaml": `bases:
 | |
|   - ./bases/first.yaml
 | |
| ---
 | |
| bases:
 | |
|   - ./bases/second.yaml
 | |
| `,
 | |
| 				"/path/to/bases/first.yaml": `helmBinary: /path/to/first/helm
 | |
| `,
 | |
| 				"/path/to/bases/second.yaml": `helmBinary: /path/to/second/helm
 | |
| `,
 | |
| 			},
 | |
| 			mainFile:                "/path/to/helmfile.yaml",
 | |
| 			expectedHelmBinary:      "/path/to/second/helm",
 | |
| 			expectedKustomizeBinary: DefaultKustomizeBinary,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			testFs := testhelper.NewTestFs(tt.files)
 | |
| 			if testFs.Cwd == "" {
 | |
| 				testFs.Cwd = "/"
 | |
| 			}
 | |
| 
 | |
| 			r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
 | |
| 			creator := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "")
 | |
| 
 | |
| 			// Set up LoadFile for recursive base loading
 | |
| 			creator.LoadFile = func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) {
 | |
| 				path := filepath.Join(baseDir, file)
 | |
| 				content, ok := tt.files[path]
 | |
| 				if !ok {
 | |
| 					return nil, fmt.Errorf("file not found: %s", path)
 | |
| 				}
 | |
| 				return creator.ParseAndLoad([]byte(content), filepath.Dir(path), path, DefaultEnv, true, evaluateBases, inheritedEnv, overrodeEnv)
 | |
| 			}
 | |
| 
 | |
| 			yamlContent, ok := tt.files[tt.mainFile]
 | |
| 			if !ok {
 | |
| 				t.Fatalf("no file named %q registered", tt.mainFile)
 | |
| 			}
 | |
| 
 | |
| 			state, err := creator.ParseAndLoad([]byte(yamlContent), filepath.Dir(tt.mainFile), tt.mainFile, DefaultEnv, true, true, nil, nil)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("unexpected error: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			if state.DefaultHelmBinary != tt.expectedHelmBinary {
 | |
| 				t.Errorf("helmBinary mismatch: expected=%s, actual=%s",
 | |
| 					tt.expectedHelmBinary, state.DefaultHelmBinary)
 | |
| 			}
 | |
| 
 | |
| 			if state.DefaultKustomizeBinary != tt.expectedKustomizeBinary {
 | |
| 				t.Errorf("kustomizeBinary mismatch: expected=%s, actual=%s",
 | |
| 					tt.expectedKustomizeBinary, state.DefaultKustomizeBinary)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |