1494 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			1494 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
| package app
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/roboll/helmfile/helmexec"
 | |
| 	"github.com/roboll/helmfile/state"
 | |
| 	"gotest.tools/env"
 | |
| )
 | |
| 
 | |
| func appWithFs(app *App, files map[string]string) *App {
 | |
| 	fs := state.NewTestFs(files)
 | |
| 	return injectFs(app, fs)
 | |
| }
 | |
| 
 | |
| func injectFs(app *App, fs *state.TestFs) *App {
 | |
| 	app.readFile = fs.ReadFile
 | |
| 	app.glob = fs.Glob
 | |
| 	app.abs = fs.Abs
 | |
| 	app.getwd = fs.Getwd
 | |
| 	app.chdir = fs.Chdir
 | |
| 	app.fileExistsAt = fs.FileExistsAt
 | |
| 	app.fileExists = fs.FileExists
 | |
| 	app.directoryExistsAt = fs.DirectoryExistsAt
 | |
| 	return app
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_ReleaseOrder(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| - helmfile.d/b*.yaml
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a2.yaml": `
 | |
| releases:
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| `,
 | |
| 	}
 | |
| 	fs := state.NewTestFs(files)
 | |
| 	fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
 | |
| 	app := &App{
 | |
| 		KubeContext: "default",
 | |
| 		Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 		Namespace:   "",
 | |
| 		Env:         "default",
 | |
| 	}
 | |
| 	app = injectFs(app, fs)
 | |
| 	actualOrder := []string{}
 | |
| 	noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 		actualOrder = append(actualOrder, st.FilePath)
 | |
| 		return []error{}
 | |
| 	}
 | |
| 
 | |
| 	err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 		"helmfile.yaml", noop,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	expectedOrder := []string{"a1.yaml", "a2.yaml", "b.yaml", "helmfile.yaml"}
 | |
| 	if !reflect.DeepEqual(actualOrder, expectedOrder) {
 | |
| 		t.Errorf("unexpected order of processed state files: expected=%v, actual=%v", expectedOrder, actualOrder)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_EnvValuesFileOrder(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| environments:
 | |
|   default:
 | |
|     values:
 | |
|     - env.*.yaml
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/env.1.yaml": `FOO: 1
 | |
| BAR: 2
 | |
| `,
 | |
| 		"/path/to/env.2.yaml": `BAR: 3
 | |
| BAZ: 4
 | |
| `,
 | |
| 	}
 | |
| 	fs := state.NewTestFs(files)
 | |
| 	fs.GlobFixtures["/path/to/env.*.yaml"] = []string{"/path/to/env.2.yaml", "/path/to/env.1.yaml"}
 | |
| 	app := &App{
 | |
| 		KubeContext: "default",
 | |
| 		Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 		Namespace:   "",
 | |
| 		Env:         "default",
 | |
| 	}
 | |
| 	app = injectFs(app, fs)
 | |
| 	noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 		return []error{}
 | |
| 	}
 | |
| 
 | |
| 	err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 		"helmfile.yaml", noop,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	expectedOrder := []string{"helmfile.yaml", "/path/to/env.1.yaml", "/path/to/env.2.yaml", "/path/to/env.1.yaml", "/path/to/env.2.yaml"}
 | |
| 	actualOrder := fs.SuccessfulReads()
 | |
| 	if !reflect.DeepEqual(actualOrder, expectedOrder) {
 | |
| 		t.Errorf("unexpected order of processed state files: expected=%v, actual=%v", expectedOrder, actualOrder)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_MissingEnvValuesFile(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| environments:
 | |
|   default:
 | |
|     values:
 | |
|     - env.*.yaml
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 	}
 | |
| 	fs := state.NewTestFs(files)
 | |
| 	app := &App{
 | |
| 		KubeContext: "default",
 | |
| 		Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 		Namespace:   "",
 | |
| 		Env:         "default",
 | |
| 	}
 | |
| 	app = injectFs(app, fs)
 | |
| 	noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 		return []error{}
 | |
| 	}
 | |
| 
 | |
| 	err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 		"helmfile.yaml", noop,
 | |
| 	)
 | |
| 	if err == nil {
 | |
| 		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 in \".\""
 | |
| 	if err.Error() != expected {
 | |
| 		t.Errorf("unexpected error: expected=%s, got=%v", expected, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_MissingEnvValuesFileHandler(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		name        string
 | |
| 		handler     string
 | |
| 		filePattern string
 | |
| 		expectErr   bool
 | |
| 	}{
 | |
| 		{name: "error handler with no files matching glob", handler: "Error", filePattern: "env.*.yaml", expectErr: true},
 | |
| 		{name: "warn handler with no files matching glob", handler: "Warn", filePattern: "env.*.yaml", expectErr: false},
 | |
| 		{name: "info handler with no files matching glob", handler: "Info", filePattern: "env.*.yaml", expectErr: false},
 | |
| 		{name: "debug handler with no files matching glob", handler: "Debug", filePattern: "env.*.yaml", expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	for i := range testcases {
 | |
| 		testcase := testcases[i]
 | |
| 		t.Run(testcase.name, func(t *testing.T) {
 | |
| 			files := map[string]string{
 | |
| 				"/path/to/helmfile.yaml": fmt.Sprintf(`
 | |
| environments:
 | |
|   default:
 | |
|     missingFileHandler: %s
 | |
|     values:
 | |
|     - %s
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `, testcase.handler, testcase.filePattern),
 | |
| 			}
 | |
| 			fs := state.NewTestFs(files)
 | |
| 			app := &App{
 | |
| 				KubeContext: "default",
 | |
| 				Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 				Namespace:   "",
 | |
| 				Env:         "default",
 | |
| 			}
 | |
| 			app = injectFs(app, fs)
 | |
| 			noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 				return []error{}
 | |
| 			}
 | |
| 
 | |
| 			err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 				"helmfile.yaml", noop,
 | |
| 			)
 | |
| 			if testcase.expectErr && err == nil {
 | |
| 				t.Fatal("expected error did not occur")
 | |
| 			}
 | |
| 
 | |
| 			if !testcase.expectErr && err != nil {
 | |
| 				t.Errorf("not error expected, but got: %v", err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // See https://github.com/roboll/helmfile/issues/193
 | |
| func TestVisitDesiredStatesWithReleasesFiltered(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| - helmfile.d/b*.yaml
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a2.yaml": `
 | |
| releases:
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| `,
 | |
| 	}
 | |
| 	testcases := []struct {
 | |
| 		name      string
 | |
| 		expectErr bool
 | |
| 	}{
 | |
| 		{name: "prometheus", expectErr: false},
 | |
| 		{name: "zipkin", expectErr: false},
 | |
| 		{name: "grafana", expectErr: false},
 | |
| 		{name: "elasticsearch", expectErr: true},
 | |
| 	}
 | |
| 
 | |
| 	for _, testcase := range testcases {
 | |
| 		fs := state.NewTestFs(files)
 | |
| 		fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
 | |
| 		app := &App{
 | |
| 			KubeContext: "default",
 | |
| 			Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 			Selectors:   []string{fmt.Sprintf("name=%s", testcase.name)},
 | |
| 			Namespace:   "",
 | |
| 			Env:         "default",
 | |
| 		}
 | |
| 		app = injectFs(app, fs)
 | |
| 		noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 			return []error{}
 | |
| 		}
 | |
| 
 | |
| 		err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 			"helmfile.yaml", noop,
 | |
| 		)
 | |
| 		if testcase.expectErr && err == nil {
 | |
| 			t.Errorf("error expected but not happened for name=%s", testcase.name)
 | |
| 		} else if !testcase.expectErr && err != nil {
 | |
| 			t.Errorf("unexpected error for name=%s: %v", testcase.name, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // See https://github.com/roboll/helmfile/issues/320
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_UndefinedEnv(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| environments:
 | |
|   prod:
 | |
| 
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| environments:
 | |
|   prod:
 | |
| 
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 	}
 | |
| 	noop := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 		return []error{}
 | |
| 	}
 | |
| 
 | |
| 	testcases := []struct {
 | |
| 		name      string
 | |
| 		expectErr bool
 | |
| 	}{
 | |
| 		{name: "undefined_env", expectErr: true},
 | |
| 		{name: "default", expectErr: false},
 | |
| 		{name: "prod", expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	for _, testcase := range testcases {
 | |
| 		app := appWithFs(&App{
 | |
| 			KubeContext: "default",
 | |
| 			Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 			Namespace:   "",
 | |
| 			Selectors:   []string{},
 | |
| 			Env:         testcase.name,
 | |
| 		}, files)
 | |
| 		err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 			"helmfile.yaml", noop,
 | |
| 		)
 | |
| 		if testcase.expectErr && err == nil {
 | |
| 			t.Errorf("error expected but not happened for environment=%s", testcase.name)
 | |
| 		} else if !testcase.expectErr && err != nil {
 | |
| 			t.Errorf("unexpected error for environment=%s: %v", testcase.name, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // See https://github.com/roboll/helmfile/issues/322
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_Selectors(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| - helmfile.d/b*.yaml
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a2.yaml": `
 | |
| releases:
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| helmDefaults:
 | |
|   tillerNamespace: zoo
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| - name: foo
 | |
|   chart: charts/foo
 | |
|   labels:
 | |
|     duplicated: yes
 | |
| - name: foo
 | |
|   chart: charts/foo
 | |
|   labels:
 | |
|     duplicated: yes
 | |
| - name: bar
 | |
|   chart: charts/foo
 | |
|   tillerNamespace:  bar1
 | |
|   labels:
 | |
|     duplicatedOK: yes
 | |
| - name: bar
 | |
|   chart: charts/foo
 | |
|   tillerNamespace: bar2
 | |
|   labels:
 | |
|     duplicatedOK: yes
 | |
| `,
 | |
| 	}
 | |
| 
 | |
| 	testcases := []struct {
 | |
| 		label         string
 | |
| 		expectedCount int
 | |
| 		expectErr     bool
 | |
| 		errMsg        string
 | |
| 	}{
 | |
| 		{label: "name=prometheus", expectedCount: 1, expectErr: false},
 | |
| 		{label: "name=", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name=. Expected label in form k=v or k!=v"},
 | |
| 		{label: "name!=", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name!=. Expected label in form k=v or k!=v"},
 | |
| 		{label: "name", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name. Expected label in form k=v or k!=v"},
 | |
| 		// See https://github.com/roboll/helmfile/issues/193
 | |
| 		{label: "duplicated=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in \"zoo\": there were 2 releases named \"foo\" matching specified selector"},
 | |
| 		{label: "duplicatedOK=yes", expectedCount: 2, expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	for _, testcase := range testcases {
 | |
| 		actual := []string{}
 | |
| 
 | |
| 		collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 			for _, r := range st.Releases {
 | |
| 				actual = append(actual, r.Name)
 | |
| 			}
 | |
| 			return []error{}
 | |
| 		}
 | |
| 
 | |
| 		app := appWithFs(&App{
 | |
| 			KubeContext: "default",
 | |
| 			Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 			Namespace:   "",
 | |
| 			Selectors:   []string{testcase.label},
 | |
| 			Env:         "default",
 | |
| 		}, files)
 | |
| 
 | |
| 		err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 			"helmfile.yaml", collectReleases,
 | |
| 		)
 | |
| 		if testcase.expectErr {
 | |
| 			if err == nil {
 | |
| 				t.Errorf("error expected but not happened for selector %s", testcase.label)
 | |
| 			} else if err.Error() != testcase.errMsg {
 | |
| 				t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", testcase.errMsg, err.Error())
 | |
| 			}
 | |
| 		} else if !testcase.expectErr && err != nil {
 | |
| 			t.Errorf("unexpected error for selector %s: %v", testcase.label, err)
 | |
| 		}
 | |
| 		if len(actual) != testcase.expectedCount {
 | |
| 			t.Errorf("unexpected release count for selector %s: expected=%d, actual=%d", testcase.label, testcase.expectedCount, len(actual))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_EmbeddedSelectors(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - path: helmfile.d/a*.yaml
 | |
|   selectors:
 | |
|   - name=prometheus      
 | |
|   - name=zipkin      
 | |
| - helmfile.d/b*.yaml
 | |
| - path: helmfile.d/c*.yaml
 | |
|   selectors: []
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a2.yaml": `
 | |
| releases:
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a3.yaml": `
 | |
| releases:
 | |
| - name: mongodb
 | |
|   chart: stable/mongodb
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| - name: bar
 | |
|   chart: charts/foo
 | |
|   tillerNamespace:  bar1
 | |
|   labels:
 | |
|     duplicatedOK: yes
 | |
| - name: bar
 | |
|   chart: charts/foo
 | |
|   tillerNamespace: bar2
 | |
|   labels:
 | |
|     duplicatedOK: yes
 | |
| `,
 | |
| 		"/path/to/helmfile.d/c.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| - name: postgresql
 | |
|   chart: charts/postgresql
 | |
|   labels:
 | |
|     whatever: yes
 | |
| `,
 | |
| 	}
 | |
| 	//Check with legacy behavior, that is when no explicit selector then sub-helmfiles inherits from command line selector
 | |
| 	legacyTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "duplicatedOK=yes", expectedReleases: []string{"zipkin", "prometheus", "bar", "bar", "grafana", "postgresql"}, expectErr: false},
 | |
