diff --git a/app_test.go b/app_test.go index 8725252b..5b361ec8 100644 --- a/app_test.go +++ b/app_test.go @@ -5,6 +5,7 @@ import ( "github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/state" "os" + "reflect" "testing" ) @@ -302,3 +303,111 @@ releases: } } } + +// See https://github.com/roboll/helmfile/issues/312 +func TestFindAndIterateOverDesiredStates_ReverseOrder(t *testing.T) { + absPaths := map[string]string{ + ".": "/path/to", + "/path/to/helmfile.d": "/path/to/helmfile.d", + } + dirs := map[string]bool{ + "helmfile.d": true, + } + files := map[string]string{ + "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 +`, + } + globMatches := map[string][]string{ + "/path/to/helmfile.d/a*.yaml": []string{"/path/to/helmfile.d/a1.yaml", "/path/to/helmfile.d/a2.yaml"}, + "/path/to/helmfile.d/b*.yaml": []string{"/path/to/helmfile.d/b.yaml"}, + } + fileExistsAt := func(path string) bool { + _, ok := files[path] + return ok + } + directoryExistsAt := func(path string) bool { + _, ok := dirs[path] + return ok + } + readFile := func(filename string) ([]byte, error) { + str, ok := files[filename] + if !ok { + return []byte(nil), fmt.Errorf("no file found: %s", filename) + } + return []byte(str), nil + } + glob := func(pattern string) ([]string, error) { + matches, ok := globMatches[pattern] + if !ok { + return []string(nil), fmt.Errorf("no file matched: %s", pattern) + } + return matches, nil + } + abs := func(path string) (string, error) { + a, ok := absPaths[path] + if !ok { + return "", fmt.Errorf("abs: unexpected path: %s", path) + } + return a, nil + } + + 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 := &app{ + readFile: readFile, + glob: glob, + abs: abs, + fileExistsAt: fileExistsAt, + directoryExistsAt: directoryExistsAt, + kubeContext: "default", + logger: helmexec.NewLogger(os.Stderr, "debug"), + reverse: testcase.reverse, + } + err := app.FindAndIterateOverDesiredStates( + "helmfile.yaml", collectReleases, "", []string{}, "default", + ) + 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) + } + } +} diff --git a/main.go b/main.go index 4bd07fe6..7f035294 100644 --- a/main.go +++ b/main.go @@ -418,7 +418,7 @@ Do you really want to apply? }, }, Action: func(c *cli.Context) error { - return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error { + return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface) []error { purge := c.Bool("purge") args := args.GetArgs(c.String("args"), state) @@ -542,9 +542,14 @@ type app struct { abs func(string) (string, error) fileExistsAt func(string) bool directoryExistsAt func(string) bool + reverse bool } func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error { + return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, false, converge) +} + +func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface) []error) error { fileOrDir := c.GlobalString("file") kubeContext := c.GlobalString("kube-context") namespace := c.GlobalString("namespace") @@ -564,6 +569,7 @@ func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*st directoryExistsAt: directoryExistsAt, kubeContext: kubeContext, logger: logger, + reverse: reverse, } if err := app.FindAndIterateOverDesiredStates(fileOrDir, converge, namespace, selectors, env); err != nil { switch e := err.(type) { @@ -703,25 +709,16 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s if len(st.Helmfiles) > 0 { noMatchInSubHelmfiles := true - for _, globPattern := range st.Helmfiles { - helmfileRelativePattern := st.JoinBase(globPattern) - matches, err := a.glob(helmfileRelativePattern) - if err != nil { - return fmt.Errorf("failed processing %s: %v", globPattern, err) - } - sort.Strings(matches) + for _, m := range st.Helmfiles { + if err := a.FindAndIterateOverDesiredStates(m, converge, namespace, selectors, env); err != nil { + switch err.(type) { + case *noMatchingHelmfileError: - for _, m := range matches { - if err := a.FindAndIterateOverDesiredStates(m, converge, namespace, selectors, env); err != nil { - switch err.(type) { - case *noMatchingHelmfileError: - - default: - return fmt.Errorf("failed processing %s: %v", globPattern, err) - } - } else { - noMatchInSubHelmfiles = false + default: + return fmt.Errorf("failed processing %s: %v", m, err) } + } else { + noMatchInSubHelmfiles = false } } noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles @@ -807,6 +804,27 @@ func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace strin return nil, false, err } + helmfiles := []string{} + for _, globPattern := range st.Helmfiles { + helmfileRelativePattern := st.JoinBase(globPattern) + matches, err := a.glob(helmfileRelativePattern) + if err != nil { + return nil, false, fmt.Errorf("failed processing %s: %v", globPattern, err) + } + sort.Strings(matches) + + helmfiles = append(helmfiles, matches...) + } + st.Helmfiles = helmfiles + + if a.reverse { + rev := func(i, j int) bool { + return j < i + } + sort.Slice(st.Releases, rev) + sort.Slice(st.Helmfiles, rev) + } + if a.kubeContext != "" { if st.Context != "" { log.Printf("err: Cannot use option --kube-context and set attribute context.")