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>
pull/31528/head
Benoit Tigeot 10 months ago
parent 51a9bc5157
commit ce273eea48
No known key found for this signature in database
GPG Key ID: 8E6D4FC8AEBDA62C

@ -230,7 +230,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values,
if ch.Metadata.KubeVersion != "" { if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { 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{}{} vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err) 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) { func TestInstallRelease_Wait(t *testing.T) {

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

@ -83,18 +83,41 @@ func TestParseKubeVersion(t *testing.T) {
} }
} }
func TestParseKubeVersionSuffix(t *testing.T) { func TestParseKubeVersionWithVendorSuffixes(t *testing.T) {
kv, err := ParseKubeVersion("v1.28+") tests := []struct {
if err != nil { name string
t.Errorf("Expected v1.28+ to parse successfully") input string
} wantVer string
if kv.Version != "v1.28" { wantString string
t.Errorf("Expected parsed KubeVersion.Version to be v1.28, got %q", kv.String()) wantMajor string
} wantMinor string
if kv.Major != "1" { }{
t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) {"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