| 		{label: "name=zipkin", expectedReleases: []string{"zipkin", "prometheus", "grafana", "postgresql"}, expectErr: false},
 | |
| 		{label: "name=grafana", expectedReleases: []string{"zipkin", "prometheus", "grafana", "grafana", "postgresql"}, expectErr: false},
 | |
| 		{label: "name=doesnotexists", expectedReleases: []string{"zipkin", "prometheus", "grafana", "postgresql"}, expectErr: false},
 | |
| 	}
 | |
| 	runFilterSubHelmFilesTests(legacyTestcases, files, t, "1st EmbeddedSelectors")
 | |
| 
 | |
| 	//Check with experimental behavior, that is when no explicit selector then sub-helmfiles do no inherit from any selector
 | |
| 	desiredTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "duplicatedOK=yes", expectedReleases: []string{"zipkin", "prometheus", "grafana", "bar", "bar", "grafana", "postgresql"}, expectErr: false},
 | |
| 		{label: "name=doesnotexists", expectedReleases: []string{"zipkin", "prometheus", "grafana", "bar", "bar", "grafana", "postgresql"}, expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	defer env.Patch(t, ExperimentalEnvVar, ExperimentalSelectorExplicit)()
 | |
| 
 | |
| 	runFilterSubHelmFilesTests(desiredTestcases, files, t, "2nd EmbeddedSelectors")
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_InheritedSelectors_3leveldeep(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| releases:
 | |
| - name: mongodb
 | |
|   chart: stable/mongodb
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a.yaml": `
 | |
| helmfiles:
 | |
| - b*.yaml
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| `,
 | |
| 	}
 | |
| 	//Check with legacy behavior, that is when no explicit selector then sub-helmfiles inherits from command line selector
 | |
| 	legacyTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "name!=grafana", expectedReleases: []string{"zipkin", "mongodb"}, expectErr: false},
 | |
| 	}
 | |
| 	runFilterSubHelmFilesTests(legacyTestcases, files, t, "1st 3leveldeep")
 | |
| 
 | |
| 	//Check with experimental behavior, that is when no explicit selector then sub-helmfiles do no inherit from any selector
 | |
| 	desiredTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "name!=grafana", expectedReleases: []string{"grafana", "zipkin", "mongodb"}, expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	defer env.Patch(t, ExperimentalEnvVar, ExperimentalSelectorExplicit)()
 | |
| 
 | |
| 	runFilterSubHelmFilesTests(desiredTestcases, files, t, "2nd 3leveldeep")
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestVisitDesiredStatesWithReleasesFiltered_InheritedSelectors_inherits(t *testing.T) {
 | |
| 	files := map[string]string{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| - path: helmfile.d/a*.yaml
 | |
|   selectors:
 | |
|   - select=foo
 | |
| releases:
 | |
| - name: mongodb
 | |
|   chart: stable/mongodb
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a.yaml": `
 | |
| helmfiles:
 | |
| - path: b*.yaml
 | |
|   selectorsInherited: true
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
|   labels:
 | |
|     select: foo
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
|   labels:
 | |
|     select: foo
 | |
| `,
 | |
| 	}
 | |
| 	//Check with legacy behavior, that is when no explicit selector then sub-helmfiles inherits from command line selector
 | |
| 	legacyTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "name=grafana", expectedReleases: []string{"grafana", "prometheus", "zipkin"}, expectErr: false},
 | |
| 		{label: "select!=foo", expectedReleases: []string{"grafana", "prometheus", "zipkin", "mongodb"}, expectErr: false},
 | |
| 	}
 | |
| 	runFilterSubHelmFilesTests(legacyTestcases, files, t, "1st inherits")
 | |
| 
 | |
| 	//Check with experimental behavior, that is when no explicit selector then sub-helmfiles do no inherit from any selector
 | |
| 	desiredTestcases := []struct {
 | |
| 		label            string
 | |
| 		expectedReleases []string
 | |
| 		expectErr        bool
 | |
| 		errMsg           string
 | |
| 	}{
 | |
| 		{label: "name=grafana", expectedReleases: []string{"grafana", "prometheus", "zipkin", "prometheus", "zipkin"}, expectErr: false},
 | |
| 		{label: "select!=foo", expectedReleases: []string{"grafana", "prometheus", "zipkin", "prometheus", "zipkin", "mongodb"}, expectErr: false},
 | |
| 	}
 | |
| 
 | |
| 	defer env.Patch(t, ExperimentalEnvVar, ExperimentalSelectorExplicit)()
 | |
| 
 | |
| 	runFilterSubHelmFilesTests(desiredTestcases, files, t, "2nd inherits")
 | |
| 
 | |
| }
 | |
| 
 | |
| func runFilterSubHelmFilesTests(testcases []struct {
 | |
| 	label            string
 | |
| 	expectedReleases []string
 | |
| 	expectErr        bool
 | |
| 	errMsg           string
 | |
| }, files map[string]string, t *testing.T, testName string) {
 | |
| 	for _, testcase := range testcases {
 | |
| 		actual := []string{}
 | |
| 
 | |
| 		collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 			for _, r := range st.Releases {
 | |
| 				actual = append(actual, r.Name)
 | |
| 			}
 | |
| 			return []error{}
 | |
| 		}
 | |
| 
 | |
| 		app := appWithFs(&App{
 | |
| 			KubeContext: "default",
 | |
| 			Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 			Namespace:   "",
 | |
| 			Selectors:   []string{testcase.label},
 | |
| 			Env:         "default",
 | |
| 		}, files)
 | |
| 
 | |
| 		err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 			"helmfile.yaml", collectReleases,
 | |
| 		)
 | |
| 		if testcase.expectErr {
 | |
| 			if err == nil {
 | |
| 				t.Errorf("[%s]error expected but not happened for selector %s", testName, testcase.label)
 | |
| 			} else if err.Error() != testcase.errMsg {
 | |
| 				t.Errorf("[%s]unexpected error message: expected=\"%s\", actual=\"%s\"", testName, testcase.errMsg, err.Error())
 | |
| 			}
 | |
| 		} else if !testcase.expectErr && err != nil {
 | |
| 			t.Errorf("[%s]unexpected error for selector %s: %v", testName, testcase.label, err)
 | |
| 		}
 | |
| 		if !reflect.DeepEqual(actual, testcase.expectedReleases) {
 | |
| 			t.Errorf("[%s]unexpected releases for selector %s: expected=%v, actual=%v", testName, testcase.label, testcase.expectedReleases, actual)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| 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{
 | |
| 		"/path/to/helmfile.yaml": `
 | |
| helmfiles:
 | |
| - helmfile.d/a*.yaml
 | |
| - helmfile.d/b*.yaml
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a1.yaml": `
 | |
| releases:
 | |
| - name: zipkin
 | |
|   chart: stable/zipkin
 | |
| `,
 | |
| 		"/path/to/helmfile.d/a2.yaml": `
 | |
| releases:
 | |
| - name: prometheus
 | |
|   chart: stable/prometheus
 | |
| - name: elasticsearch
 | |
|   chart: stable/elasticsearch
 | |
| `,
 | |
| 		"/path/to/helmfile.d/b.yaml": `
 | |
| releases:
 | |
| - name: grafana
 | |
|   chart: stable/grafana
 | |
| `,
 | |
| 	}
 | |
| 
 | |
| 	expected := []string{"grafana", "elasticsearch", "prometheus", "zipkin"}
 | |
| 
 | |
| 	testcases := []struct {
 | |
| 		reverse  bool
 | |
| 		expected []string
 | |
| 	}{
 | |
| 		{reverse: false, expected: []string{"zipkin", "prometheus", "elasticsearch", "grafana"}},
 | |
| 		{reverse: true, expected: []string{"grafana", "elasticsearch", "prometheus", "zipkin"}},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		actual := []string{}
 | |
| 
 | |
| 		collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
 | |
| 			for _, r := range st.Releases {
 | |
| 				actual = append(actual, r.Name)
 | |
| 			}
 | |
| 			return []error{}
 | |
| 		}
 | |
| 		app := appWithFs(&App{
 | |
| 			KubeContext: "default",
 | |
| 			Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 			Reverse:     testcase.reverse,
 | |
| 			Namespace:   "",
 | |
| 			Selectors:   []string{},
 | |
| 			Env:         "default",
 | |
| 		}, files)
 | |
| 		err := app.VisitDesiredStatesWithReleasesFiltered(
 | |
| 			"helmfile.yaml", collectReleases,
 | |
| 		)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("unexpected error: %v", err)
 | |
| 		}
 | |
| 		if !reflect.DeepEqual(testcase.expected, actual) {
 | |
| 			t.Errorf("releases did not match: expected=%v actual=%v", expected, actual)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
 | |
| 	yamlFile := "example/path/to/yaml/file"
 | |
| 	yamlContent := []byte(`releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     stage: pre
 | |
|     foo: bar
 | |
| - name: myrelease1
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     stage: post
 | |
| `)
 | |
| 	readFile := func(filename string) ([]byte, error) {
 | |
| 		if filename != yamlFile {
 | |
| 			return nil, fmt.Errorf("unexpected filename: %s", filename)
 | |
| 		}
 | |
| 		return yamlContent, nil
 | |
| 	}
 | |
| 	app := &App{
 | |
| 		readFile:    readFile,
 | |
| 		glob:        filepath.Glob,
 | |
| 		abs:         filepath.Abs,
 | |
| 		KubeContext: "default",
 | |
| 		Env:         "default",
 | |
| 		Logger:      helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	_, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_Bases(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `bases:
 | |
| - ../base.yaml
 | |
| - ../base.gotmpl
 | |
| 
 | |
| {{ readFile "templates.yaml" }}
 | |
| 
 | |
| releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     stage: pre
 | |
|     foo: bar
 | |
| - name: myrelease1
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     stage: post
 | |
|   <<: *default
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/base.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/1.yaml
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
 | |
| 		"/path/to/base.gotmpl": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/2.yaml
 | |
| 
 | |
| helmDefaults:
 | |
|   tillerNamespace: {{ .Environment.Values.tillerNs }}
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	app := &App{
 | |
| 		readFile:     testFs.ReadFile,
 | |
| 		glob:         testFs.Glob,
 | |
| 		abs:          testFs.Abs,
 | |
| 		fileExistsAt: testFs.FileExistsAt,
 | |
| 		fileExists:   testFs.FileExists,
 | |
| 		KubeContext:  "default",
 | |
| 		Env:          "default",
 | |
| 		Logger:       helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	st, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.TillerNamespace != "TILLER_NS" {
 | |
| 		t.Errorf("unexpected helmDefaults.tillerNamespace: expected=TILLER_NS, got=%s", st.HelmDefaults.TillerNamespace)
 | |
| 	}
 | |
| 
 | |
| 	if *st.Releases[1].MissingFileHandler != "Warn" {
 | |
| 		t.Errorf("unexpected releases[0].missingFileHandler: expected=Warn, got=%s", *st.Releases[1].MissingFileHandler)
 | |
| 	}
 | |
| 
 | |
| 	if st.Releases[1].Values[0] != "{{`{{.Release.Name}}`}}/values.yaml" {
 | |
| 		t.Errorf("unexpected releases[0].missingFileHandler: expected={{`{{.Release.Name}}`}}/values.yaml, got=%s", st.Releases[1].Values[0])
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_MultiPartTemplate(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `bases:
 | |
| - ../base.yaml
 | |
| ---
 | |
| bases:
 | |
| - ../base.gotmpl
 | |
| ---
 | |
| helmDefaults:
 | |
|   kubeContext: {{ .Environment.Values.foo }}
 | |
| ---
 | |
| releases:
 | |
| - name: myrelease0
 | |
|   chart: mychart0
 | |
| ---
 | |
| 
 | |
| {{ readFile "templates.yaml" }}
 | |
| 
 | |
| releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     stage: pre
 | |
|     foo: bar
 | |
| - name: myrelease1
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     stage: post
 | |
|   <<: *default
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/base.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/1.yaml
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
 | |
| 		"/path/to/base.gotmpl": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/2.yaml
 | |
| 
 | |
| helmDefaults:
 | |
|   tillerNamespace: {{ .Environment.Values.tillerNs }}
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	app := &App{
 | |
| 		readFile:   testFs.ReadFile,
 | |
| 		fileExists: testFs.FileExists,
 | |
| 		glob:       testFs.Glob,
 | |
| 		abs:        testFs.Abs,
 | |
| 		Env:        "default",
 | |
| 		Logger:     helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	st, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.TillerNamespace != "TILLER_NS" {
 | |
| 		t.Errorf("unexpected helmDefaults.tillerNamespace: expected=TILLER_NS, got=%s", st.HelmDefaults.TillerNamespace)
 | |
| 	}
 | |
| 	firstRelease := st.Releases[0]
 | |
| 	if firstRelease.Name != "myrelease1" {
 | |
| 		t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", firstRelease.Name)
 | |
| 	}
 | |
| 	secondRelease := st.Releases[1]
 | |
| 	if secondRelease.Name != "myrelease1" {
 | |
| 		t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", secondRelease.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 *secondRelease.MissingFileHandler != "Warn" {
 | |
| 		t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *firstRelease.MissingFileHandler)
 | |
| 	}
 | |
| 
 | |
| 	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" {
 | |
| 		t.Errorf("unexpected helmDefaults.kubeContext: expected=FOO, got=%s", st.HelmDefaults.KubeContext)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_EnvvalsInheritanceToBaseTemplate(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `bases:
 | |
| - ../base.yaml
 | |
| ---
 | |
| bases:
 | |
| # "envvals inheritance"
 | |
| # base.gotmpl should be able to reference environment values defined in the base.yaml and default/1.yaml
 | |
| - ../base.gotmpl
 | |
| ---
 | |
| releases:
 | |
| - name: myrelease0
 | |
|   chart: mychart0
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/base.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/1.yaml
 | |
| `,
 | |
| 		"/path/to/base.gotmpl": `helmDefaults:
 | |
|   kubeContext: {{ .Environment.Values.foo }}
 | |
|   tillerNamespace: {{ .Environment.Values.tillerNs }}
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/1.yaml": `tillerNs: TILLER_NS
 | |
| foo: FOO
 | |
| `,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	app := &App{
 | |
| 		readFile:   testFs.ReadFile,
 | |
| 		fileExists: testFs.FileExists,
 | |
| 		glob:       testFs.Glob,
 | |
| 		abs:        testFs.Abs,
 | |
| 		Env:        "default",
 | |
| 		Logger:     helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	st, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	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)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.KubeContext != "FOO" {
 | |
| 		t.Errorf("unexpected helmDefaults.kubeContext: expected=FOO, got=%s", st.HelmDefaults.KubeContext)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_InlineEnvVals(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `bases:
 | |
| - ../base.yaml
 | |
| ---
 | |
| bases:
 | |
| # "envvals inheritance"
 | |
| # base.gotmpl should be able to reference environment values defined in the base.yaml and default/1.yaml
 | |
| - ../base.gotmpl
 | |
| ---
 | |
| releases:
 | |
| - name: myrelease0
 | |
|   chart: mychart0
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/base.yaml": `environments:
 | |
|   default:
 | |
|     values:
 | |
|     - environments/default/1.yaml
 | |
|     - tillerNs: INLINE_TILLER_NS
 | |
| `,
 | |
| 		"/path/to/base.gotmpl": `helmDefaults:
 | |
|   kubeContext: {{ .Environment.Values.foo }}
 | |
|   tillerNamespace: {{ .Environment.Values.tillerNs }}
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/1.yaml": `tillerNs: TILLER_NS
 | |
| foo: FOO
 | |
| `,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	app := &App{
 | |
| 		readFile:   testFs.ReadFile,
 | |
| 		fileExists: testFs.FileExists,
 | |
| 		glob:       testFs.Glob,
 | |
| 		abs:        testFs.Abs,
 | |
| 		Env:        "default",
 | |
| 		Logger:     helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	st, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.TillerNamespace != "INLINE_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)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.KubeContext != "FOO" {
 | |
| 		t.Errorf("unexpected helmDefaults.kubeContext: expected=FOO, got=%s", st.HelmDefaults.KubeContext)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithNonDefaultEnv(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `bases:
 | |
| - ../base.yaml
 | |
| ---
 | |
| bases:
 | |
| - ../base.gotmpl
 | |
| ---
 | |
| helmDefaults:
 | |
|   kubeContext: {{ .Environment.Values.foo }}
 | |
| ---
 | |
| releases:
 | |
| - name: myrelease0
 | |
|   chart: mychart0
 | |
| ---
 | |
| 
 | |
| {{ readFile "templates.yaml" }}
 | |
| 
 | |
| releases:
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   labels:
 | |
|     stage: pre
 | |
|     foo: bar
 | |
| - name: myrelease1
 | |
|   chart: mychart2
 | |
|   labels:
 | |
|     stage: post
 | |
|   <<: *default
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/base.yaml": `environments:
 | |
|   test:
 | |
|     values:
 | |
|     - environments/default/1.yaml
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
 | |
| 		"/path/to/base.gotmpl": `environments:
 | |
|   test:
 | |
|     values:
 | |
|     - environments/default/2.yaml
 | |
| 
 | |
| helmDefaults:
 | |
|   tillerNamespace: {{ .Environment.Values.tillerNs }}
 | |
| `,
 | |
| 		"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	app := &App{
 | |
| 		readFile:   testFs.ReadFile,
 | |
| 		fileExists: testFs.FileExists,
 | |
| 		glob:       testFs.Glob,
 | |
| 		abs:        testFs.Abs,
 | |
| 		Env:        "test",
 | |
| 		Logger:     helmexec.NewLogger(os.Stderr, "debug"),
 | |
| 	}
 | |
| 	st, err := app.loadDesiredStateFromYaml(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if st.HelmDefaults.TillerNamespace != "TILLER_NS" {
 | |
| 		t.Errorf("unexpected helmDefaults.tillerNamespace: expected=TILLER_NS, got=%s", st.HelmDefaults.TillerNamespace)
 | |
| 	}
 | |
| 
 | |
| 	firstRelease := st.Releases[0]
 | |
| 	if firstRelease.Name != "myrelease1" {
 | |
| 		t.Errorf("unexpected releases[1].name: expected=myrelease1, got=%s", firstRelease.Name)
 | |
| 	}
 | |
| 	secondRelease := st.Releases[1]
 | |
| 	if secondRelease.Name != "myrelease1" {
 | |
| 		t.Errorf("unexpected releases[2].name: expected=myrelease1, got=%s", secondRelease.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 *secondRelease.MissingFileHandler != "Warn" {
 | |
| 		t.Errorf("unexpected releases[2].missingFileHandler: expected=Warn, got=%s", *firstRelease.MissingFileHandler)
 | |
| 	}
 | |
| 
 | |
| 	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" {
 | |
| 		t.Errorf("unexpected helmDefaults.kubeContext: expected=FOO, got=%s", st.HelmDefaults.KubeContext)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithReverse(t *testing.T) {
 | |
| 	yamlFile := "/path/to/yaml/file"
 | |
| 	yamlContent := `
 | |
| {{ readFile "templates.yaml" }}
 | |
| 
 | |
| releases:
 | |
| - name: myrelease0
 | |
|   chart: mychart0
 | |
| - name: myrelease1
 | |
|   chart: mychart1
 | |
|   <<: *default
 | |
| ---
 | |
| 
 | |
| {{ readFile "templates.yaml" }}
 | |
| 
 | |
| releases:
 | |
| - name: myrelease2
 | |
|   chart: mychart2
 | |
| - name: myrelease3
 | |
|   chart: mychart3
 | |
|   <<: *default
 | |
| `
 | |
| 	testFs := state.NewTestFs(map[string]string{
 | |
| 		yamlFile: yamlContent,
 | |
| 		"/path/to/yaml/templates.yaml": `templates:
 | |
|   default: &default
 | |
|     missingFileHandler: Warn
 | |
|     values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
 | |
| `,
 | |
| 	})
 | |
| 	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(yamlFile)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if st.Releases[0].Name != "myrelease3" {
 | |
| 		t.Errorf("unexpected releases[0].name: expected=myrelease3, got=%s", st.Releases[0].Name)
 | |
| 	}
 | |
| 	if st.Releases[1].Name != "myrelease2" {
 | |
| 		t.Errorf("unexpected releases[0].name: expected=myrelease2, got=%s", st.Releases[1].Name)
 | |
| 	}
 | |
| 
 | |
| 	if len(st.Releases) != 2 {
 | |
| 		t.Errorf("unexpected number of releases: expected=2, got=%d", len(st.Releases))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // See https://github.com/roboll/helmfile/issues/623
 | |
| func TestLoadDesiredStateFromYaml_MultiPartTemplate_MergeMapsVariousKeys(t *testing.T) {
 | |
| 	type testcase struct {
 | |
| 		overrideValues interface{}
 | |
| 		expected       string
 | |
| 	}
 | |
| 	testcases := []testcase{
 | |
| 		{map[interface{}]interface{}{"foo": "FOO"}, `FOO`},
 | |
| 		{map[interface{}]interface{}{"foo": map[interface{}]interface{}{"foo": "FOO"}}, `map[foo:FOO]`},
 | |
| 		{map[interface{}]interface{}{"foo": map[string]interface{}{"foo": "FOO"}}, `map[foo:FOO]`},
 | |
| 		{map[interface{}]interface{}{"foo": []interface{}{"foo"}}, `[foo]`},
 | |
| 		{map[interface{}]interface{}{"foo": "FOO"}, `FOO`},
 | |
| 	}
 | |
| 	for i := range testcases {
 | |
| 		tc := testcases[i]
 | |
| 		statePath := "/path/to/helmfile.yaml"
 | |
| 		stateContent := `
 | |
| environments:
 | |
|   default:
 | |
|     values:
 | |
|     - 1.yaml
 | |
|     - 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,
 | |
| 		}
 | |
| 		opts := LoadOpts{
 | |
| 			CalleePath: statePath,
 | |
| 			Environment: state.SubhelmfileEnvironmentSpec{
 | |
| 				OverrideValues: []interface{}{tc.overrideValues},
 | |
| 			},
 | |
| 		}
 | |
| 		st, err := app.loadDesiredStateFromYaml(statePath, opts)
 | |
| 
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("unexpected error: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		if st.Releases[0].Name != tc.expected {
 | |
| 			t.Errorf("unexpected releases[0].name: expected=%s, got=%s", tc.expected, st.Releases[0].Name)
 | |
| 		}
 | |
| 		if st.Releases[0].Chart != "[BAR]" {
 | |
| 			t.Errorf("unexpected releases[0].chart: expected=BAR, got=%s", st.Releases[0].Chart)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadDesiredStateFromYaml_MultiPartTemplate_SprigDictFuncs(t *testing.T) {
 | |
| 	type testcase struct {
 | |
| 		state    string
 | |
| 		expr     string
 | |
| 		expected string
 | |
| 	}
 | |
| 	stateInline := `
 | |
| environments:
 | |
|   default:
 | |
|     values:
 | |
|     - foo: FOO
 | |
|       bar: { "baz": "BAZ" }
 | |
| ---
 | |
| releases:
 | |
| - name: %s
 | |
|   chart: stable/nginx
 | |
| `
 | |
| 	stateExternal := `
 | |
| environments:
 | |
|   default:
 | |
|     values:
 | |
|     - 1.yaml
 | |
|     - 2.yaml
 | |
| ---
 | |
| releases:
 | |
| - name: %s
 | |
|   chart: stable/nginx
 | |
| `
 | |
| 	testcases := []testcase{
 | |
| 		{stateInline, `{{ getOrNil "foo" .Environment.Values }}`, `FOO`},
 | |
| 		{stateInline, `{{ getOrNil "baz" (getOrNil "bar" .Environment.Values) }}`, `BAZ`},
 | |
| 		{stateInline, `{{ if hasKey .Environment.Values "foo" }}{{ .Environment.Values.foo }}{{ end }}`, `FOO`},
 | |
| 		{stateInline, `{{ if hasKey .Environment.Values "bar" }}{{ .Environment.Values.bar.baz }}{{ end }}`, `BAZ`},
 | |
| 		{stateInline, `{{ if (keys .Environment.Values | has "foo") }}{{ .Environment.Values.foo }}{{ end }}`, `FOO`},
 | |
| 		// See https://github.com/roboll/helmfile/issues/624
 | |
| 		// This fails when .Environment.Values.bar is not map[string]interface{}. At the time of #624 it was map[interface{}]interface{}, which sprig's dict funcs don't support.
 | |
| 		{stateInline, `{{ if (keys .Environment.Values | has "bar") }}{{ if (keys .Environment.Values.bar | has "baz") }}{{ .Environment.Values.bar.baz }}{{ end }}{{ end }}`, `BAZ`},
 | |
| 		{stateExternal, `{{ getOrNil "foo" .Environment.Values }}`, `FOO`},
 | |
| 		{stateExternal, `{{ getOrNil "baz" (getOrNil "bar" .Environment.Values) }}`, `BAZ`},
 | |
| 		{stateExternal, `{{ if hasKey .Environment.Values "foo" }}{{ .Environment.Values.foo }}{{ end }}`, `FOO`},
 | |
| 		{stateExternal, `{{ if hasKey .Environment.Values "bar" }}{{ .Environment.Values.bar.baz }}{{ end }}`, `BAZ`},
 | |
| 		{stateExternal, `{{ if (keys .Environment.Values | has "foo") }}{{ .Environment.Values.foo }}{{ end }}`, `FOO`},
 | |
| 		// See https://github.com/roboll/helmfile/issues/624
 | |
| 		{stateExternal, `{{ if (keys .Environment.Values | has "bar") }}{{ if (keys .Environment.Values.bar | has "baz") }}{{ .Environment.Values.bar.baz }}{{ end }}{{ end }}`, `BAZ`},
 | |
| 	}
 | |
| 	for i := range testcases {
 | |
| 		tc := testcases[i]
 | |
| 		statePath := "/path/to/helmfile.yaml"
 | |
| 		stateContent := fmt.Sprintf(tc.state, tc.expr)
 | |
| 		testFs := state.NewTestFs(map[string]string{
 | |
| 			statePath:         stateContent,
 | |
| 			"/path/to/1.yaml": `foo: FOO`,
 | |
| 			"/path/to/2.yaml": `bar: { "baz": "BAZ" }`,
 | |
| 		})
 | |
| 		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 at %d: %v", i, err)
 | |
| 		}
 | |
| 
 | |
| 		if st.Releases[0].Name != tc.expected {
 | |
| 			t.Errorf("unexpected releases[0].name at %d: expected=%s, got=%s", i, tc.expected, st.Releases[0].Name)
 | |
| 		}
 | |
| 	}
 | |
| }
 |