Files
helmfile/pkg/state/oci_chart_version_test.go
T
Aditya Menon 5c43fa6465 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>
2026-02-12 20:20:43 +08:00

181 lines
5.8 KiB
Go

package state
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestOCIChartVersionHandling tests the handling of OCI chart versions (issue #2247)
func TestOCIChartVersionHandling(t *testing.T) {
tests := []struct {
name string
chart string
version string
devel bool
helmVersion string
expectedVersion string
expectedError bool
expectedQualifiedChart string
}{
{
name: "OCI chart with explicit version",
chart: "oci://registry.example.com/my-chart",
version: "1.2.3",
helmVersion: "3.18.0",
expectedVersion: "1.2.3",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart:1.2.3",
},
{
name: "OCI chart with semver range version",
chart: "oci://registry.example.com/my-chart",
version: "^1.0.0",
helmVersion: "3.18.0",
expectedVersion: "^1.0.0",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart:^1.0.0",
},
{
name: "OCI chart without version should use empty string",
chart: "oci://registry.example.com/my-chart",
version: "",
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart",
},
{
name: "OCI chart with explicit 'latest' should fail (any Helm version)",
chart: "oci://registry.example.com/my-chart",
version: "latest",
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: true,
expectedQualifiedChart: "",
},
{
name: "OCI chart with explicit 'latest' should also fail on older Helm",
chart: "oci://registry.example.com/my-chart",
version: "latest",
helmVersion: "3.7.0",
expectedVersion: "",
expectedError: true,
expectedQualifiedChart: "",
},
{
name: "OCI chart without version in devel mode",
chart: "oci://registry.example.com/my-chart",
version: "",
devel: true,
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart",
},
{
name: "non-OCI chart returns empty qualified name",
chart: "stable/nginx",
version: "",
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: false,
expectedQualifiedChart: "",
},
{
name: "OCI chart with digest in version field",
chart: "oci://registry.example.com/my-chart",
version: "1.2.3@sha256:abc123def456",
helmVersion: "3.18.0",
expectedVersion: "1.2.3",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart@sha256:abc123def456",
},
{
name: "OCI chart with digest-only in version field",
chart: "oci://registry.example.com/my-chart",
version: "@sha256:abc123def456",
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart@sha256:abc123def456",
},
{
name: "OCI chart with version tag in URL",
chart: "oci://registry.example.com/my-chart:1.2.3",
version: "",
helmVersion: "3.18.0",
expectedVersion: "1.2.3",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart",
},
{
name: "OCI chart with digest in URL",
chart: "oci://registry.example.com/my-chart@sha256:abc123def456",
version: "",
helmVersion: "3.18.0",
expectedVersion: "",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart@sha256:abc123def456",
},
{
name: "OCI chart with version and digest in URL",
chart: "oci://registry.example.com/my-chart:1.2.3@sha256:abc123def456",
version: "",
helmVersion: "3.18.0",
expectedVersion: "1.2.3",
expectedError: false,
expectedQualifiedChart: "registry.example.com/my-chart@sha256:abc123def456",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a minimal HelmState
st := &HelmState{
basePath: "/test",
}
// Create a release
release := &ReleaseSpec{
Name: "test-release",
Chart: tt.chart,
Version: tt.version,
}
if tt.devel {
devel := true
release.Devel = &devel
}
// Call the function
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release)
// Check error
if tt.expectedError {
require.Error(t, err)
assert.Contains(t, err.Error(), "semver compliant")
} else {
require.NoError(t, err)
}
// Check version
assert.Equal(t, tt.expectedVersion, chartVersion, "chartVersion mismatch")
// Check qualified chart name
assert.Equal(t, tt.expectedQualifiedChart, qualifiedChartName, "qualifiedChartName mismatch")
// Check chart name extraction for OCI charts
if IsOCIChart(tt.chart) && !tt.expectedError {
assert.Equal(t, "my-chart", chartName, "chartName mismatch")
}
})
}
}
// IsOCIChart is a helper function to check if a chart is OCI-based
func IsOCIChart(chart string) bool {
return len(chart) > 6 && chart[:6] == "oci://"
}