Add --{include,skip}-needs to various helmfile commands (#1772)
* Add --{include,skip}-needs to helmfile-sync and helmfile-apply
* Add --include-needs to helmfile-template
* Add TODO related to #1018
* Add a few new test files to cover new functionalities
* Update apply test to incorporate the change that the destroy and sync steps target affected releases only
			
			
This commit is contained in:
		
							parent
							
								
									d6db4b53e7
								
							
						
					
					
						commit
						5d43b30a7c
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -29,7 +29,7 @@ require ( | ||||||
| 	github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 | 	github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 | ||||||
| 	github.com/urfave/cli v1.22.5 | 	github.com/urfave/cli v1.22.5 | ||||||
| 	github.com/variantdev/chartify v0.8.0 | 	github.com/variantdev/chartify v0.8.0 | ||||||
| 	github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 | 	github.com/variantdev/dag v1.0.0 | ||||||
| 	github.com/variantdev/vals v0.13.0 | 	github.com/variantdev/vals v0.13.0 | ||||||
| 	go.uber.org/multierr v1.6.0 | 	go.uber.org/multierr v1.6.0 | ||||||
| 	go.uber.org/zap v1.16.0 | 	go.uber.org/zap v1.16.0 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -585,6 +585,8 @@ github.com/variantdev/chartify v0.8.0 h1:yIBsS/dIUeMjWP8U6JWlT3PM0Lky7en9QBi+MgD | ||||||
| github.com/variantdev/chartify v0.8.0/go.mod h1:qF4XzQlkfH/6k2jAi1hLas+lK4zSCa8kY+r5JdmLA68= | github.com/variantdev/chartify v0.8.0/go.mod h1:qF4XzQlkfH/6k2jAi1hLas+lK4zSCa8kY+r5JdmLA68= | ||||||
| github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 h1:KrfQBEUn+wEOQ/6UIfoqRDvn+Q/wZridQ7t0G1vQqKE= | github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 h1:KrfQBEUn+wEOQ/6UIfoqRDvn+Q/wZridQ7t0G1vQqKE= | ||||||
| github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE= | github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE= | ||||||
|  | github.com/variantdev/dag v1.0.0 h1:7SFjATxHtrYV20P3tx53yNDBMegz6RT4jv8JPHqAHdM= | ||||||
|  | github.com/variantdev/dag v1.0.0/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE= | ||||||
| github.com/variantdev/vals v0.13.0 h1:zdtTBjoWKkUGdFauxETkDVjqWXdjUNwI+ggWcUmpxv8= | github.com/variantdev/vals v0.13.0 h1:zdtTBjoWKkUGdFauxETkDVjqWXdjUNwI+ggWcUmpxv8= | ||||||
| github.com/variantdev/vals v0.13.0/go.mod h1:pBwm+vPLQALN6otkNqiT1fUKdWHfjAm4070UkrNLsVA= | github.com/variantdev/vals v0.13.0/go.mod h1:pBwm+vPLQALN6otkNqiT1fUKdWHfjAm4070UkrNLsVA= | ||||||
| github.com/vektra/mockery v1.1.2/go.mod h1:VcfZjKaFOPO+MpN4ZvwPjs4c48lkq1o3Ym8yHZJu0jU= | github.com/vektra/mockery v1.1.2/go.mod h1:VcfZjKaFOPO+MpN4ZvwPjs4c48lkq1o3Ym8yHZJu0jU= | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								main.go
								
								
								
								
							
							
						
						
									
										36
									
								
								main.go
								
								
								
								
							|  | @ -199,6 +199,14 @@ func main() { | ||||||
| 					Name:  "include-tests", | 					Name:  "include-tests", | ||||||
| 					Usage: "enable the diffing of the helm test hooks", | 					Usage: "enable the diffing of the helm test hooks", | ||||||
| 				}, | 				}, | ||||||
|  | 				cli.BoolFlag{ | ||||||
|  | 					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`, | ||||||
|  | 				}, | ||||||
|  | 				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{ | 				cli.BoolFlag{ | ||||||
| 					Name:  "suppress-secrets", | 					Name:  "suppress-secrets", | ||||||
| 					Usage: "suppress secrets in the output. highly recommended to specify on CI/CD use-cases", | 					Usage: "suppress secrets in the output. highly recommended to specify on CI/CD use-cases", | ||||||
|  | @ -260,6 +268,10 @@ func main() { | ||||||
| 					Name:  "include-crds", | 					Name:  "include-crds", | ||||||
| 					Usage: "include CRDs in the templated output", | 					Usage: "include CRDs in the templated output", | ||||||
| 				}, | 				}, | ||||||
|  | 				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{ | 				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"`, | ||||||
|  | @ -386,6 +398,14 @@ func main() { | ||||||
| 					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"`, | ||||||
| 				}, | 				}, | ||||||
|  | 				cli.BoolFlag{ | ||||||
|  | 					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`, | ||||||
|  | 				}, | ||||||
|  | 				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{ | 				cli.BoolFlag{ | ||||||
| 					Name:  "wait", | 					Name:  "wait", | ||||||
| 					Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`, | 					Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`, | ||||||
|  | @ -442,6 +462,14 @@ func main() { | ||||||
| 					Name:  "skip-crds", | 					Name:  "skip-crds", | ||||||
| 					Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present", | 					Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present", | ||||||
| 				}, | 				}, | ||||||
|  | 				cli.BoolFlag{ | ||||||
|  | 					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`, | ||||||
|  | 				}, | ||||||
|  | 				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{ | 				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", | ||||||
|  | @ -710,6 +738,14 @@ func (c configImpl) HasCommandName(name string) bool { | ||||||
| 	return c.c.Command.HasName(name) | 	return c.c.Command.HasName(name) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c configImpl) SkipNeeds() bool { | ||||||
|  | 	return c.c.Bool("skip-needs") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) IncludeNeeds() bool { | ||||||
|  | 	return c.c.Bool("include-needs") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // DiffConfig
 | // DiffConfig
 | ||||||
| 
 | 
 | ||||||
| func (c configImpl) SkipDeps() bool { | func (c configImpl) SkipDeps() bool { | ||||||
|  |  | ||||||
							
								
								
									
										177
									
								
								pkg/app/app.go
								
								
								
								
							
							
						
						
									
										177
									
								
								pkg/app/app.go
								
								
								
								
							|  | @ -868,12 +868,16 @@ func printBatches(batches [][]state.Release) string { | ||||||
| 	return buf.String() | 	return buf.String() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func withDAG(templated *state.HelmState, helm helmexec.Interface, logger *zap.SugaredLogger, reverse bool, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) (bool, []error) { | func withDAG(templated *state.HelmState, helm helmexec.Interface, logger *zap.SugaredLogger, opts state.PlanOptions, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) (bool, []error) { | ||||||
| 	batches, err := templated.PlanReleases(reverse) | 	batches, err := templated.PlanReleases(opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, []error{err} | 		return false, []error{err} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return withBatches(templated, batches, helm, logger, converge) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func withBatches(templated *state.HelmState, batches [][]state.Release, helm helmexec.Interface, logger *zap.SugaredLogger, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) (bool, []error) { | ||||||
| 	numBatches := len(batches) | 	numBatches := len(batches) | ||||||
| 
 | 
 | ||||||
| 	logger.Debugf("processing %d groups of releases in this order:\n%s", numBatches, printBatches(batches)) | 	logger.Debugf("processing %d groups of releases in this order:\n%s", numBatches, printBatches(batches)) | ||||||
|  | @ -966,12 +970,24 @@ func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, conve | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := checkDuplicates(helm, st, st.Releases); err != nil { | ||||||
|  | 		return false, []error{err} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	errs := converge(st) | ||||||
|  | 
 | ||||||
|  | 	processed := len(st.Releases) != 0 && len(errs) == 0 | ||||||
|  | 
 | ||||||
|  | 	return processed, errs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func checkDuplicates(helm helmexec.Interface, st *state.HelmState, releases []state.ReleaseSpec) error { | ||||||
| 	type Key struct { | 	type Key struct { | ||||||
| 		TillerNamespace, Name, KubeContext string | 		TillerNamespace, Name, KubeContext string | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	releaseNameCounts := map[Key]int{} | 	releaseNameCounts := map[Key]int{} | ||||||
| 	for _, r := range st.Releases { | 	for _, r := range releases { | ||||||
| 		namespace := r.Namespace | 		namespace := r.Namespace | ||||||
| 		if !helm.IsHelm3() { | 		if !helm.IsHelm3() { | ||||||
| 			if r.TillerNamespace != "" { | 			if r.TillerNamespace != "" { | ||||||
|  | @ -994,15 +1010,11 @@ func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, conve | ||||||
| 				msg += fmt.Sprintf(" in kubecontext %q", name.KubeContext) | 				msg += fmt.Sprintf(" in kubecontext %q", name.KubeContext) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return false, []error{fmt.Errorf("duplicate release %q found%s: there were %d releases named \"%s\" matching specified selector", name.Name, msg, c, name.Name)} | 			return fmt.Errorf("duplicate release %q found%s: there were %d releases named \"%s\" matching specified selector", name.Name, msg, c, name.Name) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	errs := converge(st) | 	return nil | ||||||
| 
 |  | ||||||
| 	processed := len(st.Releases) != 0 && len(errs) == 0 |  | ||||||
| 
 |  | ||||||
| 	return processed, errs |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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) (bool, []error) { | ||||||
|  | @ -1013,6 +1025,14 @@ func (a *App) Wrap(converge func(*state.HelmState, helmexec.Interface) []error) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (a *App) WrapWithoutSelector(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) { | ||||||
|  | 		errs := converge(st, helm) | ||||||
|  | 		processed := len(st.Releases) != 0 && len(errs) == 0 | ||||||
|  | 		return processed, errs | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]string, error) { | func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]string, error) { | ||||||
| 	path, err := a.remote.Locate(specifiedPath) | 	path, err := a.remote.Locate(specifiedPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -1081,6 +1101,10 @@ func (a *App) getSelectedReleases(r *Run) ([]state.ReleaseSpec, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := checkDuplicates(r.helm, r.state, releases); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var extra string | 	var extra string | ||||||
| 
 | 
 | ||||||
| 	if len(r.state.Selectors) > 0 { | 	if len(r.state.Selectors) > 0 { | ||||||
|  | @ -1106,9 +1130,22 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { | ||||||
| 		return false, false, nil | 		return false, false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: toApply, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds()}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, false, []error{err} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var toApplyWithNeeds []state.ReleaseSpec | ||||||
|  | 
 | ||||||
|  | 	for _, rs := range plan { | ||||||
|  | 		for _, r := range rs { | ||||||
|  | 			toApplyWithNeeds = append(toApplyWithNeeds, r.ReleaseSpec) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Do build deps and prepare only on selected releases so that we won't waste time
 | 	// Do build deps and prepare only on selected releases so that we won't waste time
 | ||||||
| 	// on running various helm commands on unnecessary releases
 | 	// on running various helm commands on unnecessary releases
 | ||||||
| 	st.Releases = toApply | 	st.Releases = toApplyWithNeeds | ||||||
| 
 | 
 | ||||||
| 	// helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly
 | 	// helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly
 | ||||||
| 	detailedExitCode := true | 	detailedExitCode := true | ||||||
|  | @ -1126,11 +1163,21 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { | ||||||
| 		return false, false, errs | 		return false, false, errs | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var toDelete []state.ReleaseSpec | ||||||
|  | 	for _, r := range releasesToBeDeleted { | ||||||
|  | 		toDelete = append(toDelete, r) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var toUpdate []state.ReleaseSpec | ||||||
|  | 	for _, r := range releasesToBeUpdated { | ||||||
|  | 		toUpdate = append(toUpdate, r) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	releasesWithNoChange := map[string]state.ReleaseSpec{} | 	releasesWithNoChange := map[string]state.ReleaseSpec{} | ||||||
| 	for _, r := range toApply { | 	for _, r := range toApplyWithNeeds { | ||||||
| 		id := state.ReleaseToID(&r) | 		id := state.ReleaseToID(&r) | ||||||
| 		_, uninstalled := releasesToBeUpdated[id] | 		_, uninstalled := releasesToBeDeleted[id] | ||||||
| 		_, updated := releasesToBeDeleted[id] | 		_, updated := releasesToBeUpdated[id] | ||||||
| 		if !uninstalled && !updated { | 		if !uninstalled && !updated { | ||||||
| 			releasesWithNoChange[id] = r | 			releasesWithNoChange[id] = r | ||||||
| 		} | 		} | ||||||
|  | @ -1174,7 +1221,7 @@ Do you really want to apply? | ||||||
| 
 | 
 | ||||||
| 		// We deleted releases by traversing the DAG in reverse order
 | 		// We deleted releases by traversing the DAG in reverse order
 | ||||||
| 		if len(releasesToBeDeleted) > 0 { | 		if len(releasesToBeDeleted) > 0 { | ||||||
| 			_, deletionErrs := withDAG(st, helm, a.Logger, true, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 			_, deletionErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: true, SelectedReleases: toDelete, SkipNeeds: true}, 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 { | ||||||
|  | @ -1195,7 +1242,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, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 			_, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, Reverse: false, SkipNeeds: true}, 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 { | ||||||
|  | @ -1286,7 +1333,7 @@ Do you really want to delete? | ||||||
| 		r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) | 		r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) | ||||||
| 
 | 
 | ||||||
| 		if len(releasesToDelete) > 0 { | 		if len(releasesToDelete) > 0 { | ||||||
| 			_, deletionErrs := withDAG(st, helm, a.Logger, true, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 			_, deletionErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: true, SkipNeeds: true}, a.Wrap(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 { | ||||||
|  | @ -1338,13 +1385,22 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) | ||||||
| 	// Validate all releases for missing `needs` targets
 | 	// Validate all releases for missing `needs` targets
 | ||||||
| 	st.Releases = allReleases | 	st.Releases = allReleases | ||||||
| 
 | 
 | ||||||
| 	if _, err := st.PlanReleases(false); err != nil { | 	plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: toDiff, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds()}) | ||||||
|  | 	if err != nil { | ||||||
| 		return nil, false, false, []error{err} | 		return nil, false, false, []error{err} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var toDiffWithNeeds []state.ReleaseSpec | ||||||
|  | 
 | ||||||
|  | 	for _, rs := range plan { | ||||||
|  | 		for _, r := range rs { | ||||||
|  | 			toDiffWithNeeds = append(toDiffWithNeeds, r.ReleaseSpec) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Diff only targeted releases
 | 	// Diff only targeted releases
 | ||||||
| 
 | 
 | ||||||
| 	st.Releases = toDiff | 	st.Releases = toDiffWithNeeds | ||||||
| 
 | 
 | ||||||
| 	filtered := &Run{ | 	filtered := &Run{ | ||||||
| 		state: st, | 		state: st, | ||||||
|  | @ -1400,7 +1456,7 @@ func (a *App) lint(r *Run, c LintConfigProvider) (bool, []error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(releasesToRender) > 0 { | 	if len(releasesToRender) > 0 { | ||||||
| 		_, templateErrs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 		_, templateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: false, SkipNeeds: true}, a.Wrap(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 { | ||||||
|  | @ -1466,7 +1522,7 @@ func (a *App) status(r *Run, c StatusesConfigProvider) (bool, []error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(releasesToRender) > 0 { | 	if len(releasesToRender) > 0 { | ||||||
| 		_, templateErrs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 		_, templateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: false, SkipNeeds: true}, a.Wrap(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 { | ||||||
|  | @ -1501,11 +1557,24 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: toSync, IncludeNeeds: c.IncludeNeeds(), SkipNeeds: c.SkipNeeds()}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, []error{err} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var toSyncWithNeeds []state.ReleaseSpec | ||||||
|  | 
 | ||||||
|  | 	for _, rs := range batches { | ||||||
|  | 		for _, r := range rs { | ||||||
|  | 			toSyncWithNeeds = append(toSyncWithNeeds, r.ReleaseSpec) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Do build deps and prepare only on selected releases so that we won't waste time
 | 	// Do build deps and prepare only on selected releases so that we won't waste time
 | ||||||
| 	// on running various helm commands on unnecessary releases
 | 	// on running various helm commands on unnecessary releases
 | ||||||
| 	st.Releases = toSync | 	st.Releases = toSync | ||||||
| 
 | 
 | ||||||
| 	toDelete, err := st.DetectReleasesToBeDeletedForSync(helm, toSync) | 	toDelete, err := st.DetectReleasesToBeDeletedForSync(helm, toSyncWithNeeds) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, []error{err} | 		return false, []error{err} | ||||||
| 	} | 	} | ||||||
|  | @ -1517,9 +1586,15 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var toUpdate []state.ReleaseSpec | 	var toUpdate []state.ReleaseSpec | ||||||
| 	for _, r := range toSync { | 	for _, r := range toSyncWithNeeds { | ||||||
| 		if _, deleted := releasesToDelete[state.ReleaseToID(&r)]; !deleted { | 		if _, deleted := releasesToDelete[state.ReleaseToID(&r)]; !deleted { | ||||||
| 			toUpdate = append(toUpdate, r) | 			if r.Installed == nil || *r.Installed { | ||||||
|  | 				toUpdate = append(toUpdate, r) | ||||||
|  | 			} else { | ||||||
|  | 				// TODO Emit error when the user opted to fail when the needed release is disabled,
 | ||||||
|  | 				// instead of silently ignoring it.
 | ||||||
|  | 				// See https://github.com/roboll/helmfile/issues/1018
 | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1530,7 +1605,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	releasesWithNoChange := map[string]state.ReleaseSpec{} | 	releasesWithNoChange := map[string]state.ReleaseSpec{} | ||||||
| 	for _, r := range toSync { | 	for _, r := range toSyncWithNeeds { | ||||||
| 		id := state.ReleaseToID(&r) | 		id := state.ReleaseToID(&r) | ||||||
| 		_, uninstalled := releasesToDelete[id] | 		_, uninstalled := releasesToDelete[id] | ||||||
| 		_, updated := releasesToUpdate[id] | 		_, updated := releasesToUpdate[id] | ||||||
|  | @ -1572,7 +1647,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { | ||||||
| 	affectedReleases := state.AffectedReleases{} | 	affectedReleases := state.AffectedReleases{} | ||||||
| 
 | 
 | ||||||
| 	if len(releasesToDelete) > 0 { | 	if len(releasesToDelete) > 0 { | ||||||
| 		_, deletionErrs := withDAG(st, helm, a.Logger, true, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 		_, deletionErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: true, SelectedReleases: toDelete, SkipNeeds: true}, 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 { | ||||||
|  | @ -1592,12 +1667,12 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(releasesToUpdate) > 0 { | 	if len(releasesToUpdate) > 0 { | ||||||
| 		_, syncErrs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 		_, syncErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, SkipNeeds: true}, 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 { | ||||||
| 				if r2, ok := releasesToUpdate[state.ReleaseToID(&r)]; ok { | 				if _, ok := releasesToDelete[state.ReleaseToID(&r)]; !ok { | ||||||
| 					rs = append(rs, r2) | 					rs = append(rs, r) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -1626,25 +1701,37 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { | ||||||
| 
 | 
 | ||||||
| 	allReleases := st.GetReleasesWithOverrides() | 	allReleases := st.GetReleasesWithOverrides() | ||||||
| 
 | 
 | ||||||
| 	toRender, err := a.getSelectedReleases(r) | 	selectedReleases, err := a.getSelectedReleases(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, []error{err} | 		return false, []error{err} | ||||||
| 	} | 	} | ||||||
| 	if len(toRender) == 0 { | 	if len(selectedReleases) == 0 { | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Do build deps and prepare only on selected releases so that we won't waste time
 | 	batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), SkipNeeds: !c.IncludeNeeds()}) | ||||||
| 	// on running various helm commands on unnecessary releases
 | 	if err != nil { | ||||||
| 	st.Releases = toRender | 		return false, []error{err} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	releasesToRender := map[string]state.ReleaseSpec{} | 	var selectedReleasesWithNeeds []state.ReleaseSpec | ||||||
| 	for _, r := range toRender { | 
 | ||||||
|  | 	for _, rs := range batches { | ||||||
|  | 		for _, r := range rs { | ||||||
|  | 			selectedReleasesWithNeeds = append(selectedReleasesWithNeeds, r.ReleaseSpec) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var toRender []state.ReleaseSpec | ||||||
|  | 
 | ||||||
|  | 	releasesDisabled := map[string]state.ReleaseSpec{} | ||||||
|  | 	for _, r := range selectedReleasesWithNeeds { | ||||||
| 		id := state.ReleaseToID(&r) | 		id := state.ReleaseToID(&r) | ||||||
| 		if r.Installed != nil && !*r.Installed { | 		if r.Installed != nil && !*r.Installed { | ||||||
| 			continue | 			releasesDisabled[id] = r | ||||||
|  | 		} else { | ||||||
|  | 			toRender = append(toRender, r) | ||||||
| 		} | 		} | ||||||
| 		releasesToRender[id] = r |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var errs []error | 	var errs []error | ||||||
|  | @ -1661,18 +1748,8 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { | ||||||
| 		helm.SetExtraArgs(args...) | 		helm.SetExtraArgs(args...) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(releasesToRender) > 0 { | 	if len(toRender) > 0 { | ||||||
| 		_, templateErrs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { | 		_, templateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toRender, Reverse: false, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { | ||||||
| 			var rs []state.ReleaseSpec |  | ||||||
| 
 |  | ||||||
| 			for _, r := range subst.Releases { |  | ||||||
| 				if r2, ok := releasesToRender[state.ReleaseToID(&r)]; ok { |  | ||||||
| 					rs = append(rs, r2) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			subst.Releases = rs |  | ||||||
| 
 |  | ||||||
| 			opts := &state.TemplateOpts{ | 			opts := &state.TemplateOpts{ | ||||||
| 				Set:               c.Set(), | 				Set:               c.Set(), | ||||||
| 				IncludeCRDs:       c.IncludeCRDs(), | 				IncludeCRDs:       c.IncludeCRDs(), | ||||||
|  | @ -1682,7 +1759,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { | ||||||
| 			return subst.TemplateReleases(helm, c.OutputDir(), c.Values(), args, c.Concurrency(), c.Validate(), opts) | 			return subst.TemplateReleases(helm, c.OutputDir(), c.Values(), args, c.Concurrency(), c.Validate(), opts) | ||||||
| 		})) | 		})) | ||||||
| 
 | 
 | ||||||
| 		if templateErrs != nil && len(templateErrs) > 0 { | 		if len(templateErrs) > 0 { | ||||||
| 			errs = append(errs, templateErrs...) | 			errs = append(errs, templateErrs...) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,790 @@ | ||||||
|  | package app | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	"io" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"sync" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | 	"github.com/roboll/helmfile/pkg/exectest" | ||||||
|  | 	"github.com/roboll/helmfile/pkg/helmexec" | ||||||
|  | 	"github.com/roboll/helmfile/pkg/testhelper" | ||||||
|  | 	"github.com/variantdev/vals" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestSync(t *testing.T) { | ||||||
|  | 	type fields struct { | ||||||
|  | 		skipNeeds    bool | ||||||
|  | 		includeNeeds bool | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	type testcase struct { | ||||||
|  | 		fields            fields | ||||||
|  | 		ns                string | ||||||
|  | 		concurrency       int | ||||||
|  | 		skipDiffOnInstall bool | ||||||
|  | 		error             string | ||||||
|  | 		files             map[string]string | ||||||
|  | 		selectors         []string | ||||||
|  | 		lists             map[exectest.ListKey]string | ||||||
|  | 		upgraded          []exectest.Release | ||||||
|  | 		deleted           []exectest.Release | ||||||
|  | 		log               string | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	check := func(t *testing.T, tc testcase) { | ||||||
|  | 		t.Helper() | ||||||
|  | 
 | ||||||
|  | 		wantUpgrades := tc.upgraded | ||||||
|  | 		wantDeletes := tc.deleted | ||||||
|  | 
 | ||||||
|  | 		var helm = &exectest.Helm{ | ||||||
|  | 			FailOnUnexpectedList: true, | ||||||
|  | 			FailOnUnexpectedDiff: true, | ||||||
|  | 			Lists:                tc.lists, | ||||||
|  | 			DiffMutex:            &sync.Mutex{}, | ||||||
|  | 			ChartsMutex:          &sync.Mutex{}, | ||||||
|  | 			ReleasesMutex:        &sync.Mutex{}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		bs := &bytes.Buffer{} | ||||||
|  | 
 | ||||||
|  | 		func() { | ||||||
|  | 			t.Helper() | ||||||
|  | 
 | ||||||
|  | 			logReader, logWriter := io.Pipe() | ||||||
|  | 
 | ||||||
|  | 			logFlushed := &sync.WaitGroup{} | ||||||
|  | 			// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
 | ||||||
|  | 			logFlushed.Add(1) | ||||||
|  | 			go func() { | ||||||
|  | 				scanner := bufio.NewScanner(logReader) | ||||||
|  | 				for scanner.Scan() { | ||||||
|  | 					bs.Write(scanner.Bytes()) | ||||||
|  | 					bs.WriteString("\n") | ||||||
|  | 				} | ||||||
|  | 				logFlushed.Done() | ||||||
|  | 			}() | ||||||
|  | 
 | ||||||
|  | 			defer func() { | ||||||
|  | 				// This is here to avoid data-trace on bytes buffer `bs` to capture logs
 | ||||||
|  | 				if err := logWriter.Close(); err != nil { | ||||||
|  | 					panic(err) | ||||||
|  | 				} | ||||||
|  | 				logFlushed.Wait() | ||||||
|  | 			}() | ||||||
|  | 
 | ||||||
|  | 			logger := helmexec.NewLogger(logWriter, "debug") | ||||||
|  | 
 | ||||||
|  | 			valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Errorf("unexpected error creating vals runtime: %v", err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			app := appWithFs(&App{ | ||||||
|  | 				OverrideHelmBinary:  DefaultHelmBinary, | ||||||
|  | 				glob:                filepath.Glob, | ||||||
|  | 				abs:                 filepath.Abs, | ||||||
|  | 				OverrideKubeContext: "default", | ||||||
|  | 				Env:                 "default", | ||||||
|  | 				Logger:              logger, | ||||||
|  | 				helms: map[helmKey]helmexec.Interface{ | ||||||
|  | 					createHelmKey("helm", "default"): helm, | ||||||
|  | 				}, | ||||||
|  | 				valsRuntime: valsRuntime, | ||||||
|  | 			}, tc.files) | ||||||
|  | 
 | ||||||
|  | 			if tc.ns != "" { | ||||||
|  | 				app.Namespace = tc.ns | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if tc.selectors != nil { | ||||||
|  | 				app.Selectors = tc.selectors | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			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, | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 			var gotErr string | ||||||
|  | 			if syncErr != nil { | ||||||
|  | 				gotErr = syncErr.Error() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if d := cmp.Diff(tc.error, gotErr); d != "" { | ||||||
|  | 				t.Fatalf("unexpected error: want (-), got (+): %s", d) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(wantUpgrades) > len(helm.Releases) { | ||||||
|  | 				t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades)) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for relIdx := range wantUpgrades { | ||||||
|  | 				if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name { | ||||||
|  | 					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name) | ||||||
|  | 				} | ||||||
|  | 				for flagIdx := range wantUpgrades[relIdx].Flags { | ||||||
|  | 					if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] { | ||||||
|  | 						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx]) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(wantDeletes) > len(helm.Deleted) { | ||||||
|  | 				t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes)) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for relIdx := range wantDeletes { | ||||||
|  | 				if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name { | ||||||
|  | 					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name) | ||||||
|  | 				} | ||||||
|  | 				for flagIdx := range wantDeletes[relIdx].Flags { | ||||||
|  | 					if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { | ||||||
|  | 						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 
 | ||||||
|  | 		if tc.log != "" { | ||||||
|  | 			actual := bs.String() | ||||||
|  | 
 | ||||||
|  | 			diff, exists := testhelper.Diff(tc.log, actual, 3) | ||||||
|  | 			if exists { | ||||||
|  | 				t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.Run("skip-needs=true", func(t *testing.T) { | ||||||
|  | 		check(t, testcase{ | ||||||
|  | 			fields: fields{ | ||||||
|  | 				skipNeeds: true, | ||||||
|  | 			}, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test"}, | ||||||
|  | 			upgraded: []exectest.Release{ | ||||||
|  | 				{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}}, | ||||||
|  | 				{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}}, | ||||||
|  | 			}, | ||||||
|  | 			// 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | Affected releases are: | ||||||
|  |   external-secrets (incubator/raw) UPDATED | ||||||
|  |   my-release (incubator/raw) UPDATED | ||||||
|  | 
 | ||||||
|  | processing 2 groups of releases in this order: | ||||||
|  | GROUP RELEASES | ||||||
|  | 1     default/external-secrets | ||||||
|  | 2     default/my-release | ||||||
|  | 
 | ||||||
|  | processing releases in group 1/2: default/external-secrets | ||||||
|  | getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | processing releases in group 2/2: default/my-release | ||||||
|  | getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | 
 | ||||||
|  | UPDATED RELEASES: | ||||||
|  | NAME               CHART           VERSION | ||||||
|  | external-secrets   incubator/raw           | ||||||
|  | my-release         incubator/raw           | ||||||
|  | 
 | ||||||
|  | `, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("skip-needs=false include-needs=true", func(t *testing.T) { | ||||||
|  | 		check(t, testcase{ | ||||||
|  | 			fields: fields{ | ||||||
|  | 				skipNeeds:    false, | ||||||
|  | 				includeNeeds: true, | ||||||
|  | 			}, | ||||||
|  | 			error: ``, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test"}, | ||||||
|  | 			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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | Affected releases are: | ||||||
|  |   external-secrets (incubator/raw) UPDATED | ||||||
|  |   kubernetes-external-secrets (incubator/raw) UPDATED | ||||||
|  |   my-release (incubator/raw) UPDATED | ||||||
|  | 
 | ||||||
|  | processing 3 groups of releases in this order: | ||||||
|  | GROUP RELEASES | ||||||
|  | 1     kube-system/kubernetes-external-secrets | ||||||
|  | 2     default/external-secrets | ||||||
|  | 3     default/my-release | ||||||
|  | 
 | ||||||
|  | processing releases in group 1/3: kube-system/kubernetes-external-secrets | ||||||
|  | getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | processing releases in group 2/3: default/external-secrets | ||||||
|  | getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | processing releases in group 3/3: default/my-release | ||||||
|  | getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | 
 | ||||||
|  | UPDATED RELEASES: | ||||||
|  | NAME                          CHART           VERSION | ||||||
|  | kubernetes-external-secrets   incubator/raw           | ||||||
|  | external-secrets              incubator/raw           | ||||||
|  | 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, | ||||||
|  | 			}, | ||||||
|  | 			error: ``, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  |   installed: false | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test"}, | ||||||
|  | 			upgraded:  []exectest.Release{}, | ||||||
|  | 			lists: map[exectest.ListKey]string{ | ||||||
|  | 				// delete frontend-v1 and backend-v1
 | ||||||
|  | 				exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE | ||||||
|  | ^kubernetes-external-secrets$ 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			// 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:   installed: false | ||||||
|  |  8:  | ||||||
|  |  9: - name: external-secrets | ||||||
|  | 10:   chart: incubator/raw | ||||||
|  | 11:   namespace: default | ||||||
|  | 12:   labels: | ||||||
|  | 13:     app: test | ||||||
|  | 14:   needs: | ||||||
|  | 15:   - kube-system/kubernetes-external-secrets | ||||||
|  | 16:  | ||||||
|  | 17: - name: my-release | ||||||
|  | 18:   chart: incubator/raw | ||||||
|  | 19:   namespace: default | ||||||
|  | 20:   labels: | ||||||
|  | 21:     app: test | ||||||
|  | 22:   needs: | ||||||
|  | 23:   - default/external-secrets | ||||||
|  | 24:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:   installed: false | ||||||
|  |  8:  | ||||||
|  |  9: - name: external-secrets | ||||||
|  | 10:   chart: incubator/raw | ||||||
|  | 11:   namespace: default | ||||||
|  | 12:   labels: | ||||||
|  | 13:     app: test | ||||||
|  | 14:   needs: | ||||||
|  | 15:   - kube-system/kubernetes-external-secrets | ||||||
|  | 16:  | ||||||
|  | 17: - name: my-release | ||||||
|  | 18:   chart: incubator/raw | ||||||
|  | 19:   namespace: default | ||||||
|  | 20:   labels: | ||||||
|  | 21:     app: test | ||||||
|  | 22:   needs: | ||||||
|  | 23:   - default/external-secrets | ||||||
|  | 24:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | Affected releases are: | ||||||
|  |   external-secrets (incubator/raw) UPDATED | ||||||
|  |   kubernetes-external-secrets (incubator/raw) DELETED | ||||||
|  |   my-release (incubator/raw) UPDATED | ||||||
|  | 
 | ||||||
|  | processing 1 groups of releases in this order: | ||||||
|  | GROUP RELEASES | ||||||
|  | 1     kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | processing releases in group 1/1: kube-system/kubernetes-external-secrets | ||||||
|  | processing 2 groups of releases in this order: | ||||||
|  | GROUP RELEASES | ||||||
|  | 1     default/external-secrets | ||||||
|  | 2     default/my-release | ||||||
|  | 
 | ||||||
|  | processing releases in group 1/2: default/external-secrets | ||||||
|  | getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | processing releases in group 2/2: default/my-release | ||||||
|  | getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | 
 | ||||||
|  | UPDATED RELEASES: | ||||||
|  | NAME               CHART           VERSION | ||||||
|  | external-secrets   incubator/raw           | ||||||
|  | my-release         incubator/raw           | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | DELETED RELEASES: | ||||||
|  | NAME | ||||||
|  | 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, | ||||||
|  | 			}, | ||||||
|  | 			error: ``, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  |   installed: false | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test"}, | ||||||
|  | 			upgraded:  []exectest.Release{}, | ||||||
|  | 			lists: map[exectest.ListKey]string{ | ||||||
|  | 				// delete frontend-v1 and backend-v1
 | ||||||
|  | 				exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: "--kube-contextdefault--deployed--failed--pending"}: ``, | ||||||
|  | 			}, | ||||||
|  | 			// 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:   installed: false | ||||||
|  |  8:  | ||||||
|  |  9: - name: external-secrets | ||||||
|  | 10:   chart: incubator/raw | ||||||
|  | 11:   namespace: default | ||||||
|  | 12:   labels: | ||||||
|  | 13:     app: test | ||||||
|  | 14:   needs: | ||||||
|  | 15:   - kube-system/kubernetes-external-secrets | ||||||
|  | 16:  | ||||||
|  | 17: - name: my-release | ||||||
|  | 18:   chart: incubator/raw | ||||||
|  | 19:   namespace: default | ||||||
|  | 20:   labels: | ||||||
|  | 21:     app: test | ||||||
|  | 22:   needs: | ||||||
|  | 23:   - default/external-secrets | ||||||
|  | 24:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:   installed: false | ||||||
|  |  8:  | ||||||
|  |  9: - name: external-secrets | ||||||
|  | 10:   chart: incubator/raw | ||||||
|  | 11:   namespace: default | ||||||
|  | 12:   labels: | ||||||
|  | 13:     app: test | ||||||
|  | 14:   needs: | ||||||
|  | 15:   - kube-system/kubernetes-external-secrets | ||||||
|  | 16:  | ||||||
|  | 17: - name: my-release | ||||||
|  | 18:   chart: incubator/raw | ||||||
|  | 19:   namespace: default | ||||||
|  | 20:   labels: | ||||||
|  | 21:     app: test | ||||||
|  | 22:   needs: | ||||||
|  | 23:   - default/external-secrets | ||||||
|  | 24:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | Affected releases are: | ||||||
|  |   external-secrets (incubator/raw) UPDATED | ||||||
|  |   my-release (incubator/raw) UPDATED | ||||||
|  | 
 | ||||||
|  | processing 2 groups of releases in this order: | ||||||
|  | GROUP RELEASES | ||||||
|  | 1     default/external-secrets | ||||||
|  | 2     default/my-release | ||||||
|  | 
 | ||||||
|  | processing releases in group 1/2: default/external-secrets | ||||||
|  | getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | processing releases in group 2/2: default/my-release | ||||||
|  | getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | 
 | ||||||
|  | UPDATED RELEASES: | ||||||
|  | NAME               CHART           VERSION | ||||||
|  | external-secrets   incubator/raw           | ||||||
|  | my-release         incubator/raw           | ||||||
|  | 
 | ||||||
|  | `, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("bad --selector", func(t *testing.T) { | ||||||
|  | 		check(t, testcase{ | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test_non_existent"}, | ||||||
|  | 			upgraded:  []exectest.Release{}, | ||||||
|  | 			error:     "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile", | ||||||
|  | 			// 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 0 release(s) matching app=test_non_existent found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | `, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -2245,6 +2245,9 @@ type configImpl struct { | ||||||
| 	skipCleanup bool | 	skipCleanup bool | ||||||
| 	skipCRDs    bool | 	skipCRDs    bool | ||||||
| 	skipDeps    bool | 	skipDeps    bool | ||||||
|  | 
 | ||||||
|  | 	skipNeeds    bool | ||||||
|  | 	includeNeeds bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a configImpl) Selectors() []string { | func (a configImpl) Selectors() []string { | ||||||
|  | @ -2279,6 +2282,14 @@ func (c configImpl) SkipDeps() bool { | ||||||
| 	return c.skipDeps | 	return c.skipDeps | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c configImpl) SkipNeeds() bool { | ||||||
|  | 	return c.skipNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) IncludeNeeds() bool { | ||||||
|  | 	return c.includeNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c configImpl) OutputDir() string { | func (c configImpl) OutputDir() string { | ||||||
| 	return "output/subdir" | 	return "output/subdir" | ||||||
| } | } | ||||||
|  | @ -2312,6 +2323,8 @@ type applyConfig struct { | ||||||
| 	skipCleanup       bool | 	skipCleanup       bool | ||||||
| 	skipCRDs          bool | 	skipCRDs          bool | ||||||
| 	skipDeps          bool | 	skipDeps          bool | ||||||
|  | 	skipNeeds         bool | ||||||
|  | 	includeNeeds      bool | ||||||
| 	includeTests      bool | 	includeTests      bool | ||||||
| 	suppressSecrets   bool | 	suppressSecrets   bool | ||||||
| 	showSecrets       bool | 	showSecrets       bool | ||||||
|  | @ -2363,6 +2376,14 @@ func (a applyConfig) SkipDeps() bool { | ||||||
| 	return a.skipDeps | 	return a.skipDeps | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c applyConfig) SkipNeeds() bool { | ||||||
|  | 	return c.skipNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c applyConfig) IncludeNeeds() bool { | ||||||
|  | 	return c.includeNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (a applyConfig) IncludeTests() bool { | func (a applyConfig) IncludeTests() bool { | ||||||
| 	return a.includeTests | 	return a.includeTests | ||||||
| } | } | ||||||
|  | @ -2696,9 +2717,14 @@ releases: | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestApply(t *testing.T) { | func TestApply(t *testing.T) { | ||||||
|  | 	type fields struct { | ||||||
|  | 		skipNeeds    bool | ||||||
|  | 		includeNeeds bool | ||||||
|  | 	} | ||||||
| 	testcases := []struct { | 	testcases := []struct { | ||||||
| 		name              string | 		name              string | ||||||
| 		loc               string | 		loc               string | ||||||
|  | 		fields            fields | ||||||
| 		ns                string | 		ns                string | ||||||
| 		concurrency       int | 		concurrency       int | ||||||
| 		skipDiffOnInstall bool | 		skipDiffOnInstall bool | ||||||
|  | @ -2946,26 +2972,20 @@ Affected releases are: | ||||||
|   logging (charts/fluent-bit) UPDATED |   logging (charts/fluent-bit) UPDATED | ||||||
|   servicemesh (charts/istio) UPDATED |   servicemesh (charts/istio) UPDATED | ||||||
| 
 | 
 | ||||||
| processing 5 groups of releases in this order: | processing 2 groups of releases in this order: | ||||||
| GROUP RELEASES | GROUP RELEASES | ||||||
| 1     frontend-v1, frontend-v2, frontend-v3 | 1     frontend-v1 | ||||||
| 2     backend-v1, backend-v2 | 2     backend-v1 | ||||||
| 3     anotherbackend |  | ||||||
| 4     database, servicemesh |  | ||||||
| 5     logging, front-proxy |  | ||||||
| 
 | 
 | ||||||
| processing releases in group 1/5: frontend-v1, frontend-v2, frontend-v3 | processing releases in group 1/2: frontend-v1 | ||||||
| processing releases in group 2/5: backend-v1, backend-v2 | processing releases in group 2/2: backend-v1 | ||||||
| processing releases in group 3/5: anotherbackend |  | ||||||
| processing releases in group 4/5: database, servicemesh |  | ||||||
| processing releases in group 5/5: logging, front-proxy |  | ||||||
| processing 5 groups of releases in this order: | processing 5 groups of releases in this order: | ||||||
| GROUP RELEASES | GROUP RELEASES | ||||||
| 1     logging, front-proxy | 1     logging, front-proxy | ||||||
| 2     database, servicemesh | 2     database, servicemesh | ||||||
| 3     anotherbackend | 3     anotherbackend | ||||||
| 4     backend-v1, backend-v2 | 4     backend-v2 | ||||||
| 5     frontend-v1, frontend-v2, frontend-v3 | 5     frontend-v3 | ||||||
| 
 | 
 | ||||||
| processing releases in group 1/5: logging, front-proxy | processing releases in group 1/5: logging, front-proxy | ||||||
| getting deployed release version failed:unexpected list key: {^logging$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^logging$ --kube-contextdefault--deployed--failed--pending} | ||||||
|  | @ -2975,9 +2995,9 @@ getting deployed release version failed:unexpected list key: {^database$ --kube- | ||||||
| getting deployed release version failed:unexpected list key: {^servicemesh$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^servicemesh$ --kube-contextdefault--deployed--failed--pending} | ||||||
| processing releases in group 3/5: anotherbackend | processing releases in group 3/5: anotherbackend | ||||||
| getting deployed release version failed:unexpected list key: {^anotherbackend$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^anotherbackend$ --kube-contextdefault--deployed--failed--pending} | ||||||
| processing releases in group 4/5: backend-v1, backend-v2 | processing releases in group 4/5: backend-v2 | ||||||
| getting deployed release version failed:unexpected list key: {^backend-v2$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^backend-v2$ --kube-contextdefault--deployed--failed--pending} | ||||||
| processing releases in group 5/5: frontend-v1, frontend-v2, frontend-v3 | processing releases in group 5/5: frontend-v3 | ||||||
| getting deployed release version failed:unexpected list key: {^frontend-v3$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^frontend-v3$ --kube-contextdefault--deployed--failed--pending} | ||||||
| 
 | 
 | ||||||
| UPDATED RELEASES: | UPDATED RELEASES: | ||||||
|  | @ -3814,8 +3834,11 @@ bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	defau | ||||||
| 		//
 | 		//
 | ||||||
| 		{ | 		{ | ||||||
| 			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
 | 			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
 | ||||||
| 			name: "upgrades with good selector", | 			name: "upgrades with good selector with --skip-needs=true", | ||||||
| 			loc:  location(), | 			loc:  location(), | ||||||
|  | 			fields: fields{ | ||||||
|  | 				skipNeeds: true, | ||||||
|  | 			}, | ||||||
| 			files: map[string]string{ | 			files: map[string]string{ | ||||||
| 				"/path/to/helmfile.yaml": ` | 				"/path/to/helmfile.yaml": ` | ||||||
| {{ $mark := "a" }} | {{ $mark := "a" }} | ||||||
|  | @ -3920,16 +3943,14 @@ Affected releases are: | ||||||
|   external-secrets (incubator/raw) UPDATED |   external-secrets (incubator/raw) UPDATED | ||||||
|   my-release (incubator/raw) UPDATED |   my-release (incubator/raw) UPDATED | ||||||
| 
 | 
 | ||||||
| processing 3 groups of releases in this order: | processing 2 groups of releases in this order: | ||||||
| GROUP RELEASES | GROUP RELEASES | ||||||
| 1     kube-system/kubernetes-external-secrets | 1     default/external-secrets | ||||||
| 2     default/external-secrets | 2     default/my-release | ||||||
| 3     default/my-release |  | ||||||
| 
 | 
 | ||||||
| processing releases in group 1/3: kube-system/kubernetes-external-secrets | processing releases in group 1/2: default/external-secrets | ||||||
| processing releases in group 2/3: default/external-secrets |  | ||||||
| getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deployed--failed--pending} | ||||||
| processing releases in group 3/3: default/my-release | processing releases in group 2/2: default/my-release | ||||||
| getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deployed--failed--pending} | ||||||
| 
 | 
 | ||||||
| UPDATED RELEASES: | UPDATED RELEASES: | ||||||
|  | @ -3937,6 +3958,115 @@ NAME               CHART           VERSION | ||||||
| external-secrets   incubator/raw           | external-secrets   incubator/raw           | ||||||
| my-release         incubator/raw           | my-release         incubator/raw           | ||||||
| 
 | 
 | ||||||
|  | `, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
 | ||||||
|  | 			name: "upgrades with good selector with --skip-needs=false --include-needs=true", | ||||||
|  | 			loc:  location(), | ||||||
|  | 			fields: fields{ | ||||||
|  | 				skipNeeds:    false, | ||||||
|  | 				includeNeeds: true, | ||||||
|  | 			}, | ||||||
|  | 			error: `in ./helmfile.yaml: release "default/external-secrets" depends on "kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors: []string{"app=test"}, | ||||||
|  | 			diffs: map[exectest.DiffKey]error{ | ||||||
|  | 				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, | ||||||
|  | 				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2}, | ||||||
|  | 			}, | ||||||
|  | 			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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | err: release "default/external-secrets" depends on "kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies | ||||||
| `, | `, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | @ -4102,10 +4232,6 @@ second-pass rendering result of "helmfile.yaml.part.0": | ||||||
| merged environment: &{default map[] map[]} | merged environment: &{default map[] map[]} | ||||||
| 2 release(s) found in helmfile.yaml | 2 release(s) found in helmfile.yaml | ||||||
| 
 | 
 | ||||||
| Affected releases are: |  | ||||||
|   baz (mychart3) UPDATED |  | ||||||
|   foo (mychart1) UPDATED |  | ||||||
| 
 |  | ||||||
| err: "foo" depends on nonexistent release "bar" | err: "foo" depends on nonexistent release "bar" | ||||||
| `, | `, | ||||||
| 		}, | 		}, | ||||||
|  | @ -4185,6 +4311,7 @@ err: "foo" depends on nonexistent release "bar" | ||||||
| 					concurrency:       tc.concurrency, | 					concurrency:       tc.concurrency, | ||||||
| 					logger:            logger, | 					logger:            logger, | ||||||
| 					skipDiffOnInstall: tc.skipDiffOnInstall, | 					skipDiffOnInstall: tc.skipDiffOnInstall, | ||||||
|  | 					skipNeeds:         tc.fields.skipNeeds, | ||||||
| 				}) | 				}) | ||||||
| 				if tc.error == "" && applyErr != nil { | 				if tc.error == "" && applyErr != nil { | ||||||
| 					t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, applyErr) | 					t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, applyErr) | ||||||
|  |  | ||||||
|  | @ -59,6 +59,9 @@ type ApplyConfigProvider interface { | ||||||
| 	SkipCleanup() bool | 	SkipCleanup() bool | ||||||
| 	SkipDiffOnInstall() bool | 	SkipDiffOnInstall() bool | ||||||
| 
 | 
 | ||||||
|  | 	SkipNeeds() bool | ||||||
|  | 	IncludeNeeds() bool | ||||||
|  | 
 | ||||||
| 	concurrencyConfig | 	concurrencyConfig | ||||||
| 	interactive | 	interactive | ||||||
| 	loggingConfig | 	loggingConfig | ||||||
|  | @ -74,6 +77,9 @@ type SyncConfigProvider interface { | ||||||
| 	Wait() bool | 	Wait() bool | ||||||
| 	WaitForJobs() bool | 	WaitForJobs() bool | ||||||
| 
 | 
 | ||||||
|  | 	SkipNeeds() bool | ||||||
|  | 	IncludeNeeds() bool | ||||||
|  | 
 | ||||||
| 	concurrencyConfig | 	concurrencyConfig | ||||||
| 	loggingConfig | 	loggingConfig | ||||||
| } | } | ||||||
|  | @ -92,6 +98,9 @@ type DiffConfigProvider interface { | ||||||
| 	ShowSecrets() bool | 	ShowSecrets() bool | ||||||
| 	SuppressDiff() bool | 	SuppressDiff() bool | ||||||
| 
 | 
 | ||||||
|  | 	SkipNeeds() bool | ||||||
|  | 	IncludeNeeds() bool | ||||||
|  | 
 | ||||||
| 	DetailedExitcode() bool | 	DetailedExitcode() bool | ||||||
| 	NoColor() bool | 	NoColor() bool | ||||||
| 	Context() int | 	Context() int | ||||||
|  | @ -155,6 +164,7 @@ type TemplateConfigProvider interface { | ||||||
| 	SkipCleanup() bool | 	SkipCleanup() bool | ||||||
| 	OutputDir() string | 	OutputDir() string | ||||||
| 	IncludeCRDs() bool | 	IncludeCRDs() bool | ||||||
|  | 	IncludeNeeds() bool | ||||||
| 
 | 
 | ||||||
| 	concurrencyConfig | 	concurrencyConfig | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
| 	"github.com/roboll/helmfile/pkg/exectest" | 	"github.com/roboll/helmfile/pkg/exectest" | ||||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | 	"github.com/roboll/helmfile/pkg/helmexec" | ||||||
| 	"github.com/roboll/helmfile/pkg/testhelper" | 	"github.com/roboll/helmfile/pkg/testhelper" | ||||||
|  | @ -24,6 +25,8 @@ type diffConfig struct { | ||||||
| 	skipCRDs          bool | 	skipCRDs          bool | ||||||
| 	skipDeps          bool | 	skipDeps          bool | ||||||
| 	includeTests      bool | 	includeTests      bool | ||||||
|  | 	includeNeeds      bool | ||||||
|  | 	skipNeeds         bool | ||||||
| 	suppressSecrets   bool | 	suppressSecrets   bool | ||||||
| 	showSecrets       bool | 	showSecrets       bool | ||||||
| 	suppressDiff      bool | 	suppressDiff      bool | ||||||
|  | @ -63,6 +66,14 @@ func (a diffConfig) IncludeTests() bool { | ||||||
| 	return a.includeTests | 	return a.includeTests | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (a diffConfig) IncludeNeeds() bool { | ||||||
|  | 	return a.includeNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a diffConfig) SkipNeeds() bool { | ||||||
|  | 	return a.skipNeeds | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (a diffConfig) SuppressSecrets() bool { | func (a diffConfig) SuppressSecrets() bool { | ||||||
| 	return a.suppressSecrets | 	return a.suppressSecrets | ||||||
| } | } | ||||||
|  | @ -104,6 +115,10 @@ func (a diffConfig) RetainValuesFiles() bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestDiff(t *testing.T) { | func TestDiff(t *testing.T) { | ||||||
|  | 	type flags struct { | ||||||
|  | 		skipNeeds bool | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	testcases := []struct { | 	testcases := []struct { | ||||||
| 		name             string | 		name             string | ||||||
| 		loc              string | 		loc              string | ||||||
|  | @ -111,6 +126,7 @@ func TestDiff(t *testing.T) { | ||||||
| 		concurrency      int | 		concurrency      int | ||||||
| 		detailedExitcode bool | 		detailedExitcode bool | ||||||
| 		error            string | 		error            string | ||||||
|  | 		flags            flags | ||||||
| 		files            map[string]string | 		files            map[string]string | ||||||
| 		selectors        []string | 		selectors        []string | ||||||
| 		lists            map[exectest.ListKey]string | 		lists            map[exectest.ListKey]string | ||||||
|  | @ -899,8 +915,9 @@ bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	defau | ||||||
| 		//
 | 		//
 | ||||||
| 		{ | 		{ | ||||||
| 			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
 | 			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
 | ||||||
| 			name: "upgrades with good selector", | 			name:  "upgrades with good selector with --skip-needs=true", | ||||||
| 			loc:  location(), | 			loc:   location(), | ||||||
|  | 			flags: flags{skipNeeds: true}, | ||||||
| 			files: map[string]string{ | 			files: map[string]string{ | ||||||
| 				"/path/to/helmfile.yaml": ` | 				"/path/to/helmfile.yaml": ` | ||||||
| {{ $mark := "a" }} | {{ $mark := "a" }} | ||||||
|  | @ -1004,6 +1021,112 @@ Affected releases are: | ||||||
|   external-secrets (incubator/raw) UPDATED |   external-secrets (incubator/raw) UPDATED | ||||||
|   my-release (incubator/raw) UPDATED |   my-release (incubator/raw) UPDATED | ||||||
| 
 | 
 | ||||||
|  | `, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:  "upgrades with good selector with --skip-needs=false", | ||||||
|  | 			loc:   location(), | ||||||
|  | 			flags: flags{skipNeeds: false}, | ||||||
|  | 			files: map[string]string{ | ||||||
|  | 				"/path/to/helmfile.yaml": ` | ||||||
|  | {{ $mark := "a" }} | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: kubernetes-external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: kube-system | ||||||
|  | 
 | ||||||
|  | - name: external-secrets | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - kube-system/kubernetes-external-secrets | ||||||
|  | 
 | ||||||
|  | - name: my-release | ||||||
|  |   chart: incubator/raw | ||||||
|  |   namespace: default | ||||||
|  |   labels: | ||||||
|  |     app: test | ||||||
|  |   needs: | ||||||
|  |   - default/external-secrets | ||||||
|  | `, | ||||||
|  | 			}, | ||||||
|  | 			selectors:        []string{"app=test"}, | ||||||
|  | 			detailedExitcode: true, | ||||||
|  | 			diffs: map[exectest.DiffKey]error{ | ||||||
|  | 				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, | ||||||
|  | 				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2}, | ||||||
|  | 			}, | ||||||
|  | 			upgraded: []exectest.Release{}, | ||||||
|  | 			// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
 | ||||||
|  | 			concurrency: 1, | ||||||
|  | 			error:       `in ./helmfile.yaml: release "default/external-secrets" depends on "kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`, | ||||||
|  | 			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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | 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: kubernetes-external-secrets | ||||||
|  |  5:   chart: incubator/raw | ||||||
|  |  6:   namespace: kube-system | ||||||
|  |  7:  | ||||||
|  |  8: - name: external-secrets | ||||||
|  |  9:   chart: incubator/raw | ||||||
|  | 10:   namespace: default | ||||||
|  | 11:   labels: | ||||||
|  | 12:     app: test | ||||||
|  | 13:   needs: | ||||||
|  | 14:   - kube-system/kubernetes-external-secrets | ||||||
|  | 15:  | ||||||
|  | 16: - name: my-release | ||||||
|  | 17:   chart: incubator/raw | ||||||
|  | 18:   namespace: default | ||||||
|  | 19:   labels: | ||||||
|  | 20:     app: test | ||||||
|  | 21:   needs: | ||||||
|  | 22:   - default/external-secrets | ||||||
|  | 23:  | ||||||
|  | 
 | ||||||
|  | merged environment: &{default map[] map[]} | ||||||
|  | 2 release(s) matching app=test found in helmfile.yaml | ||||||
|  | 
 | ||||||
|  | err: release "default/external-secrets" depends on "kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies | ||||||
| `, | `, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | @ -1250,13 +1373,16 @@ err: "foo" depends on nonexistent release "bar" | ||||||
| 					concurrency:      tc.concurrency, | 					concurrency:      tc.concurrency, | ||||||
| 					logger:           logger, | 					logger:           logger, | ||||||
| 					detailedExitcode: tc.detailedExitcode, | 					detailedExitcode: tc.detailedExitcode, | ||||||
|  | 					skipNeeds:        tc.flags.skipNeeds, | ||||||
| 				}) | 				}) | ||||||
| 				if tc.error == "" && diffErr != nil { | 
 | ||||||
| 					t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, diffErr) | 				var diffErrStr string | ||||||
| 				} else if tc.error != "" && diffErr == nil { | 				if diffErr != nil { | ||||||
| 					t.Fatalf("expected error did not occur for data defined at %s", tc.loc) | 					diffErrStr = diffErr.Error() | ||||||
| 				} else if tc.error != "" && diffErr != nil && tc.error != diffErr.Error() { | 				} | ||||||
| 					t.Fatalf("invalid error: expected %q, got %q", tc.error, diffErr.Error()) | 
 | ||||||
|  | 				if d := cmp.Diff(tc.error, diffErrStr); d != "" { | ||||||
|  | 					t.Fatalf("invalid error: want (-), got (+): %s", d) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if len(wantUpgrades) > len(helm.Releases) { | 				if len(wantUpgrades) > len(helm.Releases) { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| package state | package state | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"sort" | 	"sort" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | 	"github.com/roboll/helmfile/pkg/helmexec" | ||||||
|  | @ -97,17 +99,20 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int, | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PlanOpts struct { | type PlanOptions struct { | ||||||
| 	Reverse bool | 	Reverse          bool | ||||||
|  | 	IncludeNeeds     bool | ||||||
|  | 	SkipNeeds        bool | ||||||
|  | 	SelectedReleases []ReleaseSpec | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) { | func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) { | ||||||
| 	marked, err := st.SelectReleasesWithOverrides() | 	marked, err := st.SelectReleasesWithOverrides() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	groups, err := SortedReleaseGroups(marked, reverse) | 	groups, err := SortedReleaseGroups(marked, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -115,8 +120,10 @@ func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) { | ||||||
| 	return groups, nil | 	return groups, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func SortedReleaseGroups(releases []Release, reverse bool) ([][]Release, error) { | func SortedReleaseGroups(releases []Release, opts PlanOptions) ([][]Release, error) { | ||||||
| 	groups, err := GroupReleasesByDependency(releases) | 	reverse := opts.Reverse | ||||||
|  | 
 | ||||||
|  | 	groups, err := GroupReleasesByDependency(releases, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -130,7 +137,7 @@ func SortedReleaseGroups(releases []Release, reverse bool) ([][]Release, error) | ||||||
| 	return groups, nil | 	return groups, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func GroupReleasesByDependency(releases []Release) ([][]Release, error) { | func GroupReleasesByDependency(releases []Release, opts PlanOptions) ([][]Release, error) { | ||||||
| 	idToReleases := map[string][]Release{} | 	idToReleases := map[string][]Release{} | ||||||
| 	idToIndex := map[string]int{} | 	idToIndex := map[string]int{} | ||||||
| 
 | 
 | ||||||
|  | @ -159,8 +166,59 @@ func GroupReleasesByDependency(releases []Release) ([][]Release, error) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	plan, err := d.Plan() | 	var selectedReleaseIDs []string | ||||||
|  | 
 | ||||||
|  | 	for _, r := range opts.SelectedReleases { | ||||||
|  | 		id := ReleaseToID(&r) | ||||||
|  | 		selectedReleaseIDs = append(selectedReleaseIDs, id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	plan, err := d.Plan(dag.SortOptions{ | ||||||
|  | 		Only:                selectedReleaseIDs, | ||||||
|  | 		WithDependencies:    opts.IncludeNeeds, | ||||||
|  | 		WithoutDependencies: opts.SkipNeeds, | ||||||
|  | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		if ude, ok := err.(*dag.UnhandledDependencyError); ok { | ||||||
|  | 			msgs := make([]string, len(ude.UnhandledDependencies)) | ||||||
|  | 			for i, ud := range ude.UnhandledDependencies { | ||||||
|  | 				id := ud.Id | ||||||
|  | 
 | ||||||
|  | 				ds := make([]string, len(ud.Dependents)) | ||||||
|  | 				for i, d := range ud.Dependents { | ||||||
|  | 					ds[i] = fmt.Sprintf("%q", d) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var dsHumanized string | ||||||
|  | 				if len(ds) < 3 { | ||||||
|  | 					dsHumanized = strings.Join(ds, " and ") | ||||||
|  | 				} else { | ||||||
|  | 					dsHumanized = strings.Join(ds[:len(ds)-1], ", ") | ||||||
|  | 					dsHumanized += ", and " + ds[len(ds)-1] | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var verb string | ||||||
|  | 				if len(ds) == 1 { | ||||||
|  | 					verb = "depends" | ||||||
|  | 				} else { | ||||||
|  | 					verb = "depend" | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				idComponents := strings.Split(id, "/") | ||||||
|  | 				name := idComponents[len(idComponents)-1] | ||||||
|  | 
 | ||||||
|  | 				msg := fmt.Sprintf( | ||||||
|  | 					"release %s %s on %q which does not match the selectors. "+ | ||||||
|  | 						"Please add a selector like \"--selector name=%s\", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies", | ||||||
|  | 					dsHumanized, | ||||||
|  | 					verb, | ||||||
|  | 					id, | ||||||
|  | 					name, | ||||||
|  | 				) | ||||||
|  | 				msgs[i] = msg | ||||||
|  | 			} | ||||||
|  | 			return nil, errors.New(msgs[0]) | ||||||
|  | 		} | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue