Add support for transitive dependencies. (#1983)

Co-authored-by: Peter Aichinger <petera@topdesk.com>
This commit is contained in:
pjotre86 2021-10-20 10:55:08 +02:00 committed by GitHub
parent 9a0ce53608
commit 77e6268bcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 583 additions and 177 deletions

24
main.go
View File

@ -201,7 +201,7 @@ func main() {
}, },
cli.BoolTFlag{ cli.BoolTFlag{
Name: "skip-needs", 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{ cli.BoolFlag{
Name: "include-needs", Name: "include-needs",
@ -286,6 +286,10 @@ func main() {
Name: "include-needs", 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`, 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{ cli.BoolFlag{
Name: "skip-deps", Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`, Usage: `skip running "helm repo update" and "helm dependency build"`,
@ -414,12 +418,16 @@ func main() {
}, },
cli.BoolTFlag{ cli.BoolTFlag{
Name: "skip-needs", 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{ cli.BoolFlag{
Name: "include-needs", 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`, 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{ cli.BoolFlag{
Name: "wait", Name: "wait",
Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`, Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`,
@ -487,12 +495,16 @@ func main() {
}, },
cli.BoolTFlag{ cli.BoolTFlag{
Name: "skip-needs", 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{ cli.BoolFlag{
Name: "include-needs", 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`, 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{ cli.BoolFlag{
Name: "skip-diff-on-install", 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", 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 { 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 // DiffConfig

View File

@ -120,7 +120,7 @@ func (a *App) Deps(c DepsConfigProvider) error {
} }
return return
}, SetFilter(true)) }, c.IncludeTransitiveNeeds(), SetFilter(true))
} }
func (a *App) Repos(c ReposConfigProvider) error { func (a *App) Repos(c ReposConfigProvider) error {
@ -132,7 +132,7 @@ func (a *App) Repos(c ReposConfigProvider) error {
} }
return return
}, SetFilter(true)) }, c.IncludeTransitiveNeeds(), SetFilter(true))
} }
func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error { func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
@ -149,7 +149,7 @@ func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
} }
return return
}, SetFilter(true)) }, c.IncludeTransitiveNeeds(), SetFilter(true))
} }
func (a *App) Diff(c DiffConfigProvider) error { func (a *App) Diff(c DiffConfigProvider) error {
@ -203,7 +203,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
} }
return matched, criticalErrs return matched, criticalErrs
}) }, false)
if err != nil { if err != nil {
return err return err
@ -225,9 +225,6 @@ func (a *App) Diff(c DiffConfigProvider) error {
} }
func (a *App) Template(c TemplateConfigProvider) error { func (a *App) Template(c TemplateConfigProvider) error {
opts := []LoadOption{SetRetainValuesFiles(c.SkipCleanup())}
return a.ForEachState(func(run *Run) (ok bool, errs []error) { return a.ForEachState(func(run *Run) (ok bool, errs []error) {
includeCRDs := c.IncludeCRDs() includeCRDs := c.IncludeCRDs()
@ -249,7 +246,7 @@ func (a *App) Template(c TemplateConfigProvider) error {
} }
return return
}, opts...) }, c.IncludeTransitiveNeeds())
} }
func (a *App) WriteValues(c WriteValuesConfigProvider) error { func (a *App) WriteValues(c WriteValuesConfigProvider) error {
@ -269,7 +266,7 @@ func (a *App) WriteValues(c WriteValuesConfigProvider) error {
} }
return return
}, SetFilter(true)) }, c.IncludeTransitiveNeeds(), SetFilter(true))
} }
type MultiError struct { type MultiError struct {
@ -322,7 +319,7 @@ func (a *App) Lint(c LintConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
if err != nil { if err != nil {
return err return err
@ -350,7 +347,7 @@ func (a *App) Fetch(c FetchConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
} }
func (a *App) Sync(c SyncConfigProvider) error { func (a *App) Sync(c SyncConfigProvider) error {
@ -363,6 +360,7 @@ func (a *App) Sync(c SyncConfigProvider) error {
Wait: c.Wait(), Wait: c.Wait(),
WaitForJobs: c.WaitForJobs(), WaitForJobs: c.WaitForJobs(),
IncludeCRDs: &includeCRDs, IncludeCRDs: &includeCRDs,
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
}, func() { }, func() {
ok, errs = a.sync(run, c) ok, errs = a.sync(run, c)
}) })
@ -372,7 +370,7 @@ func (a *App) Sync(c SyncConfigProvider) error {
} }
return return
}) }, c.IncludeTransitiveNeeds())
} }
func (a *App) Apply(c ApplyConfigProvider) error { func (a *App) Apply(c ApplyConfigProvider) error {
@ -410,7 +408,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
} }
return return
}, opts...) }, c.IncludeTransitiveNeeds(), opts...)
if err != nil { if err != nil {
return err return err
@ -439,7 +437,7 @@ func (a *App) Status(c StatusesConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
} }
func (a *App) Delete(c DeleteConfigProvider) error { func (a *App) Delete(c DeleteConfigProvider) error {
@ -456,7 +454,7 @@ func (a *App) Delete(c DeleteConfigProvider) error {
} }
return return
}, SetReverse(true)) }, false, SetReverse(true))
} }
func (a *App) Destroy(c DestroyConfigProvider) error { func (a *App) Destroy(c DestroyConfigProvider) error {
@ -473,7 +471,7 @@ func (a *App) Destroy(c DestroyConfigProvider) error {
} }
return return
}, SetReverse(true)) }, false, SetReverse(true))
} }
func (a *App) Test(c TestConfigProvider) error { func (a *App) Test(c TestConfigProvider) error {
@ -496,7 +494,7 @@ func (a *App) Test(c TestConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
} }
func (a *App) PrintState(c StateConfigProvider) error { func (a *App) PrintState(c StateConfigProvider) error {
@ -543,7 +541,7 @@ func (a *App) PrintState(c StateConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
} }
func (a *App) ListReleases(c ListConfigProvider) error { func (a *App) ListReleases(c ListConfigProvider) error {
@ -594,7 +592,7 @@ func (a *App) ListReleases(c ListConfigProvider) error {
} }
return return
}, SetFilter(true)) }, false, SetFilter(true))
if err != nil { if err != nil {
return err 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() ctx := NewContext()
err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) {
helm := a.getHelm(st) helm := a.getHelm(st)
run := NewRun(st, helm, ctx) run := NewRun(st, helm, ctx)
return do(run) return do(run)
}, o...) }, includeTransitiveNeeds, o...)
return err return err
} }
@ -966,7 +964,7 @@ type Opts struct {
DAGEnabled bool 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{ opts := LoadOpts{
Selectors: a.Selectors, 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 { return processFilteredReleases(st, a.getHelm(st), func(st *state.HelmState) []error {
_, err := converge(st) _, err := converge(st)
return err return err
}) },
includeTransitiveNeeds)
} }
} }
return a.visitStates(fileOrDir, opts, f) 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 { if len(st.Selectors) > 0 {
err := st.FilterReleases() err := st.FilterReleases(includeTransitiveNeeds)
if err != nil { if err != nil {
return false, []error{err} return false, []error{err}
} }
@ -1066,11 +1065,11 @@ func checkDuplicates(helm helmexec.Interface, st *state.HelmState, releases []st
return nil return nil
} }
func (a *App) Wrap(converge func(*state.HelmState, helmexec.Interface) []error) 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) (bool, []error) { return func(st *state.HelmState, helm helmexec.Interface, includeTransitiveNeeds bool) (bool, []error) {
return processFilteredReleases(st, helm, func(st *state.HelmState) []error { return processFilteredReleases(st, helm, func(st *state.HelmState) []error {
return converge(st, helm) return converge(st, helm)
}) }, includeTransitiveNeeds)
} }
} }
@ -1144,8 +1143,8 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri
return files, nil return files, nil
} }
func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseSpec, error) { func (a *App) getSelectedReleases(r *Run, includeTransitiveNeeds bool) ([]state.ReleaseSpec, []state.ReleaseSpec, error) {
selected, err := r.state.GetSelectedReleasesWithOverrides() selected, err := r.state.GetSelectedReleasesWithOverrides(includeTransitiveNeeds)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -1159,12 +1158,7 @@ func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseS
needed := map[string]struct{}{} needed := map[string]struct{}{}
for _, r := range selected { for _, r := range selected {
for _, id := range r.Needs { collectNeeds(r, selectedIds, needed, allReleases, includeTransitiveNeeds)
// Avoids duplicating a release that is selected AND also needed by another selected release
if _, ok := selectedIds[id]; !ok {
needed[id] = struct{}{}
}
}
} }
var releases []state.ReleaseSpec var releases []state.ReleaseSpec
@ -1192,11 +1186,29 @@ func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, []state.ReleaseS
return selected, releases, nil 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) { func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
st := r.state st := r.state
helm := r.helm helm := r.helm
selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds())
if err != nil { if err != nil {
return false, false, []error{err} 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. // See https://github.com/roboll/helmfile/issues/1818 for more context.
st.Releases = selectedAndNeededReleases 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 { if err != nil {
return false, false, []error{err} return false, false, []error{err}
} }
@ -1320,7 +1332,7 @@ Do you really want to apply?
// We upgrade releases by traversing the DAG // We upgrade releases by traversing the DAG
if len(releasesToBeUpdated) > 0 { 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 var rs []state.ReleaseSpec
for _, r := range subst.Releases { for _, r := range subst.Releases {
@ -1357,7 +1369,7 @@ func (a *App) delete(r *Run, purge bool, c DestroyConfigProvider) (bool, []error
affectedReleases := state.AffectedReleases{} affectedReleases := state.AffectedReleases{}
toSync, _, err := a.getSelectedReleases(r) toSync, _, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return false, []error{err} 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) { func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) {
st := r.state st := r.state
selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return nil, false, false, []error{err} 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 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 { if err != nil {
return nil, false, false, []error{err} return nil, false, false, []error{err}
} }
@ -1485,7 +1497,7 @@ func (a *App) lint(r *Run, c LintConfigProvider) (bool, []error, []error) {
allReleases := st.GetReleasesWithOverrides() allReleases := st.GetReleasesWithOverrides()
selectedReleases, _, err := a.getSelectedReleases(r) selectedReleases, _, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return false, nil, []error{err} return false, nil, []error{err}
} }
@ -1553,7 +1565,7 @@ func (a *App) status(r *Run, c StatusesConfigProvider) (bool, []error) {
allReleases := st.GetReleasesWithOverrides() allReleases := st.GetReleasesWithOverrides()
selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return false, []error{err} return false, []error{err}
} }
@ -1603,7 +1615,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) {
st := r.state st := r.state
helm := r.helm helm := r.helm
selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds())
if err != nil { if err != nil {
return false, []error{err} 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. // See https://github.com/roboll/helmfile/issues/1818 for more context.
st.Releases = selectedAndNeededReleases 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 { if err != nil {
return false, []error{err} return false, []error{err}
} }
@ -1727,7 +1739,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) {
} }
if len(releasesToUpdate) > 0 { 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 var rs []state.ReleaseSpec
for _, r := range subst.Releases { for _, r := range subst.Releases {
@ -1759,7 +1771,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) {
st := r.state st := r.state
helm := r.helm helm := r.helm
selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r) selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds())
if err != nil { if err != nil {
return false, []error{err} 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. // See https://github.com/roboll/helmfile/issues/1818 for more context.
st.Releases = selectedAndNeededReleases 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 { if err != nil {
return false, []error{err} return false, []error{err}
} }
@ -1813,7 +1825,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) {
} }
if len(toRender) > 0 { 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{ opts := &state.TemplateOpts{
Set: c.Set(), Set: c.Set(),
IncludeCRDs: c.IncludeCRDs(), IncludeCRDs: c.IncludeCRDs(),
@ -1837,7 +1849,7 @@ func (a *App) test(r *Run, c TestConfigProvider) []error {
st := r.state st := r.state
toTest, _, err := a.getSelectedReleases(r) toTest, _, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
@ -1859,7 +1871,7 @@ func (a *App) writeValues(r *Run, c WriteValuesConfigProvider) (bool, []error) {
st := r.state st := r.state
helm := r.helm helm := r.helm
toRender, _, err := a.getSelectedReleases(r) toRender, _, err := a.getSelectedReleases(r, false)
if err != nil { if err != nil {
return false, []error{err} return false, []error{err}
} }

View File

@ -19,6 +19,7 @@ func TestApply_3(t *testing.T) {
type fields struct { type fields struct {
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTransitiveNeeds bool
} }
type testcase struct { type testcase struct {
@ -114,6 +115,7 @@ func TestApply_3(t *testing.T) {
skipDiffOnInstall: tc.skipDiffOnInstall, skipDiffOnInstall: tc.skipDiffOnInstall,
skipNeeds: tc.fields.skipNeeds, skipNeeds: tc.fields.skipNeeds,
includeNeeds: tc.fields.includeNeeds, includeNeeds: tc.fields.includeNeeds,
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
}) })
var gotErr string var gotErr string

View File

@ -19,6 +19,7 @@ func TestApply_2(t *testing.T) {
type fields struct { type fields struct {
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTransitiveNeeds bool
} }
type testcase struct { type testcase struct {
@ -114,6 +115,7 @@ func TestApply_2(t *testing.T) {
skipDiffOnInstall: tc.skipDiffOnInstall, skipDiffOnInstall: tc.skipDiffOnInstall,
skipNeeds: tc.fields.skipNeeds, skipNeeds: tc.fields.skipNeeds,
includeNeeds: tc.fields.includeNeeds, includeNeeds: tc.fields.includeNeeds,
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
}) })
var gotErr string var gotErr string
@ -960,6 +962,128 @@ NAME CHART VERSION
external-secrets incubator/raw external-secrets incubator/raw
my-release 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=<nil>
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
`, `,
}) })
}) })

View File

@ -19,6 +19,7 @@ func TestSync(t *testing.T) {
type fields struct { type fields struct {
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTransitiveNeeds bool
} }
type testcase struct { type testcase struct {
@ -112,6 +113,7 @@ func TestSync(t *testing.T) {
skipDiffOnInstall: tc.skipDiffOnInstall, skipDiffOnInstall: tc.skipDiffOnInstall,
skipNeeds: tc.fields.skipNeeds, skipNeeds: tc.fields.skipNeeds,
includeNeeds: tc.fields.includeNeeds, includeNeeds: tc.fields.includeNeeds,
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
}) })
var gotErr string var gotErr string
@ -412,6 +414,122 @@ kubernetes-external-secrets incubator/raw
external-secrets incubator/raw external-secrets incubator/raw
my-release 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=<nil>
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
`, `,
}) })
}) })
@ -421,6 +539,7 @@ my-release incubator/raw
fields: fields{ fields: fields{
skipNeeds: false, skipNeeds: false,
includeNeeds: true, includeNeeds: true,
includeTransitiveNeeds: false,
}, },
error: ``, error: ``,
files: map[string]string{ files: map[string]string{
@ -562,6 +681,7 @@ kubernetes-external-secrets
check(t, testcase{ check(t, testcase{
fields: fields{ fields: fields{
skipNeeds: false, skipNeeds: false,
includeTransitiveNeeds: false,
includeNeeds: true, includeNeeds: true,
}, },
error: ``, error: ``,

View File

@ -106,6 +106,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
noop, noop,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -157,6 +158,7 @@ BAZ: 4
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -198,6 +200,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if err == nil { if err == nil {
@ -242,6 +245,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -293,6 +297,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if testcase.expectErr && err == nil { if testcase.expectErr && err == nil {
@ -359,6 +364,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if testcase.expectErr && err == nil { if testcase.expectErr && err == nil {
@ -413,6 +419,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
Noop, Noop,
false,
SetFilter(true), SetFilter(true),
) )
if testcase.expectErr && err == nil { if testcase.expectErr && err == nil {
@ -531,6 +538,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if testcase.expectErr { if testcase.expectErr {
@ -774,6 +782,7 @@ func runFilterSubHelmFilesTests(testcases []struct {
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if testcase.expectErr { if testcase.expectErr {
@ -869,6 +878,7 @@ tillerNs: INLINE_TILLER_NS_2
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -970,6 +980,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetReverse(testcase.reverse), SetReverse(testcase.reverse),
SetFilter(true), SetFilter(true),
) )
@ -1035,6 +1046,7 @@ bar: "bar1"
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -1157,6 +1169,7 @@ x:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -1209,6 +1222,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -1266,6 +1280,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {
@ -1316,6 +1331,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -1364,6 +1380,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -1407,6 +1424,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -1450,6 +1468,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -1497,6 +1516,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
@ -2248,6 +2268,7 @@ type configImpl struct {
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTransitiveNeeds bool
} }
func (a configImpl) Selectors() []string { func (a configImpl) Selectors() []string {
@ -2290,6 +2311,10 @@ func (c configImpl) IncludeNeeds() bool {
return c.includeNeeds return c.includeNeeds
} }
func (c configImpl) IncludeTransitiveNeeds() bool {
return c.includeTransitiveNeeds
}
func (c configImpl) OutputDir() string { func (c configImpl) OutputDir() string {
return "output/subdir" return "output/subdir"
} }
@ -2325,6 +2350,7 @@ type applyConfig struct {
skipDeps bool skipDeps bool
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTransitiveNeeds bool
includeTests bool includeTests bool
suppressSecrets bool suppressSecrets bool
showSecrets bool showSecrets bool
@ -2385,6 +2411,10 @@ func (c applyConfig) IncludeNeeds() bool {
return c.includeNeeds return c.includeNeeds
} }
func (c applyConfig) IncludeTransitiveNeeds() bool {
return c.includeTransitiveNeeds
}
func (a applyConfig) IncludeTests() bool { func (a applyConfig) IncludeTests() bool {
return a.includeTests return a.includeTests
} }
@ -2439,12 +2469,17 @@ func (a applyConfig) SkipDiffOnInstall() bool {
type depsConfig struct { type depsConfig struct {
skipRepos bool skipRepos bool
includeTransitiveNeeds bool
} }
func (d depsConfig) SkipRepos() bool { func (d depsConfig) SkipRepos() bool {
return d.skipRepos return d.skipRepos
} }
func (d depsConfig) IncludeTransitiveNeeds() bool {
return d.includeTransitiveNeeds
}
func (d depsConfig) Args() string { func (d depsConfig) Args() string {
return "" return ""
} }
@ -4483,6 +4518,7 @@ See https://github.com/roboll/helmfile/issues/878 for more information.
depsErr := app.Deps(depsConfig{ depsErr := app.Deps(depsConfig{
skipRepos: false, skipRepos: false,
includeTransitiveNeeds: false,
}) })
if tc.error == "" && depsErr != nil { if tc.error == "" && depsErr != nil {
@ -4777,6 +4813,7 @@ releases:
err := app.ForEachState( err := app.ForEachState(
collectReleases, collectReleases,
false,
SetFilter(true), SetFilter(true),
) )
if err != nil { if err != nil {

View File

@ -23,15 +23,18 @@ type DeprecatedChartsConfigProvider interface {
concurrencyConfig concurrencyConfig
loggingConfig loggingConfig
IncludeTransitiveNeeds() bool
} }
type DepsConfigProvider interface { type DepsConfigProvider interface {
Args() string Args() string
SkipRepos() bool SkipRepos() bool
IncludeTransitiveNeeds() bool
} }
type ReposConfigProvider interface { type ReposConfigProvider interface {
Args() string Args() string
IncludeTransitiveNeeds() bool
} }
type ApplyConfigProvider interface { type ApplyConfigProvider interface {
@ -63,6 +66,7 @@ type ApplyConfigProvider interface {
SkipNeeds() bool SkipNeeds() bool
IncludeNeeds() bool IncludeNeeds() bool
IncludeTransitiveNeeds() bool
concurrencyConfig concurrencyConfig
interactive interactive
@ -81,6 +85,7 @@ type SyncConfigProvider interface {
SkipNeeds() bool SkipNeeds() bool
IncludeNeeds() bool IncludeNeeds() bool
IncludeTransitiveNeeds() bool
concurrencyConfig concurrencyConfig
loggingConfig loggingConfig
@ -174,6 +179,7 @@ type TemplateConfigProvider interface {
OutputDir() string OutputDir() string
IncludeCRDs() bool IncludeCRDs() bool
IncludeNeeds() bool IncludeNeeds() bool
IncludeTransitiveNeeds() bool
concurrencyConfig concurrencyConfig
} }
@ -183,6 +189,7 @@ type WriteValuesConfigProvider interface {
Set() []string Set() []string
OutputFileTemplate() string OutputFileTemplate() string
SkipDeps() bool SkipDeps() bool
IncludeTransitiveNeeds() bool
} }
type StatusesConfigProvider interface { type StatusesConfigProvider interface {

View File

@ -105,6 +105,7 @@ func TestDestroy_2(t *testing.T) {
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency, concurrency: tc.concurrency,
logger: logger, logger: logger,
includeTransitiveNeeds: false,
}) })
if tc.error == "" && destroyErr != nil { if tc.error == "" && destroyErr != nil {

View File

@ -28,6 +28,7 @@ type destroyConfig struct {
interactive bool interactive bool
skipDeps bool skipDeps bool
logger *zap.SugaredLogger logger *zap.SugaredLogger
includeTransitiveNeeds bool
} }
func (d destroyConfig) Args() string { func (d destroyConfig) Args() string {
@ -50,6 +51,10 @@ func (d destroyConfig) SkipDeps() bool {
return d.skipDeps return d.skipDeps
} }
func (d destroyConfig) IncludeTransitiveNeeds() bool {
return d.includeTransitiveNeeds
}
func TestDestroy(t *testing.T) { func TestDestroy(t *testing.T) {
type testcase struct { type testcase struct {
helm3 bool helm3 bool

View File

@ -98,7 +98,7 @@ func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepare
func (r *Run) Deps(c DepsConfigProvider) []error { func (r *Run) Deps(c DepsConfigProvider) []error {
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) 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 { func (r *Run) Repos(c ReposConfigProvider) error {

View File

@ -1,8 +1,9 @@
package state package state
import ( import (
"github.com/google/go-cmp/cmp"
"testing" "testing"
"github.com/google/go-cmp/cmp"
) )
func TestSelectReleasesWithOverrides(t *testing.T) { func TestSelectReleasesWithOverrides(t *testing.T) {
@ -79,7 +80,76 @@ func TestSelectReleasesWithOverrides(t *testing.T) {
for _, tc := range testcases { for _, tc := range testcases {
state.Selectors = tc.selector 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 { if err != nil {
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err) t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
} }

View File

@ -398,36 +398,6 @@ type RepoUpdater interface {
RegistryLogin(name string, username string, password string) error 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) { func (st *HelmState) SyncRepos(helm RepoUpdater, shouldSkip map[string]bool) ([]string, error) {
var updated []string var updated []string
@ -973,6 +943,7 @@ type ChartPrepareOptions struct {
Wait bool Wait bool
WaitForJobs bool WaitForJobs bool
OutputDir string OutputDir string
IncludeTransitiveNeeds bool
} }
type chartPrepareResult struct { 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 // 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. // 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 { if err != nil {
return nil, []error{err} return nil, []error{err}
} }
@ -2047,16 +2018,16 @@ func (st *HelmState) GetReleasesWithOverrides() []ReleaseSpec {
return rs return rs
} }
func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { func (st *HelmState) SelectReleasesWithOverrides(includeTransitiveNeeds bool) ([]Release, error) {
values := st.Values() 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 { if err != nil {
return nil, err return nil, err
} }
return rs, nil 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 var filteredReleases []Release
filters := []ReleaseFilter{} filters := []ReleaseFilter{}
for _, label := range selectors { for _, label := range selectors {
@ -2113,12 +2084,52 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabe
} }
filteredReleases = append(filteredReleases, res) filteredReleases = append(filteredReleases, res)
} }
if includeTransitiveNeeds {
unmarkNeedsAndTransitives(filteredReleases, releases)
}
return filteredReleases, nil return filteredReleases, nil
} }
func (st *HelmState) GetSelectedReleasesWithOverrides() ([]ReleaseSpec, error) { func unmarkNeedsAndTransitives(filteredReleases []Release, allReleases []ReleaseSpec) {
filteredReleases, err := st.SelectReleasesWithOverrides() 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 { if err != nil {
return nil, err 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. // FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
func (st *HelmState) FilterReleases() error { func (st *HelmState) FilterReleases(includeTransitiveNeeds bool) error {
releases, err := st.GetSelectedReleasesWithOverrides() releases, err := st.GetSelectedReleasesWithOverrides(includeTransitiveNeeds)
if err != nil { if err != nil {
return err return err
} }
@ -2209,7 +2220,7 @@ func (st *HelmState) ResolveDeps() (*HelmState, error) {
} }
// UpdateDeps wrapper for updating dependencies on the releases // 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 var selected []ReleaseSpec
if len(st.Selectors) > 0 { 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 // 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. // 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 { if err != nil {
return []error{err} return []error{err}
} }

View File

@ -102,12 +102,13 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int,
type PlanOptions struct { type PlanOptions struct {
Reverse bool Reverse bool
IncludeNeeds bool IncludeNeeds bool
IncludeTransitiveNeeds bool
SkipNeeds bool SkipNeeds bool
SelectedReleases []ReleaseSpec SelectedReleases []ReleaseSpec
} }
func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) { func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) {
marked, err := st.SelectReleasesWithOverrides() marked, err := st.SelectReleasesWithOverrides(opts.IncludeTransitiveNeeds)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1848,7 +1848,7 @@ generated: 2019-05-16T15:42:45.50486+09:00
}) })
fs.Cwd = basePath fs.Cwd = basePath
state = injectFs(state, fs) state = injectFs(state, fs)
errs := state.UpdateDeps(helm) errs := state.UpdateDeps(helm, false)
want := []string{"/example", "./example", generatedDir} want := []string{"/example", "./example", generatedDir}
if !reflect.DeepEqual(helm.Charts, want) { if !reflect.DeepEqual(helm.Charts, want) {
@ -2141,7 +2141,7 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
RenderedValues: map[string]interface{}{}, RenderedValues: map[string]interface{}{},
} }
state.Selectors = []string{tt.labels} state.Selectors = []string{tt.labels}
errs := state.FilterReleases() errs := state.FilterReleases(false)
if (errs != nil) != tt.wantErr { if (errs != nil) != tt.wantErr {
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
return return