fix: deduplicate chart dependencies in helmfile.lock (#2567)

When multiple releases reference the same chart with the same name,
repository, and version, helmfile deps would write duplicate entries
to helmfile.lock. This adds deduplication of resolved dependencies
after sorting and before writing the lock file.

Fixes #2562

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2026-05-01 20:29:24 +08:00 committed by GitHub
parent d3c68299da
commit dac42105dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 79 additions and 0 deletions

View File

@ -129,6 +129,20 @@ func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, err
return "", fmt.Errorf("no resolved dependency found for \"%s\", running \"helmfile deps\" may resolve the issue", chart)
}
func dedupResolvedDependencies(deps []ResolvedChartDependency) []ResolvedChartDependency {
seen := map[string]bool{}
result := make([]ResolvedChartDependency, 0, len(deps))
for _, dep := range deps {
key := dep.ChartName + "|" + dep.Repository + "|" + dep.Version
if seen[key] {
continue
}
seen[key] = true
result = append(result, dep)
}
return result
}
func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
filename, unresolved := getUnresolvedDependenciess(st)
@ -369,6 +383,8 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre
return lockedReqs.ResolvedDependencies[i].ChartName < lockedReqs.ResolvedDependencies[j].ChartName
})
lockedReqs.ResolvedDependencies = dedupResolvedDependencies(lockedReqs.ResolvedDependencies)
lockedReqs.Version = version.Version()
updatedLockFileContent, err = yaml.Marshal(lockedReqs)

View File

@ -6,6 +6,69 @@ import (
"github.com/stretchr/testify/require"
)
func TestDedupResolvedDependencies(t *testing.T) {
tests := []struct {
name string
input []ResolvedChartDependency
expected []ResolvedChartDependency
}{
{
name: "no duplicates",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"},
},
},
{
name: "duplicates removed",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
},
},
{
name: "same chart different versions kept",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"},
},
},
{
name: "same chart different repos kept",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"},
},
},
{
name: "empty input",
input: []ResolvedChartDependency{},
expected: []ResolvedChartDependency{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := dedupResolvedDependencies(tt.input)
require.Equal(t, tt.expected, result)
})
}
}
func TestGetUnresolvedDependenciess(t *testing.T) {
tests := []struct {
name string