From e24ada8b8d031a6c2b66195eff76346f6da29ea6 Mon Sep 17 00:00:00 2001 From: zhaque44 Date: Mon, 26 May 2025 09:30:21 -0500 Subject: [PATCH 1/8] issue: first draft Signed-off-by: zhaque44 --- cmd/apply.go | 1 + cmd/diff.go | 1 + cmd/sync.go | 1 + cmd/template.go | 1 + pkg/app/app.go | 20 ++++++++++++++++---- pkg/app/app_test.go | 15 ++++++++++----- pkg/app/config.go | 1 + 7 files changed, 31 insertions(+), 9 deletions(-) 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 { From aefde997811c458920953c29d1b28fb3e09f9dbb Mon Sep 17 00:00:00 2001 From: zhaque44 Date: Tue, 27 May 2025 13:55:37 -0500 Subject: [PATCH 2/8] add config lint go Signed-off-by: zhaque44 --- pkg/config/apply.go | 7 +++++++ pkg/config/diff.go | 7 +++++++ pkg/config/lint.go | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/pkg/config/apply.go b/pkg/config/apply.go index 81a9a195..011783e6 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -28,6 +28,8 @@ type ApplyOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is true if the transitive needs should be included IncludeTransitiveNeeds bool + // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + EnforceNeedsAreInstalled bool // SkipDiffOnInstall is true if the diff should be skipped on install SkipDiffOnInstall bool // DiffArgs is the list of arguments to pass to the helm-diff. @@ -139,6 +141,11 @@ func (a *ApplyImpl) IncludeTransitiveNeeds() bool { return a.ApplyOptions.IncludeTransitiveNeeds } +// EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies +func (a *ApplyImpl) EnforceNeedsAreInstalled() bool { + return a.ApplyOptions.EnforceNeedsAreInstalled +} + // ShowSecrets returns the show secrets. func (a *ApplyImpl) ShowSecrets() bool { return a.ApplyOptions.ShowSecrets diff --git a/pkg/config/diff.go b/pkg/config/diff.go index d8830fad..153d87fb 100644 --- a/pkg/config/diff.go +++ b/pkg/config/diff.go @@ -18,6 +18,8 @@ type DiffOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool + // EnforceNeedsAreInstalled is the enforce needs are installed flag + EnforceNeedsAreInstalled bool // SkipDiffOnInstall is the skip diff on install flag SkipDiffOnInstall bool // ShowSecrets is the show secrets flag @@ -87,6 +89,11 @@ func (t *DiffImpl) IncludeTransitiveNeeds() bool { return t.DiffOptions.IncludeTransitiveNeeds } +// EnforceNeedsAreInstalled errors if the transitive dependencies are not installable +func (t *DiffImpl) EnforceNeedsAreInstalled() bool { + return t.DiffOptions.EnforceNeedsAreInstalled +} + // Set returns the Set func (t *DiffImpl) Set() []string { return t.DiffOptions.Set diff --git a/pkg/config/lint.go b/pkg/config/lint.go index 46cf02f8..be4de771 100644 --- a/pkg/config/lint.go +++ b/pkg/config/lint.go @@ -14,6 +14,8 @@ type LintOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool + // EnforceNeedsAreInstalled is the enforce needs are installed flag + EnforceNeedsAreInstalled bool // SkipDeps is the skip deps flag } @@ -66,6 +68,11 @@ func (l *LintImpl) IncludeTransitiveNeeds() bool { return l.LintOptions.IncludeTransitiveNeeds } +// EnforceNeedsAreInstalled errors if the transitive dependencies are not installable +func (l *LintImpl) EnforceNeedsAreInstalled() bool { + return l.LintOptions.EnforceNeedsAreInstalled +} + // SkipNeeds returns the skip needs func (l *LintImpl) SkipNeeds() bool { if !l.IncludeNeeds() { From 190946333dedad69d6d2aacc8a6be503358f9f77 Mon Sep 17 00:00:00 2001 From: zhaque44 Date: Wed, 28 May 2025 13:13:54 -0500 Subject: [PATCH 3/8] issue final fix Signed-off-by: zhaque44 --- pkg/config/sync.go | 7 +++++++ pkg/config/template.go | 7 +++++++ pkg/state/state.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/pkg/config/sync.go b/pkg/config/sync.go index 8d68aba8..5794d581 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -16,6 +16,8 @@ type SyncOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool + // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + EnforceNeedsAreInstalled bool // SkipCrds is the skip crds flag SkipCRDs bool // Wait is the wait flag @@ -82,6 +84,11 @@ func (t *SyncImpl) IncludeTransitiveNeeds() bool { return t.SyncOptions.IncludeTransitiveNeeds } +// EnforceNeedsAreInstalled errors if the transitive dependencies are not installable +func (t *SyncImpl) EnforceNeedsAreInstalled() bool { + return t.SyncOptions.EnforceNeedsAreInstalled +} + // Set returns the Set func (t *SyncImpl) Set() []string { return t.SyncOptions.Set diff --git a/pkg/config/template.go b/pkg/config/template.go index 90c82691..ae7c2439 100644 --- a/pkg/config/template.go +++ b/pkg/config/template.go @@ -30,6 +30,8 @@ type TemplateOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool + // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + EnforceNeedsAreInstalled bool // No-Hooks is the no hooks flag NoHooks bool // SkipCleanup is the skip cleanup flag @@ -158,3 +160,8 @@ func (t *TemplateImpl) KubeVersion() string { func (t *TemplateImpl) ShowOnly() []string { return t.TemplateOptions.ShowOnly } + +// EnforceNeedsAreInstalled errors if the transitive dependencies are not installable +func (t *TemplateImpl) EnforceNeedsAreInstalled() bool { + return t.TemplateOptions.EnforceNeedsAreInstalled +} diff --git a/pkg/state/state.go b/pkg/state/state.go index d8e4013c..f5214d67 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -2657,6 +2657,34 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds return nil } +// DependencyError holds information about a release and its dependency that is not installed. +type DependencyError struct { + Release ReleaseSpec + Dependency ReleaseSpec +} + +// hasTransitiveDependencyWithInstallFalse checks if a release has a transitive dependency with Install set to false. +func (st *HelmState) HasTransitiveDependencyWithInstalledFalse(release ReleaseSpec, visited map[string]bool) *DependencyError { + if visited[release.Name] { + return nil + } + visited[release.Name] = true + + for _, dep := range release.Needs { + for _, r := range st.Releases { + if r.Name == dep { + if r.Installed != nil && !*r.Installed { + return &DependencyError{Release: release, Dependency: r} + } + if depErr := st.HasTransitiveDependencyWithInstalledFalse(r, visited); depErr != nil { + return depErr + } + } + } + } + return nil +} + // find "Chart.yaml" func findChartDirectory(topLevelDir string) (string, error) { var files []string From bdc00a30643ca1a6225fff04d32e1d8cbe37905f Mon Sep 17 00:00:00 2001 From: Zubair Haque Date: Mon, 27 Oct 2025 09:47:44 -0400 Subject: [PATCH 4/8] Update pkg/app/app.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/app/app.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 26d7a5d4..74eb129d 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1987,8 +1987,11 @@ func (a *App) withNeeds(r *Run, c DAGConfig, includeDisabled bool, f func(*state SkipNeeds: c.SkipNeeds(), }) - var selectedReleasesWithNeeds []state.ReleaseSpec + if err != nil { + return false, []error{err} + } + var selectedReleasesWithNeeds []state.ReleaseSpec for _, rs := range batches { for _, r := range rs { selectedReleasesWithNeeds = append(selectedReleasesWithNeeds, r.ReleaseSpec) From 303c328a4b7a5876e6c3b7ae1c384f3ecedb58c4 Mon Sep 17 00:00:00 2001 From: Zubair Haque Date: Mon, 27 Oct 2025 09:52:52 -0400 Subject: [PATCH 5/8] Update pkg/app/app.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 74eb129d..85cbf0ea 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -2002,7 +2002,7 @@ func (a *App) withNeeds(r *Run, c DAGConfig, includeDisabled bool, f func(*state 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)} + return false, []error{fmt.Errorf("Release %s has a transitive dependency %s marked as installed=false", depErr.Release.Name, depErr.Dependency.Name)} } } } From 801a3e7bfdfc5d25dc0f968b4ee8f11f2edb9396 Mon Sep 17 00:00:00 2001 From: Zubair Haque Date: Mon, 27 Oct 2025 09:53:04 -0400 Subject: [PATCH 6/8] Update pkg/config/template.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/config/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/template.go b/pkg/config/template.go index ae7c2439..90e87ba3 100644 --- a/pkg/config/template.go +++ b/pkg/config/template.go @@ -30,7 +30,7 @@ type TemplateOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool - // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + // EnforceNeedsAreInstalled indicates whether to error when there are unmeetable dependencies EnforceNeedsAreInstalled bool // No-Hooks is the no hooks flag NoHooks bool From 6e466a0150922000beae6e198cee5c5142d0deb6 Mon Sep 17 00:00:00 2001 From: Zubair Haque Date: Mon, 27 Oct 2025 09:53:17 -0400 Subject: [PATCH 7/8] Update pkg/config/apply.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/config/apply.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/apply.go b/pkg/config/apply.go index 011783e6..cfdd3451 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -28,7 +28,7 @@ type ApplyOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is true if the transitive needs should be included IncludeTransitiveNeeds bool - // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + // EnforceNeedsAreInstalled is true if we should error if/when there are unmeetable dependencies EnforceNeedsAreInstalled bool // SkipDiffOnInstall is true if the diff should be skipped on install SkipDiffOnInstall bool From 1af72cb67d3ccc0c2eb2fd96857222d2912d19b2 Mon Sep 17 00:00:00 2001 From: Zubair Haque Date: Mon, 27 Oct 2025 09:57:23 -0400 Subject: [PATCH 8/8] Update pkg/config/sync.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/config/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/sync.go b/pkg/config/sync.go index 5794d581..b59701f1 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -16,7 +16,7 @@ type SyncOptions struct { IncludeNeeds bool // IncludeTransitiveNeeds is the include transitive needs flag IncludeTransitiveNeeds bool - // EnforceNeedsAreInstalled is true we should error if/when there are unmeetable dependencies + // EnforceNeedsAreInstalled indicates whether to error when there are unmeetable dependencies EnforceNeedsAreInstalled bool // SkipCrds is the skip crds flag SkipCRDs bool