Implement OCI chart digest support for helm chart pinning

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-14 23:34:29 +00:00
co-authored by yxxhero
parent b95d617b96
commit 1f786a3662
4 changed files with 242 additions and 16 deletions
+54 -3
View File
@@ -4071,9 +4071,60 @@ func (st *HelmState) getOCIQualifiedChartName(release *ReleaseSpec, helm helmexe
var qualifiedChartName, chartName string
if strings.HasPrefix(release.Chart, "oci://") {
parts := strings.Split(release.Chart, "/")
chartName = parts[len(parts)-1]
qualifiedChartName = strings.Replace(fmt.Sprintf("%s:%s", release.Chart, chartVersion), "oci://", "", 1)
// Parse the chart URL to detect existing version/digest information
chartURL := release.Chart
urlWithoutProtocol := strings.TrimPrefix(chartURL, "oci://")
// Check for digest (@sha256:...)
digestIndex := strings.Index(urlWithoutProtocol, "@")
hasDigest := digestIndex != -1
// Check for version tag (:version)
var versionInURL string
if hasDigest {
// Check for version before digest (e.g., chart:1.0.0@sha256:...)
beforeDigest := urlWithoutProtocol[:digestIndex]
if colonIndex := strings.LastIndex(beforeDigest, ":"); colonIndex > strings.LastIndex(beforeDigest, "/") {
versionInURL = beforeDigest[colonIndex+1:]
}
} else {
// Check for version at end (e.g., chart:1.0.0)
if colonIndex := strings.LastIndex(urlWithoutProtocol, ":"); colonIndex > strings.LastIndex(urlWithoutProtocol, "/") {
versionInURL = urlWithoutProtocol[colonIndex+1:]
}
}
// Determine effective chart version and build qualified name
if hasDigest || versionInURL != "" {
// Chart URL already contains version/digest, use as-is without adding version
qualifiedChartName = urlWithoutProtocol
// Extract chart name from URL
parts := strings.Split(urlWithoutProtocol, "/")
chartNamePart := parts[len(parts)-1]
// Remove version/digest from chart name for extraction
if hasDigest {
chartNamePart = strings.Split(chartNamePart, "@")[0]
}
if colonIndex := strings.LastIndex(chartNamePart, ":"); colonIndex > 0 {
chartNamePart = chartNamePart[:colonIndex]
}
chartName = chartNamePart
// Set effective version for return (use version from URL if available, otherwise indicate digest)
if versionInURL != "" {
chartVersion = versionInURL
} else {
// For digest-only, extract digest as version for helm compatibility
chartVersion = urlWithoutProtocol[digestIndex:]
}
} else {
// No version/digest in URL, use explicit version
parts := strings.Split(urlWithoutProtocol, "/")
chartName = parts[len(parts)-1]
qualifiedChartName = fmt.Sprintf("%s:%s", urlWithoutProtocol, chartVersion)
}
} else {
var repo *RepositorySpec
repo, chartName = st.GetRepositoryAndNameFromChartName(release.Chart)
+81
View File
@@ -3310,6 +3310,87 @@ func TestGetOCIQualifiedChartName(t *testing.T) {
{"registry/chart-path/chart-name", "chart-name", ""},
},
},
{
state: HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Repositories: []RepositorySpec{},
Releases: []ReleaseSpec{
{
Chart: "oci://ghcr.io/nginxinc/charts/nginx-ingress:2.0.0",
},
},
},
},
helmVersion: "3.13.3",
expected: []struct {
qualifiedChartName string
chartName string
chartVersion string
}{
{"ghcr.io/nginxinc/charts/nginx-ingress:2.0.0", "nginx-ingress", "2.0.0"},
},
},
{
state: HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Repositories: []RepositorySpec{},
Releases: []ReleaseSpec{
{
Chart: "oci://ghcr.io/nginxinc/charts/nginx-ingress@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
},
},
},
},
helmVersion: "3.13.3",
expected: []struct {
qualifiedChartName string
chartName string
chartVersion string
}{
{"ghcr.io/nginxinc/charts/nginx-ingress@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085", "nginx-ingress", "@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085"},
},
},
{
state: HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Repositories: []RepositorySpec{},
Releases: []ReleaseSpec{
{
Chart: "oci://ghcr.io/nginxinc/charts/nginx-ingress:2.0.0@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
},
},
},
},
helmVersion: "3.13.3",
expected: []struct {
qualifiedChartName string
chartName string
chartVersion string
}{
{"ghcr.io/nginxinc/charts/nginx-ingress:2.0.0@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085", "nginx-ingress", "2.0.0"},
},
},
{
state: HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Repositories: []RepositorySpec{},
Releases: []ReleaseSpec{
{
Chart: "oci://ghcr.io/nginxinc/charts/nginx-ingress",
Version: "@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085",
},
},
},
},
helmVersion: "3.13.3",
expected: []struct {
qualifiedChartName string
chartName string
chartVersion string
}{
{"ghcr.io/nginxinc/charts/nginx-ingress:@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085", "nginx-ingress", "@sha256:87ad282a8e7cc31913ce0543de2933ddb3f3eba80d6e5285f33b62ed720fc085"},
},
},
}
for _, tt := range tests {