From 99a52b1d4643813aa422bd9c651edfe3e538fcf1 Mon Sep 17 00:00:00 2001 From: Aleksei Krugliak Date: Mon, 11 May 2026 21:49:04 +0300 Subject: [PATCH] fix: decouple provenance from --repo input; reset downloader state - ChartDownloader.repositoryURL is now cleared at the start of ResolveChartVersion and set in the absolute-URL branch too, so reused downloaders don't report stale URLs. - New ChartPathOptions.ResolvedRepoURL() carries provenance from LocateChart so it no longer overwrites the --repo input field. Install/Upgrade actions stamp metadata.RepoURL from it first, falling back to RepoURL for consumers that bypass LocateChart. - Align RepoURL doc strings in chart v2 and v3 metadata. Signed-off-by: Aleksei Krugliak --- internal/chart/v3/metadata.go | 3 +- pkg/action/install.go | 49 ++++++++++++++++++++++-------- pkg/action/upgrade.go | 11 ++++--- pkg/chart/v2/metadata.go | 3 +- pkg/downloader/chart_downloader.go | 8 +++++ 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/internal/chart/v3/metadata.go b/internal/chart/v3/metadata.go index 30f433a29..6add6b6d6 100644 --- a/internal/chart/v3/metadata.go +++ b/internal/chart/v3/metadata.go @@ -81,7 +81,8 @@ type Metadata struct { Dependencies []*Dependency `json:"dependencies,omitempty"` // Specifies the chart type: application or library Type string `json:"type,omitempty"` - // Specifies the chart URL that was used to initially install a chart. + // The URL from which the chart was obtained (a Helm repository URL, + // an OCI reference, or a direct artifact URL). Used to record provenance. RepoURL string `json:"repoURL,omitempty"` } diff --git a/pkg/action/install.go b/pkg/action/install.go index 567f8a382..2879bdc8a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -153,6 +153,19 @@ type ChartPathOptions struct { // registryClient provides a registry client but is not added with // options from a flag registryClient *registry.Client + + // resolvedRepoURL is the source URL from which the chart was obtained + // during the last LocateChart call. It is independent of RepoURL + // (the --repo input) and is used to record provenance into chart metadata. + resolvedRepoURL string +} + +// ResolvedRepoURL returns the source URL from which the chart was obtained +// during the last LocateChart call (a Helm repository URL, an OCI reference, +// or a direct artifact URL). It is empty for local-path installs or when +// LocateChart has not been called. +func (c *ChartPathOptions) ResolvedRepoURL() string { + return c.resolvedRepoURL } // NewInstall creates a new Install object with the given configuration. @@ -352,12 +365,14 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st return nil, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) } - // Stamp provenance information into the chart metadata if available from - // the ChartPathOptions. Do not overwrite existing metadata.RepoURL set by - // the chart itself. This ensures library consumers who call actions get - // provenance filled in when LocateChart or a downloader discovered it. + // Stamp provenance information into the chart metadata. Prefer the URL + // discovered by LocateChart (ResolvedRepoURL) and fall back to the --repo + // input for library consumers who set it without calling LocateChart. + // Do not overwrite a value already set in Chart.yaml. if chrt.Metadata != nil && chrt.Metadata.RepoURL == "" { - if i.ChartPathOptions.RepoURL != "" { + if u := i.ChartPathOptions.ResolvedRepoURL(); u != "" { + chrt.Metadata.RepoURL = u + } else if i.ChartPathOptions.RepoURL != "" { chrt.Metadata.RepoURL = i.ChartPathOptions.RepoURL } } @@ -896,6 +911,10 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) } + // Reset any provenance recorded by a previous call so callers that reuse + // this instance do not observe stale values from an earlier chart. + c.resolvedRepoURL = "" + name = strings.TrimSpace(name) version := strings.TrimSpace(c.Version) @@ -990,13 +1009,19 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( } // Record provenance information about where the chart was resolved from. - // Prefer the URL discovered by the downloader; fall back to the original - // reference when it is an OCI ref or an absolute URL. Leave RepoURL empty - // for local paths and unresolvable references so callers can distinguish. - if u := dl.RepositoryURL(); u != "" { - c.RepoURL = u - } else if c.RepoURL == "" && isRemoteChartRef(name) { - c.RepoURL = name + // Prefer the URL discovered by the downloader, then the --repo input, + // then the original reference when it is an OCI ref or an absolute URL. + // Local paths and unresolvable references leave resolvedRepoURL empty so + // callers can distinguish them. We deliberately do not mutate RepoURL, + // because it is also the --repo flag input and controls local-path vs + // remote resolution on subsequent calls. + switch { + case dl.RepositoryURL() != "": + c.resolvedRepoURL = dl.RepositoryURL() + case c.RepoURL != "": + c.resolvedRepoURL = c.RepoURL + case isRemoteChartRef(name): + c.resolvedRepoURL = name } lname, err := filepath.Abs(filename) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 5df7438ac..66426d973 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -312,11 +312,14 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str u.cfg.Logger().Debug("determined release apply method", slog.Bool("server_side_apply", serverSideApply), slog.String("previous_release_apply_method", lastRelease.ApplyMethod)) - // Stamp provenance information into the chart metadata if available from - // the ChartPathOptions. Only set it when chart metadata doesn't already - // contain a RepoURL so we don't overwrite embedded provenance. + // Stamp provenance information into the chart metadata. Prefer the URL + // discovered by LocateChart (ResolvedRepoURL) and fall back to the --repo + // input for library consumers who set it without calling LocateChart. + // Do not overwrite a value already set in Chart.yaml. if chart.Metadata != nil && chart.Metadata.RepoURL == "" { - if u.ChartPathOptions.RepoURL != "" { + if r := u.ChartPathOptions.ResolvedRepoURL(); r != "" { + chart.Metadata.RepoURL = r + } else if u.ChartPathOptions.RepoURL != "" { chart.Metadata.RepoURL = u.ChartPathOptions.RepoURL } } diff --git a/pkg/chart/v2/metadata.go b/pkg/chart/v2/metadata.go index fbf19cda2..be8608359 100644 --- a/pkg/chart/v2/metadata.go +++ b/pkg/chart/v2/metadata.go @@ -52,7 +52,8 @@ type Metadata struct { Home string `json:"home,omitempty"` // Source is the URL to the source code of this chart Sources []string `json:"sources,omitempty"` - // The URL to the chart's repository (provenance information) + // The URL from which the chart was obtained (a Helm repository URL, + // an OCI reference, or a direct artifact URL). Used to record provenance. RepoURL string `json:"repoURL,omitempty"` // A version string of the chart. Required. Version string `json:"version,omitempty"` diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 033efc4af..247a20a62 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -359,6 +359,11 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena // // TODO: support OCI hash func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url.URL, error) { + // Clear any URL recorded by a previous call so RepositoryURL() does not + // report stale provenance when the same ChartDownloader instance is + // reused for a chart that does not resolve to a configured repo. + c.repositoryURL = "" + u, err := url.Parse(ref) if err != nil { return "", nil, fmt.Errorf("invalid chart URL format: %s", ref) @@ -399,6 +404,9 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url // If we get here, we don't need to go through the next phase of looking // up the URL. We have it already. So we just set the parameters and return. + if rc.URL != "" { + c.repositoryURL = rc.URL + } c.Options = append( c.Options, getter.WithURL(rc.URL),