fix: support OCI chart digest syntax (@sha256:...) (#2398)

fix: support OCI chart digest syntax in chart URLs and version fields

Helm supports pinning OCI chart images by digest (@sha256:...), version
tag (:version), or both (:version@sha256:digest) since helm/helm#12690.
Helmfile failed to parse these formats, incorrectly constructing helm
commands and losing version/digest information embedded in chart URLs.

Root causes:
- resolveOciChart() used last ":" to find version tag, but sha256:abc
  contains ":", so digest URLs were split incorrectly
- getOCIQualifiedChartName() included :version and @digest in chartName
  with no parsing of either source
- appendChartVersionFlags() passed release.Version verbatim to --version
  flag, including any digest suffix
- ChartPull() discarded the tag from resolveOciChart but did not
  preserve digest in the URL

This commit adds parseOCIChartRef() and parseVersionDigest() utilities,
then updates the OCI chart handling pipeline so that:
- Digests are preserved in the chart URL passed to helm pull
- Version tags are extracted cleanly for the --version flag
- Both chart URL and version field are parsed for version/digest info

Fixes #2097

Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
This commit is contained in:
Aditya Menon
2026-02-12 20:20:43 +08:00
committed by GitHub
parent 2f8b9cbdfb
commit 5c43fa6465
14 changed files with 910 additions and 10 deletions
+10 -2
View File
@@ -962,8 +962,16 @@ func (helm *execer) IsVersionAtLeast(versionStr string) bool {
}
func resolveOciChart(ociChart string) (ociChartURL, ociChartTag string) {
// Split off digest (e.g., @sha256:abc) first so the colon in sha256:
// does not confuse the version tag search below.
var digest string
if atIdx := strings.Index(ociChart, "@"); atIdx >= 0 {
digest = ociChart[atIdx:] // includes the "@"
ociChart = ociChart[:atIdx]
}
var urlTagIndex int
// Get the last : index
// Get the last : index in the pre-digest part
// e.g.,
// 1. registry:443/helm-charts
// 2. registry/helm-charts:latest
@@ -975,7 +983,7 @@ func resolveOciChart(ociChart string) (ociChartURL, ociChartTag string) {
urlTagIndex = strings.LastIndex(ociChart, ":")
ociChartTag = ociChart[urlTagIndex+1:]
}
ociChartURL = fmt.Sprintf("oci://%s", ociChart[:urlTagIndex])
ociChartURL = fmt.Sprintf("oci://%s%s", ociChart[:urlTagIndex], digest)
return ociChartURL, ociChartTag
}
+24
View File
@@ -1381,6 +1381,30 @@ func Test_resolveOciChart(t *testing.T) {
ociChartURL: "oci://chart:5000/nginx",
ociChartTag: "",
},
{
name: "digest only",
chartPath: "ghcr.io/nginxinc/charts/nginx-ingress@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
ociChartURL: "oci://ghcr.io/nginxinc/charts/nginx-ingress@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
ociChartTag: "",
},
{
name: "version and digest",
chartPath: "ghcr.io/nginxinc/charts/nginx-ingress:2.0.0@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
ociChartURL: "oci://ghcr.io/nginxinc/charts/nginx-ingress@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
ociChartTag: "2.0.0",
},
{
name: "port with digest",
chartPath: "registry:5000/chart@sha256:abc123",
ociChartURL: "oci://registry:5000/chart@sha256:abc123",
ociChartTag: "",
},
{
name: "port with version and digest",
chartPath: "registry:5000/chart:1.0.0@sha256:abc123",
ociChartURL: "oci://registry:5000/chart@sha256:abc123",
ociChartTag: "1.0.0",
},
}
for i := range tests {
tt := tests[i]