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 {
@ -358,11 +355,12 @@ func (a *App) Sync(c SyncConfigProvider) error {
includeCRDs := !c.SkipCRDs() includeCRDs := !c.SkipCRDs()
prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{ prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{
SkipRepos: c.SkipDeps(), SkipRepos: c.SkipDeps(),
SkipDeps: c.SkipDeps(), SkipDeps: c.SkipDeps(),
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

@ -17,8 +17,9 @@ import (
func TestApply_3(t *testing.T) { 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 {
@ -109,11 +110,12 @@ func TestApply_3(t *testing.T) {
syncErr := app.Apply(applyConfig{ syncErr := app.Apply(applyConfig{
// 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,
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

@ -17,8 +17,9 @@ import (
func TestApply_2(t *testing.T) { 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 {
@ -109,11 +110,12 @@ func TestApply_2(t *testing.T) {
syncErr := app.Apply(applyConfig{ syncErr := app.Apply(applyConfig{
// 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,
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

@ -17,8 +17,9 @@ import (
func TestSync(t *testing.T) { 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 {
@ -107,11 +108,12 @@ func TestSync(t *testing.T) {
syncErr := app.Sync(applyConfig{ syncErr := app.Sync(applyConfig{
// 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,
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
`, `,
}) })
}) })
@ -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) { t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
check(t, testcase{ check(t, testcase{
fields: fields{ fields: fields{
skipNeeds: false, skipNeeds: false,
includeNeeds: true, includeNeeds: true,
includeTransitiveNeeds: false,
}, },
error: ``, error: ``,
files: map[string]string{ 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) { t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
check(t, testcase{ check(t, testcase{
fields: fields{ fields: fields{
skipNeeds: false, skipNeeds: false,
includeNeeds: true, includeTransitiveNeeds: false,
includeNeeds: true,
}, },
error: ``, error: ``,
files: map[string]string{ files: map[string]string{

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),
) )
@ -2246,8 +2266,9 @@ type configImpl struct {
skipCRDs bool skipCRDs bool
skipDeps bool skipDeps bool
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"
} }
@ -2315,30 +2340,31 @@ func (c configImpl) Output() string {
} }
type applyConfig struct { type applyConfig struct {
args string args string
values []string values []string
retainValuesFiles bool retainValuesFiles bool
set []string set []string
validate bool validate bool
skipCleanup bool skipCleanup bool
skipCRDs bool skipCRDs bool
skipDeps bool skipDeps bool
skipNeeds bool skipNeeds bool
includeNeeds bool includeNeeds bool
includeTests bool includeTransitiveNeeds bool
suppressSecrets bool includeTests bool
showSecrets bool suppressSecrets bool
suppressDiff bool showSecrets bool
noColor bool suppressDiff bool
context int noColor bool
diffOutput string context int
concurrency int diffOutput string
detailedExitcode bool concurrency int
interactive bool detailedExitcode bool
skipDiffOnInstall bool interactive bool
logger *zap.SugaredLogger skipDiffOnInstall bool
wait bool logger *zap.SugaredLogger
waitForJobs bool wait bool
waitForJobs bool
} }
func (a applyConfig) Args() string { func (a applyConfig) Args() string {
@ -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
} }
@ -2438,13 +2468,18 @@ 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 ""
} }
@ -4482,7 +4517,8 @@ See https://github.com/roboll/helmfile/issues/878 for more information.
}, tc.files) }, tc.files)
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

@ -103,8 +103,9 @@ func TestDestroy_2(t *testing.T) {
destroyErr := app.Destroy(destroyConfig{ destroyErr := app.Destroy(destroyConfig{
// 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

@ -23,11 +23,12 @@ const (
) )
type destroyConfig struct { type destroyConfig struct {
args string args string
concurrency int concurrency int
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
@ -968,11 +938,12 @@ type ChartPrepareOptions struct {
SkipCleanup bool 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. // 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 // It's required when one of your chart relies on Capabilities.APIVersions in a template
Validate bool Validate bool
IncludeCRDs *bool IncludeCRDs *bool
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

@ -100,14 +100,15 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int,
} }
type PlanOptions struct { type PlanOptions struct {
Reverse bool Reverse bool
IncludeNeeds bool IncludeNeeds bool
SkipNeeds bool IncludeTransitiveNeeds bool
SelectedReleases []ReleaseSpec SkipNeeds bool
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