diff --git a/cmd/apply.go b/cmd/apply.go index 6b5a23f4..c47ce83d 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -47,6 +47,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&applyOptions.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present") f.BoolVar(&applyOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`) f.BoolVar(&applyOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`) + f.BoolVar(&applyOptions.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&applyOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&applyOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "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") f.BoolVar(&applyOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks") diff --git a/cmd/diff.go b/cmd/diff.go index c2238704..32267d3e 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -40,6 +40,7 @@ func NewDiffCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&diffOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`) f.BoolVar(&diffOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks") f.BoolVar(&diffOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`) + f.BoolVar(&diffOptions.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&diffOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&diffOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "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") f.BoolVar(&diffOptions.ShowSecrets, "show-secrets", false, "do not redact secret values in the output. should be used for debug purpose only") diff --git a/cmd/sync.go b/cmd/sync.go index 3bd80e6f..6cc31975 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -41,6 +41,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&syncOptions.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present") f.BoolVar(&syncOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`) f.BoolVar(&syncOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) + f.BoolVar(&syncOptions.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&syncOptions.HideNotes, "hide-notes", false, "add --hide-notes flag to helm") f.BoolVar(&syncOptions.TakeOwnership, "take-ownership", false, `add --take-ownership flag to helm`) f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") diff --git a/cmd/template.go b/cmd/template.go index 6cb81fea..8713f5b3 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -43,6 +43,7 @@ func NewTemplateCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&templateOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`) f.BoolVar(&templateOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`) f.BoolVar(&templateOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) + f.BoolVar(&templateOptions.EnforceNeedsAreInstalled, "enforce-needs-are-installed", false, "enforce that all 'needs' dependencies are installable before applying changes") f.BoolVar(&templateOptions.SkipCleanup, "skip-cleanup", false, "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security") f.BoolVar(&templateOptions.NoHooks, "no-hooks", false, "do not template files made by hooks.") f.StringVar(&templateOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) diff --git a/pkg/app/app.go b/pkg/app/app.go index aa73fbb2..26d7a5d4 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1979,10 +1979,13 @@ func (a *App) withNeeds(r *Run, c DAGConfig, includeDisabled bool, f func(*state includeNeeds = true } - batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: includeNeeds, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), SkipNeeds: c.SkipNeeds()}) - if err != nil { - return false, []error{err} - } + batches, err := st.PlanReleases(state.PlanOptions{ + Reverse: false, + SelectedReleases: selectedReleases, + IncludeNeeds: includeNeeds, + IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), + SkipNeeds: c.SkipNeeds(), + }) var selectedReleasesWithNeeds []state.ReleaseSpec @@ -1992,6 +1995,15 @@ func (a *App) withNeeds(r *Run, c DAGConfig, includeDisabled bool, f func(*state } } + if c.EnforceNeedsAreInstalled() { + for _, r := range toRender { + visited := make(map[string]bool) + if depErr := st.HasTransitiveDependencyWithInstalledFalse(r, visited); depErr != nil { + return false, []error{fmt.Errorf("Release %s has a transitive dependency %s marked as install=false", depErr.Release.Name, depErr.Dependency.Name)} + } + } + } + var toRender []state.ReleaseSpec releasesToUninstall := map[string]state.ReleaseSpec{} diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index cf38aa2e..f91fe9b3 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2104,11 +2104,12 @@ type configImpl struct { skipSchemaValidation bool skipRefresh bool - skipNeeds bool - includeNeeds bool - includeTransitiveNeeds bool - skipCharts bool - kubeVersion string + skipNeeds bool + includeNeeds bool + includeTransitiveNeeds bool + enforceNeedsAreInstalled bool + skipCharts bool + kubeVersion string } func (c configImpl) Selectors() []string { @@ -2318,6 +2319,10 @@ func (a applyConfig) SkipNeeds() bool { return a.skipNeeds } +func (a applyConfig) EnforceNeedsAreInstalled() bool { + return a.enforceNeedsAreInstalled +} + func (a applyConfig) IncludeNeeds() bool { return a.includeNeeds || a.IncludeTransitiveNeeds() } diff --git a/pkg/app/config.go b/pkg/app/config.go index e699d86f..f7aabf85 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -242,6 +242,7 @@ type DAGConfig interface { SkipNeeds() bool IncludeNeeds() bool IncludeTransitiveNeeds() bool + EnforceNeedsAreInstalled() bool } type WriteValuesConfigProvider interface {