fix: --include-needs should only include direct dependencies

Fixes #1003

Previously, --include-needs was incorrectly including transitive
dependencies (dependencies of dependencies). This fix ensures that:

- --include-needs only includes direct needs
- --include-transitive-needs includes both direct and transitive needs

Changes:
- Add separate handling for direct vs transitive needs in state.go
- Add IncludeNeeds field to ChartPrepareOptions
- Add unmarkNeedsDirectOnly() and collectDirectNeedsOnly() functions
- Update ForEachState and related functions to accept both flags
- Fix incorrect usage of c.IncludeNeeds() for IncludeTransitiveNeeds
- Update tests to verify the correct behavior

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero
2026-03-18 08:25:32 +08:00
committed by yxxhero
parent 97b3256058
commit 72a8727d17
17 changed files with 135 additions and 89 deletions
+2 -2
View File
@@ -117,7 +117,7 @@ func TestSelectReleasesWithOverrides(t *testing.T) {
}
state.Releases = state.GetReleasesWithLabels()
rs, err := state.GetSelectedReleases(false)
rs, err := state.GetSelectedReleases(false, false)
if err != nil {
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
}
@@ -192,7 +192,7 @@ func TestSelectReleasesWithOverridesWithIncludedTransitives(t *testing.T) {
}
state.Releases = state.GetReleasesWithLabels()
rs, err := state.GetSelectedReleases(tc.includeTransitiveNeeds)
rs, err := state.GetSelectedReleases(tc.includeTransitiveNeeds, tc.includeTransitiveNeeds)
if err != nil {
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
}
+30 -10
View File
@@ -1346,6 +1346,7 @@ type ChartPrepareOptions struct {
WaitForJobs bool
OutputDir string
OutputDirTemplate string
IncludeNeeds bool
IncludeTransitiveNeeds bool
Concurrency int
KubeVersion string
@@ -1822,7 +1823,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
}
*st = *updated
}
selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds)
selected, err := st.GetSelectedReleases(opts.IncludeNeeds, opts.IncludeTransitiveNeeds)
if err != nil {
return nil, []error{err}
}
@@ -2945,16 +2946,16 @@ func (st *HelmState) GetReleasesWithLabels() []ReleaseSpec {
return rs
}
func (st *HelmState) SelectReleases(includeTransitiveNeeds bool) ([]Release, error) {
func (st *HelmState) SelectReleases(includeNeeds bool, includeTransitiveNeeds bool) ([]Release, error) {
values := st.Values()
rs, err := markExcludedReleases(st.Releases, st.Selectors, values, includeTransitiveNeeds)
rs, err := markExcludedReleases(st.Releases, st.Selectors, values, includeNeeds, includeTransitiveNeeds)
if err != nil {
return nil, err
}
return rs, nil
}
func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map[string]any, includeTransitiveNeeds bool) ([]Release, error) {
func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map[string]any, includeNeeds bool, includeTransitiveNeeds bool) ([]Release, error) {
var filteredReleases []Release
filters := []ReleaseFilter{}
for _, label := range selectors {
@@ -2986,6 +2987,8 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map
}
if includeTransitiveNeeds {
unmarkNeedsAndTransitives(filteredReleases, releases)
} else if includeNeeds {
unmarkNeedsDirectOnly(filteredReleases, releases)
}
return filteredReleases, nil
}
@@ -3043,6 +3046,23 @@ func unmarkNeedsAndTransitives(filteredReleases []Release, allReleases []Release
unmarkReleases(needsWithTranstives, filteredReleases)
}
func unmarkNeedsDirectOnly(filteredReleases []Release, allReleases []ReleaseSpec) {
directNeeds := collectDirectNeedsOnly(filteredReleases)
unmarkReleases(directNeeds, filteredReleases)
}
func collectDirectNeedsOnly(filteredReleases []Release) map[string]struct{} {
directNeeds := map[string]struct{}{}
for _, r := range filteredReleases {
if !r.Filtered {
for _, id := range r.ReleaseSpec.Needs {
directNeeds[id] = struct{}{}
}
}
}
return directNeeds
}
func collectAllNeedsWithTransitives(filteredReleases []Release, allReleases []ReleaseSpec) map[string]struct{} {
needsWithTranstives := map[string]struct{}{}
for _, r := range filteredReleases {
@@ -3076,8 +3096,8 @@ func collectNeedsWithTransitives(release ReleaseSpec, allReleases []ReleaseSpec,
}
}
func (st *HelmState) GetSelectedReleases(includeTransitiveNeeds bool) ([]ReleaseSpec, error) {
filteredReleases, err := st.SelectReleases(includeTransitiveNeeds)
func (st *HelmState) GetSelectedReleases(includeNeeds bool, includeTransitiveNeeds bool) ([]ReleaseSpec, error) {
filteredReleases, err := st.SelectReleases(includeNeeds, includeTransitiveNeeds)
if err != nil {
return nil, err
}
@@ -3092,8 +3112,8 @@ func (st *HelmState) GetSelectedReleases(includeTransitiveNeeds bool) ([]Release
}
// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
func (st *HelmState) FilterReleases(includeTransitiveNeeds bool) error {
releases, err := st.GetSelectedReleases(includeTransitiveNeeds)
func (st *HelmState) FilterReleases(includeNeeds bool, includeTransitiveNeeds bool) error {
releases, err := st.GetSelectedReleases(includeNeeds, includeTransitiveNeeds)
if err != nil {
return err
}
@@ -3173,7 +3193,7 @@ func (st *HelmState) ResolveDeps() (*HelmState, error) {
}
// UpdateDeps wrapper for updating dependencies on the releases
func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds bool) []error {
func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeNeeds bool, includeTransitiveNeeds bool) []error {
var selected []ReleaseSpec
if len(st.Selectors) > 0 {
@@ -3181,7 +3201,7 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds
// This and releasesNeedCharts ensures that we run operations like helm-dep-build and prepare-hook calls only on
// releases that are (1) selected by the selectors and (2) to be installed.
selected, err = st.GetSelectedReleases(includeTransitiveNeeds)
selected, err = st.GetSelectedReleases(includeNeeds, includeTransitiveNeeds)
if err != nil {
return []error{err}
}
+1 -1
View File
@@ -99,7 +99,7 @@ type PlanOptions struct {
}
func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) {
marked, err := st.SelectReleases(opts.IncludeTransitiveNeeds)
marked, err := st.SelectReleases(opts.IncludeNeeds, opts.IncludeTransitiveNeeds)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -2589,7 +2589,7 @@ generated: 2019-05-16T15:42:45.50486+09:00
})
fs.Cwd = basePath
state = injectFs(state, fs)
errs := state.UpdateDeps(helm, false)
errs := state.UpdateDeps(helm, false, false)
want := []string{"/example", "./example", generatedDir}
if !reflect.DeepEqual(helm.Charts, want) {
@@ -3058,7 +3058,7 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
RenderedValues: map[string]any{},
}
state.Selectors = []string{tt.labels}
errs := state.FilterReleases(false)
errs := state.FilterReleases(false, false)
if (errs != nil) != tt.wantErr {
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
return