From 6411a69ebb3f400ce34bb9a5f514d30c6b4e4f79 Mon Sep 17 00:00:00 2001 From: Simone Tollardo Date: Fri, 19 Jun 2026 11:56:26 +0200 Subject: [PATCH] Exclude content digests from Chart.lock metadata hash HashReq now strips per-dependency digest fields so the top-level lock digest continues to detect Chart.yaml drift independently of tarball bytes. Signed-off-by: Simone Tollardo --- internal/resolver/resolver.go | 25 +++++++++++++++++++++++-- internal/resolver/resolver_test.go | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 5f0c5b148..1bf53b5a8 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -209,9 +209,13 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string // HashReq generates a hash of the dependencies. // // This should be used only to compare against another hash generated by this -// function. +// function. Per-dependency content digests are excluded; the top-level lock +// digest tracks Chart.yaml constraints and resolved metadata only. func HashReq(req, lock []*chart.Dependency) (string, error) { - data, err := json.Marshal([2][]*chart.Dependency{req, lock}) + data, err := json.Marshal([2][]*chart.Dependency{ + dependenciesForMetadataHash(req), + dependenciesForMetadataHash(lock), + }) if err != nil { return "", err } @@ -219,6 +223,23 @@ func HashReq(req, lock []*chart.Dependency) (string, error) { return "sha256:" + s, err } +// dependenciesForMetadataHash returns a copy of deps with content digests cleared. +func dependenciesForMetadataHash(deps []*chart.Dependency) []*chart.Dependency { + if len(deps) == 0 { + return nil + } + out := make([]*chart.Dependency, len(deps)) + for i, d := range deps { + if d == nil { + continue + } + cp := *d + cp.Digest = "" + out[i] = &cp + } + return out +} + // HashV2Req generates a hash of requirements generated in Helm v2. // // This should be used only to compare against another hash generated by the diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index 1e33837a9..d9c395bbf 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -19,6 +19,9 @@ import ( "runtime" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/registry" ) @@ -308,3 +311,21 @@ func TestGetLocalPath(t *testing.T) { }) } } + +func TestHashReqIgnoresContentDigest(t *testing.T) { + req := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, + } + lockWithout := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, + } + lockWith := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts", Digest: "sha256:abc123"}, + } + + h1, err := HashReq(req, lockWithout) + require.NoError(t, err) + h2, err := HashReq(req, lockWith) + require.NoError(t, err) + assert.Equal(t, h1, h2) +}