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