diff --git a/main.go b/main.go index 22c3f912..6ed46a08 100644 --- a/main.go +++ b/main.go @@ -201,7 +201,7 @@ func main() { }, cli.BoolTFlag{ Name: "skip-needs", - Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`, + Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`, }, cli.BoolFlag{ Name: "include-needs", @@ -286,6 +286,10 @@ func main() { Name: "include-needs", Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`, }, + cli.BoolFlag{ + Name: "include-transitive-needs", + Usage: `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`, + }, cli.BoolFlag{ Name: "skip-deps", Usage: `skip running "helm repo update" and "helm dependency build"`, @@ -414,12 +418,16 @@ func main() { }, cli.BoolTFlag{ Name: "skip-needs", - Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`, + Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`, }, cli.BoolFlag{ Name: "include-needs", Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`, }, + cli.BoolFlag{ + Name: "include-transitive-needs", + Usage: `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`, + }, cli.BoolFlag{ Name: "wait", Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`, @@ -487,12 +495,16 @@ func main() { }, cli.BoolTFlag{ Name: "skip-needs", - Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`, + Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`, }, cli.BoolFlag{ Name: "include-needs", Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`, }, + cli.BoolFlag{ + Name: "include-transitive-needs", + Usage: `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`, + }, cli.BoolFlag{ Name: "skip-diff-on-install", Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all", @@ -782,7 +794,11 @@ func (c configImpl) SkipNeeds() bool { } func (c configImpl) IncludeNeeds() bool { - return c.c.Bool("include-needs") + return c.c.Bool("include-needs") || c.IncludeTransitiveNeeds() +} + +func (c configImpl) IncludeTransitiveNeeds() bool { + return c.c.Bool("include-transitive-needs") } // DiffConfig diff --git a/pkg/app/app.go b/pkg/app/app.go index 55183683..ac595578 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -120,7 +120,7 @@ func (a *App) Deps(c DepsConfigProvider) error { } return - }, SetFilter(true)) + }, c.IncludeTransitiveNeeds(), SetFilter(true)) } func (a *App) Repos(c ReposConfigProvider) error { @@ -132,7 +132,7 @@ func (a *App) Repos(c ReposConfigProvider) error { } return - }, SetFilter(true)) + }, c.IncludeTransitiveNeeds(), SetFilter(true)) } func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error { @@ -149,7 +149,7 @@ func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error { } return - }, SetFilter(true)) + }, c.IncludeTransitiveNeeds(), SetFilter(true)) } func (a *App) Diff(c DiffConfigProvider) error { @@ -203,7 +203,7 @@ func (a *App) Diff(c DiffConfigProvider) error { } return matched, criticalErrs - }) + }, false) if err != nil { return err @@ -225,9 +225,6 @@ func (a *App) Diff(c DiffConfigProvider) error { } func (a *App) Template(c TemplateConfigProvider) error { - - opts := []LoadOption{SetRetainValuesFiles(c.SkipCleanup())} - return a.ForEachState(func(run *Run) (ok bool, errs []error) { includeCRDs := c.IncludeCRDs() @@ -249,7 +246,7 @@ func (a *App) Template(c TemplateConfigProvider) error { } return - }, opts...) + }, c.IncludeTransitiveNeeds()) } func (a *App) WriteValues(c WriteValuesConfigProvider) error { @@ -269,7 +266,7 @@ func (a *App) WriteValues(c WriteValuesConfigProvider) error { } return - }, SetFilter(true)) + }, c.IncludeTransitiveNeeds(), SetFilter(true)) } type MultiError struct { @@ -322,7 +319,7 @@ func (a *App) Lint(c LintConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) if err != nil { return err @@ -350,7 +347,7 @@ func (a *App) Fetch(c FetchConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) } func (a *App) Sync(c SyncConfigProvider) error { @@ -358,11 +355,12 @@ func (a *App) Sync(c SyncConfigProvider) error { includeCRDs := !c.SkipCRDs() prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{ - SkipRepos: c.SkipDeps(), - SkipDeps: c.SkipDeps(), - Wait: c.Wait(), - WaitForJobs: c.WaitForJobs(), - IncludeCRDs: &includeCRDs, + SkipRepos: c.SkipDeps(), + SkipDeps: c.SkipDeps(), + Wait: c.Wait(), + WaitForJobs: c.WaitForJobs(), + IncludeCRDs: &includeCRDs, + IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), }, func() { ok, errs = a.sync(run, c) }) @@ -372,7 +370,7 @@ func (a *App) Sync(c SyncConfigProvider) error { } return - }) + }, c.IncludeTransitiveNeeds()) } func (a *App) Apply(c ApplyConfigProvider) error { @@ -410,7 +408,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { } return - }, opts...) + }, c.IncludeTransitiveNeeds(), opts...) if err != nil { return err @@ -439,7 +437,7 @@ func (a *App) Status(c StatusesConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) } func (a *App) Delete(c DeleteConfigProvider) error { @@ -456,7 +454,7 @@ func (a *App) Delete(c DeleteConfigProvider) error { } return - }, SetReverse(true)) + }, false, SetReverse(true)) } func (a *App) Destroy(c DestroyConfigProvider) error { @@ -473,7 +471,7 @@ func (a *App) Destroy(c DestroyConfigProvider) error { } return - }, SetReverse(true)) + }, false, SetReverse(true)) } func (a *App) Test(c TestConfigProvider) error { @@ -496,7 +494,7 @@ func (a *App) Test(c TestConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) } func (a *App) PrintState(c StateConfigProvider) error { @@ -543,7 +541,7 @@ func (a *App) PrintState(c StateConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) } func (a *App) ListReleases(c ListConfigProvider) error { @@ -594,7 +592,7 @@ func (a *App) ListReleases(c ListConfigProvider) error { } return - }, SetFilter(true)) + }, false, SetFilter(true)) if err != nil { return err @@ -883,14 +881,14 @@ var ( } ) -func (a *App) ForEachState(do func(*Run) (bool, []error), o ...LoadOption) error { +func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error { ctx := NewContext() err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { helm := a.getHelm(st) run := NewRun(st, helm, ctx) return do(run) - }, o...) + }, includeTransitiveNeeds, o...) return err } @@ -966,7 +964,7 @@ type Opts struct { DAGEnabled bool } -func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converge func(*state.HelmState) (bool, []error), opt ...LoadOption) error { +func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converge func(*state.HelmState) (bool, []error), includeTransitiveNeeds bool, opt ...LoadOption) error { opts := LoadOpts{ Selectors: a.Selectors, } @@ -1004,16 +1002,17 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converg return processFilteredReleases(st, a.getHelm(st), func(st *state.HelmState) []error { _, err := converge(st) return err - }) + }, + includeTransitiveNeeds) } } return a.visitStates(fileOrDir, opts, f) } -func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, converge func(st *state.HelmState) []error) (bool, []error) { +func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, converge func(st *state.HelmState) []error, includeTransitiveNeeds bool) (bool, []error) { if len(st.Selectors) > 0 { - err := st.FilterReleases() + err := st.FilterReleases(includeTransitiveNeeds) if err != nil { return false, []error{err} } @@ -1066,11 +1065,11 @@ func checkDuplicates(helm helmexec.Interface, st *state.HelmState, releases []st return nil } -func (a *App) Wrap(converge func(*state.HelmState, helmexec.Interface) []error) func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { - return func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { +func (a *App) Wrap(converge func(*state.HelmState, helmexec.Interface) []error) func(st *state.HelmState, helm helmexec.Interface, includeTransitiveNeeds bool) (bool, []error) { + return func(st *state.HelmState, helm helmexec.Interface, includeTransitiveNeeds bool) (bool, []error) { return processFilteredReleases(st, helm, func(st *state.HelmState) []error { return converge(st, helm) - }) + }, includeTransitiveNeeds) } } @@ -1144,8 +1143,8 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri return files, nil } -func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseSpec, error) { - selected, err := r.state.GetSelectedReleasesWithOverrides() +func (a *App) getSelectedReleases(r *Run, includeTransitiveNeeds bool) ([]state.ReleaseSpec, []state.ReleaseSpec, error) { + selected, err := r.state.GetSelectedReleasesWithOverrides(includeTransitiveNeeds) if err != nil { return nil, nil, err } @@ -1159,12 +1158,7 @@ func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseS needed := map[string]struct{}{} for _, r := range selected { - for _, id := range r.Needs { - // Avoids duplicating a release that is selected AND also needed by another selected release - if _, ok := selectedIds[id]; !ok { - needed[id] = struct{}{} - } - } + collectNeeds(r, selectedIds, needed, allReleases, includeTransitiveNeeds) } var releases []state.ReleaseSpec @@ -1192,11 +1186,29 @@ func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseS return selected, releases, nil } +func collectNeeds(release state.ReleaseSpec, selectedIds map[string]struct{}, needed map[string]struct{}, allReleases []state.ReleaseSpec, includeTransitiveNeeds bool) { + for _, id := range release.Needs { + // Avoids duplicating a release that is selected AND also needed by another selected release + if _, ok := selectedIds[id]; !ok { + needed[id] = struct{}{} + if includeTransitiveNeeds { + releaseParts := strings.Split(id, "/") + releaseName := releaseParts[len(releaseParts)-1] + for _, r := range allReleases { + if r.Name == releaseName { + collectNeeds(r, selectedIds, needed, allReleases, includeTransitiveNeeds) + } + } + } + } + } +} + func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { st := r.state helm := r.helm - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) if err != nil { return false, false, []error{err} } @@ -1210,7 +1222,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { // See https://github.com/roboll/helmfile/issues/1818 for more context. st.Releases = selectedAndNeededReleases - plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds()}) + plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}) if err != nil { return false, false, []error{err} } @@ -1320,7 +1332,7 @@ Do you really want to apply? // We upgrade releases by traversing the DAG if len(releasesToBeUpdated) > 0 { - _, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, Reverse: false, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { + _, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, Reverse: false, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { var rs []state.ReleaseSpec for _, r := range subst.Releases { @@ -1357,7 +1369,7 @@ func (a *App) delete(r *Run, purge bool, c DestroyConfigProvider) (bool, []error affectedReleases := state.AffectedReleases{} - toSync, _, err := a.getSelectedReleases(r) + toSync, _, err := a.getSelectedReleases(r, false) if err != nil { return false, []error{err} } @@ -1429,7 +1441,7 @@ Do you really want to delete? func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) { st := r.state - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, false) if err != nil { return nil, false, false, []error{err} } @@ -1450,7 +1462,7 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) st.Releases = selectedAndNeededReleases - plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds()}) + plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: false}) if err != nil { return nil, false, false, []error{err} } @@ -1485,7 +1497,7 @@ func (a *App) lint(r *Run, c LintConfigProvider) (bool, []error, []error) { allReleases := st.GetReleasesWithOverrides() - selectedReleases, _, err := a.getSelectedReleases(r) + selectedReleases, _, err := a.getSelectedReleases(r, false) if err != nil { return false, nil, []error{err} } @@ -1553,7 +1565,7 @@ func (a *App) status(r *Run, c StatusesConfigProvider) (bool, []error) { allReleases := st.GetReleasesWithOverrides() - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, false) if err != nil { return false, []error{err} } @@ -1603,7 +1615,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { st := r.state helm := r.helm - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) if err != nil { return false, []error{err} } @@ -1617,7 +1629,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { // See https://github.com/roboll/helmfile/issues/1818 for more context. st.Releases = selectedAndNeededReleases - batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), SkipNeeds: c.SkipNeeds()}) + batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), SkipNeeds: c.SkipNeeds()}) if err != nil { return false, []error{err} } @@ -1727,7 +1739,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { } if len(releasesToUpdate) > 0 { - _, syncErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { + _, syncErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { var rs []state.ReleaseSpec for _, r := range subst.Releases { @@ -1759,7 +1771,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { st := r.state helm := r.helm - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) if err != nil { return false, []error{err} } @@ -1773,7 +1785,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { // See https://github.com/roboll/helmfile/issues/1818 for more context. st.Releases = selectedAndNeededReleases - batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), SkipNeeds: !c.IncludeNeeds()}) + batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), SkipNeeds: !c.IncludeNeeds()}) if err != nil { return false, []error{err} } @@ -1813,7 +1825,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { } if len(toRender) > 0 { - _, templateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toRender, Reverse: false, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { + _, templateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toRender, Reverse: false, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { opts := &state.TemplateOpts{ Set: c.Set(), IncludeCRDs: c.IncludeCRDs(), @@ -1837,7 +1849,7 @@ func (a *App) test(r *Run, c TestConfigProvider) []error { st := r.state - toTest, _, err := a.getSelectedReleases(r) + toTest, _, err := a.getSelectedReleases(r, false) if err != nil { return []error{err} } @@ -1859,7 +1871,7 @@ func (a *App) writeValues(r *Run, c WriteValuesConfigProvider) (bool, []error) { st := r.state helm := r.helm - toRender, _, err := a.getSelectedReleases(r) + toRender, _, err := a.getSelectedReleases(r, false) if err != nil { return false, []error{err} } diff --git a/pkg/app/app_apply_nokubectx_test.go b/pkg/app/app_apply_nokubectx_test.go index a4a9e224..ac1b7d4b 100644 --- a/pkg/app/app_apply_nokubectx_test.go +++ b/pkg/app/app_apply_nokubectx_test.go @@ -17,8 +17,9 @@ import ( func TestApply_3(t *testing.T) { type fields struct { - skipNeeds bool - includeNeeds bool + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool } type testcase struct { @@ -109,11 +110,12 @@ func TestApply_3(t *testing.T) { syncErr := app.Apply(applyConfig{ // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. - concurrency: tc.concurrency, - logger: logger, - skipDiffOnInstall: tc.skipDiffOnInstall, - skipNeeds: tc.fields.skipNeeds, - includeNeeds: tc.fields.includeNeeds, + concurrency: tc.concurrency, + logger: logger, + skipDiffOnInstall: tc.skipDiffOnInstall, + skipNeeds: tc.fields.skipNeeds, + includeNeeds: tc.fields.includeNeeds, + includeTransitiveNeeds: tc.fields.includeTransitiveNeeds, }) var gotErr string diff --git a/pkg/app/app_apply_test.go b/pkg/app/app_apply_test.go index 7a16a907..82ed9a06 100644 --- a/pkg/app/app_apply_test.go +++ b/pkg/app/app_apply_test.go @@ -17,8 +17,9 @@ import ( func TestApply_2(t *testing.T) { type fields struct { - skipNeeds bool - includeNeeds bool + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool } type testcase struct { @@ -109,11 +110,12 @@ func TestApply_2(t *testing.T) { syncErr := app.Apply(applyConfig{ // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. - concurrency: tc.concurrency, - logger: logger, - skipDiffOnInstall: tc.skipDiffOnInstall, - skipNeeds: tc.fields.skipNeeds, - includeNeeds: tc.fields.includeNeeds, + concurrency: tc.concurrency, + logger: logger, + skipDiffOnInstall: tc.skipDiffOnInstall, + skipNeeds: tc.fields.skipNeeds, + includeNeeds: tc.fields.includeNeeds, + includeTransitiveNeeds: tc.fields.includeTransitiveNeeds, }) var gotErr string @@ -960,6 +962,128 @@ NAME CHART VERSION external-secrets incubator/raw my-release incubator/raw +`, + }) + }) + + t.Run("include-transitive-needs=true", func(t *testing.T) { + check(t, testcase{ + fields: fields{ + skipNeeds: false, + includeNeeds: true, + includeTransitiveNeeds: true, + }, + error: ``, + files: map[string]string{ + "/path/to/helmfile.yaml": ` +{{ $mark := "a" }} + +releases: +- name: serviceA + chart: my/chart + needs: + - serviceB + +- name: serviceB + chart: my/chart + needs: + - serviceC + +- name: serviceC + chart: my/chart + +- name: serviceD + chart: my/chart +`, + }, + selectors: []string{"name=serviceA"}, + upgraded: []exectest.Release{}, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "serviceA", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "serviceB", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "serviceC", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + // as we check for log output, set concurrency to 1 to avoid non-deterministic test result + concurrency: 1, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: serviceA + 5: chart: my/chart + 6: needs: + 7: - serviceB + 8: + 9: - name: serviceB +10: chart: my/chart +11: needs: +12: - serviceC +13: +14: - name: serviceC +15: chart: my/chart +16: +17: - name: serviceD +18: chart: my/chart +19: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: serviceA + 5: chart: my/chart + 6: needs: + 7: - serviceB + 8: + 9: - name: serviceB +10: chart: my/chart +11: needs: +12: - serviceC +13: +14: - name: serviceC +15: chart: my/chart +16: +17: - name: serviceD +18: chart: my/chart +19: + +merged environment: &{default map[] map[]} +3 release(s) matching name=serviceA found in helmfile.yaml + +Affected releases are: + serviceA (my/chart) UPDATED + serviceB (my/chart) UPDATED + serviceC (my/chart) UPDATED + +processing 3 groups of releases in this order: +GROUP RELEASES +1 default//serviceC +2 default//serviceB +3 default//serviceA + +processing releases in group 1/3: default//serviceC +getting deployed release version failed:unexpected list key: {^serviceC$ --kube-contextdefault--deleting--deployed--failed--pending} +processing releases in group 2/3: default//serviceB +getting deployed release version failed:unexpected list key: {^serviceB$ --kube-contextdefault--deleting--deployed--failed--pending} +processing releases in group 3/3: default//serviceA +getting deployed release version failed:unexpected list key: {^serviceA$ --kube-contextdefault--deleting--deployed--failed--pending} + +UPDATED RELEASES: +NAME CHART VERSION +serviceC my/chart +serviceB my/chart +serviceA my/chart + `, }) }) diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go index d36dc379..25103cca 100644 --- a/pkg/app/app_sync_test.go +++ b/pkg/app/app_sync_test.go @@ -17,8 +17,9 @@ import ( func TestSync(t *testing.T) { type fields struct { - skipNeeds bool - includeNeeds bool + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool } type testcase struct { @@ -107,11 +108,12 @@ func TestSync(t *testing.T) { syncErr := app.Sync(applyConfig{ // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. - concurrency: tc.concurrency, - logger: logger, - skipDiffOnInstall: tc.skipDiffOnInstall, - skipNeeds: tc.fields.skipNeeds, - includeNeeds: tc.fields.includeNeeds, + concurrency: tc.concurrency, + logger: logger, + skipDiffOnInstall: tc.skipDiffOnInstall, + skipNeeds: tc.fields.skipNeeds, + includeNeeds: tc.fields.includeNeeds, + includeTransitiveNeeds: tc.fields.includeTransitiveNeeds, }) var gotErr string @@ -412,6 +414,122 @@ kubernetes-external-secrets incubator/raw external-secrets incubator/raw my-release incubator/raw +`, + }) + }) + + t.Run("include-transitive-needs=true", func(t *testing.T) { + check(t, testcase{ + fields: fields{ + skipNeeds: false, + includeTransitiveNeeds: true, + }, + error: ``, + files: map[string]string{ + "/path/to/helmfile.yaml": ` +{{ $mark := "a" }} + +releases: +- name: serviceA + chart: my/chart + needs: + - serviceB + +- name: serviceB + chart: my/chart + needs: + - serviceC + +- name: serviceC + chart: my/chart + +- name: serviceD + chart: my/chart +`, + }, + selectors: []string{"name=serviceA"}, + upgraded: []exectest.Release{}, + // as we check for log output, set concurrency to 1 to avoid non-deterministic test result + concurrency: 1, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: serviceA + 5: chart: my/chart + 6: needs: + 7: - serviceB + 8: + 9: - name: serviceB +10: chart: my/chart +11: needs: +12: - serviceC +13: +14: - name: serviceC +15: chart: my/chart +16: +17: - name: serviceD +18: chart: my/chart +19: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: serviceA + 5: chart: my/chart + 6: needs: + 7: - serviceB + 8: + 9: - name: serviceB +10: chart: my/chart +11: needs: +12: - serviceC +13: +14: - name: serviceC +15: chart: my/chart +16: +17: - name: serviceD +18: chart: my/chart +19: + +merged environment: &{default map[] map[]} +3 release(s) matching name=serviceA found in helmfile.yaml + +Affected releases are: + serviceA (my/chart) UPDATED + serviceB (my/chart) UPDATED + serviceC (my/chart) UPDATED + +processing 3 groups of releases in this order: +GROUP RELEASES +1 default//serviceC +2 default//serviceB +3 default//serviceA + +processing releases in group 1/3: default//serviceC +getting deployed release version failed:unexpected list key: {^serviceC$ --kube-contextdefault--deleting--deployed--failed--pending} +processing releases in group 2/3: default//serviceB +getting deployed release version failed:unexpected list key: {^serviceB$ --kube-contextdefault--deleting--deployed--failed--pending} +processing releases in group 3/3: default//serviceA +getting deployed release version failed:unexpected list key: {^serviceA$ --kube-contextdefault--deleting--deployed--failed--pending} + +UPDATED RELEASES: +NAME CHART VERSION +serviceC my/chart +serviceB my/chart +serviceA my/chart + `, }) }) @@ -419,8 +537,9 @@ my-release incubator/raw t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) { check(t, testcase{ fields: fields{ - skipNeeds: false, - includeNeeds: true, + skipNeeds: false, + includeNeeds: true, + includeTransitiveNeeds: false, }, error: ``, files: map[string]string{ @@ -561,8 +680,9 @@ kubernetes-external-secrets t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) { check(t, testcase{ fields: fields{ - skipNeeds: false, - includeNeeds: true, + skipNeeds: false, + includeTransitiveNeeds: false, + includeNeeds: true, }, error: ``, files: map[string]string{ diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 8ff6794f..47d4c07e 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -106,6 +106,7 @@ releases: err := app.ForEachState( noop, + false, SetFilter(true), ) if err != nil { @@ -157,6 +158,7 @@ BAZ: 4 err := app.ForEachState( Noop, + false, SetFilter(true), ) if err != nil { @@ -198,6 +200,7 @@ releases: err := app.ForEachState( Noop, + false, SetFilter(true), ) if err == nil { @@ -242,6 +245,7 @@ releases: err := app.ForEachState( Noop, + false, SetFilter(true), ) if err != nil { @@ -293,6 +297,7 @@ releases: err := app.ForEachState( Noop, + false, SetFilter(true), ) if testcase.expectErr && err == nil { @@ -359,6 +364,7 @@ releases: err := app.ForEachState( Noop, + false, SetFilter(true), ) if testcase.expectErr && err == nil { @@ -413,6 +419,7 @@ releases: err := app.ForEachState( Noop, + false, SetFilter(true), ) if testcase.expectErr && err == nil { @@ -531,6 +538,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if testcase.expectErr { @@ -774,6 +782,7 @@ func runFilterSubHelmFilesTests(testcases []struct { err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if testcase.expectErr { @@ -869,6 +878,7 @@ tillerNs: INLINE_TILLER_NS_2 err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -970,6 +980,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetReverse(testcase.reverse), SetFilter(true), ) @@ -1035,6 +1046,7 @@ bar: "bar1" err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if err != nil { @@ -1157,6 +1169,7 @@ x: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if err != nil { @@ -1209,6 +1222,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if err != nil { @@ -1266,6 +1280,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if err != nil { @@ -1316,6 +1331,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -1364,6 +1380,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -1407,6 +1424,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -1450,6 +1468,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -1497,6 +1516,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) @@ -2246,8 +2266,9 @@ type configImpl struct { skipCRDs bool skipDeps bool - skipNeeds bool - includeNeeds bool + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool } func (a configImpl) Selectors() []string { @@ -2290,6 +2311,10 @@ func (c configImpl) IncludeNeeds() bool { return c.includeNeeds } +func (c configImpl) IncludeTransitiveNeeds() bool { + return c.includeTransitiveNeeds +} + func (c configImpl) OutputDir() string { return "output/subdir" } @@ -2315,30 +2340,31 @@ func (c configImpl) Output() string { } type applyConfig struct { - args string - values []string - retainValuesFiles bool - set []string - validate bool - skipCleanup bool - skipCRDs bool - skipDeps bool - skipNeeds bool - includeNeeds bool - includeTests bool - suppressSecrets bool - showSecrets bool - suppressDiff bool - noColor bool - context int - diffOutput string - concurrency int - detailedExitcode bool - interactive bool - skipDiffOnInstall bool - logger *zap.SugaredLogger - wait bool - waitForJobs bool + args string + values []string + retainValuesFiles bool + set []string + validate bool + skipCleanup bool + skipCRDs bool + skipDeps bool + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool + includeTests bool + suppressSecrets bool + showSecrets bool + suppressDiff bool + noColor bool + context int + diffOutput string + concurrency int + detailedExitcode bool + interactive bool + skipDiffOnInstall bool + logger *zap.SugaredLogger + wait bool + waitForJobs bool } func (a applyConfig) Args() string { @@ -2385,6 +2411,10 @@ func (c applyConfig) IncludeNeeds() bool { return c.includeNeeds } +func (c applyConfig) IncludeTransitiveNeeds() bool { + return c.includeTransitiveNeeds +} + func (a applyConfig) IncludeTests() bool { return a.includeTests } @@ -2438,13 +2468,18 @@ func (a applyConfig) SkipDiffOnInstall() bool { } type depsConfig struct { - skipRepos bool + skipRepos bool + includeTransitiveNeeds bool } func (d depsConfig) SkipRepos() bool { return d.skipRepos } +func (d depsConfig) IncludeTransitiveNeeds() bool { + return d.includeTransitiveNeeds +} + func (d depsConfig) Args() string { return "" } @@ -4482,7 +4517,8 @@ See https://github.com/roboll/helmfile/issues/878 for more information. }, tc.files) depsErr := app.Deps(depsConfig{ - skipRepos: false, + skipRepos: false, + includeTransitiveNeeds: false, }) if tc.error == "" && depsErr != nil { @@ -4777,6 +4813,7 @@ releases: err := app.ForEachState( collectReleases, + false, SetFilter(true), ) if err != nil { diff --git a/pkg/app/config.go b/pkg/app/config.go index b9801ba0..3b518462 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -23,15 +23,18 @@ type DeprecatedChartsConfigProvider interface { concurrencyConfig loggingConfig + IncludeTransitiveNeeds() bool } type DepsConfigProvider interface { Args() string SkipRepos() bool + IncludeTransitiveNeeds() bool } type ReposConfigProvider interface { Args() string + IncludeTransitiveNeeds() bool } type ApplyConfigProvider interface { @@ -63,6 +66,7 @@ type ApplyConfigProvider interface { SkipNeeds() bool IncludeNeeds() bool + IncludeTransitiveNeeds() bool concurrencyConfig interactive @@ -81,6 +85,7 @@ type SyncConfigProvider interface { SkipNeeds() bool IncludeNeeds() bool + IncludeTransitiveNeeds() bool concurrencyConfig loggingConfig @@ -174,6 +179,7 @@ type TemplateConfigProvider interface { OutputDir() string IncludeCRDs() bool IncludeNeeds() bool + IncludeTransitiveNeeds() bool concurrencyConfig } @@ -183,6 +189,7 @@ type WriteValuesConfigProvider interface { Set() []string OutputFileTemplate() string SkipDeps() bool + IncludeTransitiveNeeds() bool } type StatusesConfigProvider interface { diff --git a/pkg/app/destroy_nokubectx_test.go b/pkg/app/destroy_nokubectx_test.go index f248568b..99902a18 100644 --- a/pkg/app/destroy_nokubectx_test.go +++ b/pkg/app/destroy_nokubectx_test.go @@ -103,8 +103,9 @@ func TestDestroy_2(t *testing.T) { destroyErr := app.Destroy(destroyConfig{ // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. - concurrency: tc.concurrency, - logger: logger, + concurrency: tc.concurrency, + logger: logger, + includeTransitiveNeeds: false, }) if tc.error == "" && destroyErr != nil { diff --git a/pkg/app/destroy_test.go b/pkg/app/destroy_test.go index ac969e69..639fadd7 100644 --- a/pkg/app/destroy_test.go +++ b/pkg/app/destroy_test.go @@ -23,11 +23,12 @@ const ( ) type destroyConfig struct { - args string - concurrency int - interactive bool - skipDeps bool - logger *zap.SugaredLogger + args string + concurrency int + interactive bool + skipDeps bool + logger *zap.SugaredLogger + includeTransitiveNeeds bool } func (d destroyConfig) Args() string { @@ -50,6 +51,10 @@ func (d destroyConfig) SkipDeps() bool { return d.skipDeps } +func (d destroyConfig) IncludeTransitiveNeeds() bool { + return d.includeTransitiveNeeds +} + func TestDestroy(t *testing.T) { type testcase struct { helm3 bool diff --git a/pkg/app/run.go b/pkg/app/run.go index f3c6ad6b..02ba3421 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -98,7 +98,7 @@ func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepare func (r *Run) Deps(c DepsConfigProvider) []error { r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) - return r.state.UpdateDeps(r.helm) + return r.state.UpdateDeps(r.helm, c.IncludeTransitiveNeeds()) } func (r *Run) Repos(c ReposConfigProvider) error { diff --git a/pkg/state/selector_test.go b/pkg/state/selector_test.go index 49439369..80cd4afe 100644 --- a/pkg/state/selector_test.go +++ b/pkg/state/selector_test.go @@ -1,8 +1,9 @@ package state import ( - "github.com/google/go-cmp/cmp" "testing" + + "github.com/google/go-cmp/cmp" ) func TestSelectReleasesWithOverrides(t *testing.T) { @@ -79,7 +80,76 @@ func TestSelectReleasesWithOverrides(t *testing.T) { for _, tc := range testcases { state.Selectors = tc.selector - rs, err := state.GetSelectedReleasesWithOverrides() + rs, err := state.GetSelectedReleasesWithOverrides(false) + if err != nil { + t.Fatalf("%s %s: %v", tc.selector, tc.subject, err) + } + + var got []string + + for _, r := range rs { + got = append(got, r.Name) + } + + if d := cmp.Diff(tc.want, got); d != "" { + t.Errorf("%s %s: %s", tc.selector, tc.subject, d) + } + } +} + +func TestSelectReleasesWithOverridesWithIncludedTransitives(t *testing.T) { + type testcase struct { + subject string + selector []string + want []string + includeTransitiveNeeds bool + } + + testcases := []testcase{ + { + subject: "include transitives", + selector: []string{"name=serviceA"}, + want: []string{"serviceA"}, + includeTransitiveNeeds: false, + }, + { + subject: "include transitives", + selector: []string{"name=serviceA"}, + want: []string{"serviceA", "serviceB", "serviceC"}, + includeTransitiveNeeds: true, + }, + } + + example := []byte(`releases: +- name: serviceA + namespace: default + chart: stable/testchart + needs: + - serviceB +- name: serviceB + namespace: default + chart: stable/testchart + needs: + - serviceC +- name: serviceC + namespace: default + chart: stable/testchart +- name: serviceD + namespace: default + chart: stable/testchart +`) + + state := stateTestEnv{ + Files: map[string]string{ + "/helmfile.yaml": string(example), + }, + WorkDir: "/", + }.MustLoadState(t, "/helmfile.yaml", "default") + + for _, tc := range testcases { + state.Selectors = tc.selector + + rs, err := state.GetSelectedReleasesWithOverrides(tc.includeTransitiveNeeds) if err != nil { t.Fatalf("%s %s: %v", tc.selector, tc.subject, err) } diff --git a/pkg/state/state.go b/pkg/state/state.go index 7acfcabe..74622238 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -398,36 +398,6 @@ type RepoUpdater interface { RegistryLogin(name string, username string, password string) error } -// getRepositoriesToSync returns the names of repositories to be updated -func (st *HelmState) getRepositoriesToSync() (map[string]bool, error) { - releases, err := st.GetSelectedReleasesWithOverrides() - if err != nil { - return nil, err - } - - repositoriesToUpdate := map[string]bool{} - - if len(releases) == 0 { - for _, repo := range st.Repositories { - repositoriesToUpdate[repo.Name] = true - } - - return repositoriesToUpdate, nil - } - - for _, release := range releases { - if release.Installed == nil || *release.Installed { - chart := strings.Split(release.Chart, "/") - if len(chart) == 1 { - continue - } - repositoriesToUpdate[chart[0]] = true - } - } - - return repositoriesToUpdate, nil -} - func (st *HelmState) SyncRepos(helm RepoUpdater, shouldSkip map[string]bool) ([]string, error) { var updated []string @@ -968,11 +938,12 @@ type ChartPrepareOptions struct { SkipCleanup bool // Validate is a helm-3-only option. When it is set to true, it configures chartify to pass --validate to helm-template run by it. // It's required when one of your chart relies on Capabilities.APIVersions in a template - Validate bool - IncludeCRDs *bool - Wait bool - WaitForJobs bool - OutputDir string + Validate bool + IncludeCRDs *bool + Wait bool + WaitForJobs bool + OutputDir string + IncludeTransitiveNeeds bool } type chartPrepareResult struct { @@ -1026,7 +997,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre // This and releasesNeedCharts ensures that we run operations like helm-dep-build and prepare-hook calls only on // releases that are (1) selected by the selectors and (2) to be installed. - selected, err = st.GetSelectedReleasesWithOverrides() + selected, err = st.GetSelectedReleasesWithOverrides(opts.IncludeTransitiveNeeds) if err != nil { return nil, []error{err} } @@ -2047,16 +2018,16 @@ func (st *HelmState) GetReleasesWithOverrides() []ReleaseSpec { return rs } -func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { +func (st *HelmState) SelectReleasesWithOverrides(includeTransitiveNeeds bool) ([]Release, error) { values := st.Values() - rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values) + rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values, includeTransitiveNeeds) if err != nil { return nil, err } return rs, nil } -func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabels map[string]string, values map[string]interface{}) ([]Release, error) { +func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabels map[string]string, values map[string]interface{}, includeTransitiveNeeds bool) ([]Release, error) { var filteredReleases []Release filters := []ReleaseFilter{} for _, label := range selectors { @@ -2113,12 +2084,52 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabe } filteredReleases = append(filteredReleases, res) } - + if includeTransitiveNeeds { + unmarkNeedsAndTransitives(filteredReleases, releases) + } return filteredReleases, nil } -func (st *HelmState) GetSelectedReleasesWithOverrides() ([]ReleaseSpec, error) { - filteredReleases, err := st.SelectReleasesWithOverrides() +func unmarkNeedsAndTransitives(filteredReleases []Release, allReleases []ReleaseSpec) { + needsWithTranstives := collectAllNeedsWithTransitives(filteredReleases, allReleases) + unmarkReleases(needsWithTranstives, filteredReleases) +} + +func collectAllNeedsWithTransitives(filteredReleases []Release, allReleases []ReleaseSpec) map[string]struct{} { + needsWithTranstives := map[string]struct{}{} + for _, r := range filteredReleases { + if !r.Filtered { + collectNeedsWithTransitives(r.ReleaseSpec, allReleases, needsWithTranstives) + } + } + return needsWithTranstives +} + +func unmarkReleases(toUnmark map[string]struct{}, releases []Release) { + for i, r := range releases { + if _, ok := toUnmark[ReleaseToID(&r.ReleaseSpec)]; ok { + releases[i].Filtered = false + } + } +} + +func collectNeedsWithTransitives(release ReleaseSpec, allReleases []ReleaseSpec, needsWithTranstives map[string]struct{}) { + for _, id := range release.Needs { + if _, exists := needsWithTranstives[id]; !exists { + needsWithTranstives[id] = struct{}{} + releaseParts := strings.Split(id, "/") + releaseName := releaseParts[len(releaseParts)-1] + for _, r := range allReleases { + if r.Name == releaseName { + collectNeedsWithTransitives(r, allReleases, needsWithTranstives) + } + } + } + } +} + +func (st *HelmState) GetSelectedReleasesWithOverrides(includeTransitiveNeeds bool) ([]ReleaseSpec, error) { + filteredReleases, err := st.SelectReleasesWithOverrides(includeTransitiveNeeds) if err != nil { return nil, err } @@ -2133,8 +2144,8 @@ func (st *HelmState) GetSelectedReleasesWithOverrides() ([]ReleaseSpec, error) { } // FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile. -func (st *HelmState) FilterReleases() error { - releases, err := st.GetSelectedReleasesWithOverrides() +func (st *HelmState) FilterReleases(includeTransitiveNeeds bool) error { + releases, err := st.GetSelectedReleasesWithOverrides(includeTransitiveNeeds) if err != nil { return err } @@ -2209,7 +2220,7 @@ func (st *HelmState) ResolveDeps() (*HelmState, error) { } // UpdateDeps wrapper for updating dependencies on the releases -func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error { +func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds bool) []error { var selected []ReleaseSpec if len(st.Selectors) > 0 { @@ -2217,7 +2228,7 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error { // This and releasesNeedCharts ensures that we run operations like helm-dep-build and prepare-hook calls only on // releases that are (1) selected by the selectors and (2) to be installed. - selected, err = st.GetSelectedReleasesWithOverrides() + selected, err = st.GetSelectedReleasesWithOverrides(includeTransitiveNeeds) if err != nil { return []error{err} } diff --git a/pkg/state/state_run.go b/pkg/state/state_run.go index e7ea4f4d..d4695a93 100644 --- a/pkg/state/state_run.go +++ b/pkg/state/state_run.go @@ -100,14 +100,15 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int, } type PlanOptions struct { - Reverse bool - IncludeNeeds bool - SkipNeeds bool - SelectedReleases []ReleaseSpec + Reverse bool + IncludeNeeds bool + IncludeTransitiveNeeds bool + SkipNeeds bool + SelectedReleases []ReleaseSpec } func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) { - marked, err := st.SelectReleasesWithOverrides() + marked, err := st.SelectReleasesWithOverrides(opts.IncludeTransitiveNeeds) if err != nil { return nil, err } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index cbf30e69..edb3c99f 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -1848,7 +1848,7 @@ generated: 2019-05-16T15:42:45.50486+09:00 }) fs.Cwd = basePath state = injectFs(state, fs) - errs := state.UpdateDeps(helm) + errs := state.UpdateDeps(helm, false) want := []string{"/example", "./example", generatedDir} if !reflect.DeepEqual(helm.Charts, want) { @@ -2141,7 +2141,7 @@ func TestHelmState_NoReleaseMatched(t *testing.T) { RenderedValues: map[string]interface{}{}, } state.Selectors = []string{tt.labels} - errs := state.FilterReleases() + errs := state.FilterReleases(false) if (errs != nil) != tt.wantErr { t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) return