fix: preserve vendor suffixes in KubeVersion.GitVersion

Helm 3.19.0 introduced a regression where vendor-specific suffixes
(e.g., -gke.1245000, -eks-4096722, +) are stripped from
.Capabilities.KubeVersion.GitVersion, breaking charts that detect
managed Kubernetes platforms.

The root cause was using k8sversion.ParseGeneric().String() which
intentionally discards vendor suffixes. The fix stores both the full
version (with vendor suffix) and a normalized version. String() returns
the normalized version for constraint checking (e.g., ">= 1.21.0"),
while Version/GitVersion preserve the full string for template access.

Fixes #31423
Related to #31063, #31078

Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com>
pull/31615/head
Benoit Tigeot 1 month ago committed by MrJack
parent aa29dd4995
commit 27f8e27f0b

@ -230,7 +230,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values,
if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.Version)
}
}

@ -572,7 +572,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) {
vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err)
is.Contains(err.Error(), "chart requires kubeVersion")
is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.")
}
func TestInstallRelease_Wait(t *testing.T) {

@ -19,6 +19,7 @@ import (
"fmt"
"slices"
"strconv"
"strings"
"k8s.io/client-go/kubernetes/scheme"
@ -39,11 +40,13 @@ var (
DefaultVersionSet = allKnownVersions()
// DefaultCapabilities is the default set of capabilities.
version = fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor)
DefaultCapabilities = &Capabilities{
KubeVersion: KubeVersion{
Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor),
Major: k8sVersionMajor,
Minor: k8sVersionMinor,
Version: version,
normalizedVersion: version,
Major: k8sVersionMajor,
Minor: k8sVersionMinor,
},
APIVersions: DefaultVersionSet,
HelmVersion: helmversion.Get(),
@ -70,15 +73,22 @@ func (capabilities *Capabilities) Copy() *Capabilities {
// KubeVersion is the Kubernetes version.
type KubeVersion struct {
Version string // Kubernetes version
Major string // Kubernetes major version
Minor string // Kubernetes minor version
Version string // Full version (e.g., v1.33.4-gke.1245000)
normalizedVersion string // Normalized for constraint checking (e.g., v1.33.4)
Major string // Kubernetes major version
Minor string // Kubernetes minor version
}
// String implements fmt.Stringer
func (kv *KubeVersion) String() string { return kv.Version }
// String implements fmt.Stringer.
// Returns the normalized version used for constraint checking.
func (kv *KubeVersion) String() string {
if kv.normalizedVersion != "" {
return kv.normalizedVersion
}
return kv.Version
}
// GitVersion returns the Kubernetes version string.
// GitVersion returns the full Kubernetes version string.
//
// Deprecated: use KubeVersion.Version.
func (kv *KubeVersion) GitVersion() string { return kv.Version }
@ -91,10 +101,21 @@ func ParseKubeVersion(version string) (*KubeVersion, error) {
if err != nil {
return nil, err
}
// Preserve original input (e.g., v1.33.4-gke.1245000)
gitVersion := version
if !strings.HasPrefix(version, "v") {
gitVersion = "v" + version
}
// Normalize for constraint checking (strips all suffixes)
normalizedVer := "v" + sv.String()
return &KubeVersion{
Version: "v" + sv.String(),
Major: strconv.FormatUint(uint64(sv.Major()), 10),
Minor: strconv.FormatUint(uint64(sv.Minor()), 10),
Version: gitVersion,
normalizedVersion: normalizedVer,
Major: strconv.FormatUint(uint64(sv.Major()), 10),
Minor: strconv.FormatUint(uint64(sv.Minor()), 10),
}, nil
}

@ -83,18 +83,41 @@ func TestParseKubeVersion(t *testing.T) {
}
}
func TestParseKubeVersionSuffix(t *testing.T) {
kv, err := ParseKubeVersion("v1.28+")
if err != nil {
t.Errorf("Expected v1.28+ to parse successfully")
}
if kv.Version != "v1.28" {
t.Errorf("Expected parsed KubeVersion.Version to be v1.28, got %q", kv.String())
}
if kv.Major != "1" {
t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major)
func TestParseKubeVersionWithVendorSuffixes(t *testing.T) {
tests := []struct {
name string
input string
wantVer string
wantString string
wantMajor string
wantMinor string
}{
{"GKE vendor suffix", "v1.33.4-gke.1245000", "v1.33.4-gke.1245000", "v1.33.4", "1", "33"},
{"GKE without v", "1.30.2-gke.1587003", "v1.30.2-gke.1587003", "v1.30.2", "1", "30"},
{"EKS trailing +", "v1.28+", "v1.28+", "v1.28", "1", "28"},
{"EKS + without v", "1.28+", "v1.28+", "v1.28", "1", "28"},
{"Standard version", "v1.31.0", "v1.31.0", "v1.31.0", "1", "31"},
{"Standard without v", "1.29.0", "v1.29.0", "v1.29.0", "1", "29"},
}
if kv.Minor != "28" {
t.Errorf("Expected parsed KubeVersion.Minor to be 28, got %q", kv.Minor)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kv, err := ParseKubeVersion(tt.input)
if err != nil {
t.Fatalf("ParseKubeVersion() error = %v", err)
}
if kv.Version != tt.wantVer {
t.Errorf("Version = %q, want %q", kv.Version, tt.wantVer)
}
if kv.String() != tt.wantString {
t.Errorf("String() = %q, want %q", kv.String(), tt.wantString)
}
if kv.Major != tt.wantMajor {
t.Errorf("Major = %q, want %q", kv.Major, tt.wantMajor)
}
if kv.Minor != tt.wantMinor {
t.Errorf("Minor = %q, want %q", kv.Minor, tt.wantMinor)
}
})
}
}

Loading…
Cancel
Save