Merge pull request #53043 from kad/upgrade-ux
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Allow to use version labels in kubeadm upgrade apply command. **What this PR does / why we need it**: kubeadm upgrade apply now is able to utilize all possible combinations of version argument, including labels (latest, stable-1.8, ci/latest-1.9) as well as specific builds (v1.8.0-rc.1, ci/v1.9.0-alpha.1.123_01234567889) As side effect, specifying exact build to deploy from CI area is now also possible in kubeadm init command. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes kubernetes/kubeadm#451 **Special notes for your reviewer**: cc @luxas **Release note**: ```release-note - kubeadm init can now deploy exact build from CI area by specifying ID with "ci/" prefix. Example: "ci/v1.9.0-alpha.1.123+01234567889" - kubeadm upgrade apply supports all standard ways of specifying version via labels. Examples: stable-1.8, latest-1.8, ci/latest-1.9 and similar. ```
This commit is contained in:
		@@ -19,6 +19,7 @@ go_library(
 | 
				
			|||||||
        "//cmd/kubeadm/app/preflight:go_default_library",
 | 
					        "//cmd/kubeadm/app/preflight:go_default_library",
 | 
				
			||||||
        "//cmd/kubeadm/app/util:go_default_library",
 | 
					        "//cmd/kubeadm/app/util:go_default_library",
 | 
				
			||||||
        "//cmd/kubeadm/app/util/apiclient:go_default_library",
 | 
					        "//cmd/kubeadm/app/util/apiclient:go_default_library",
 | 
				
			||||||
 | 
					        "//cmd/kubeadm/app/util/config:go_default_library",
 | 
				
			||||||
        "//cmd/kubeadm/app/util/dryrun:go_default_library",
 | 
					        "//cmd/kubeadm/app/util/dryrun:go_default_library",
 | 
				
			||||||
        "//cmd/kubeadm/app/util/kubeconfig:go_default_library",
 | 
					        "//cmd/kubeadm/app/util/kubeconfig:go_default_library",
 | 
				
			||||||
        "//pkg/api:go_default_library",
 | 
					        "//pkg/api:go_default_library",
 | 
				
			||||||
@@ -41,7 +42,6 @@ go_test(
 | 
				
			|||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
 | 
					        "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
 | 
				
			||||||
        "//cmd/kubeadm/app/phases/upgrade:go_default_library",
 | 
					        "//cmd/kubeadm/app/phases/upgrade:go_default_library",
 | 
				
			||||||
        "//pkg/util/version:go_default_library",
 | 
					 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,6 @@ package upgrade
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
@@ -32,6 +31,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
 | 
						"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
 | 
				
			||||||
	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
 | 
						kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
 | 
				
			||||||
	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
 | 
						"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
 | 
				
			||||||
 | 
						configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
 | 
				
			||||||
	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
 | 
						dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api"
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/util/version"
 | 
						"k8s.io/kubernetes/pkg/util/version"
 | 
				
			||||||
@@ -123,6 +123,19 @@ func RunApply(flags *applyFlags) error {
 | 
				
			|||||||
	internalcfg := &kubeadmapi.MasterConfiguration{}
 | 
						internalcfg := &kubeadmapi.MasterConfiguration{}
 | 
				
			||||||
	api.Scheme.Convert(upgradeVars.cfg, internalcfg, nil)
 | 
						api.Scheme.Convert(upgradeVars.cfg, internalcfg, nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Validate requested and validate actual version
 | 
				
			||||||
 | 
						if err := configutil.NormalizeKubernetesVersion(internalcfg); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use normalized version string in all following code.
 | 
				
			||||||
 | 
						flags.newK8sVersionStr = internalcfg.KubernetesVersion
 | 
				
			||||||
 | 
						k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to parse normalized version %q as a semantic version", flags.newK8sVersionStr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						flags.newK8sVersion = k8sVer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Enforce the version skew policies
 | 
						// Enforce the version skew policies
 | 
				
			||||||
	if err := EnforceVersionPolicies(flags, upgradeVars.versionGetter); err != nil {
 | 
						if err := EnforceVersionPolicies(flags, upgradeVars.versionGetter); err != nil {
 | 
				
			||||||
		return fmt.Errorf("[upgrade/version] FATAL: %v", err)
 | 
							return fmt.Errorf("[upgrade/version] FATAL: %v", err)
 | 
				
			||||||
@@ -170,15 +183,8 @@ func SetImplicitFlags(flags *applyFlags) error {
 | 
				
			|||||||
		flags.nonInteractiveMode = true
 | 
							flags.nonInteractiveMode = true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
 | 
						if len(flags.newK8sVersionStr) == 0 {
 | 
				
			||||||
	if err != nil {
 | 
							return fmt.Errorf("version string can't be empty")
 | 
				
			||||||
		return fmt.Errorf("couldn't parse version %q as a semantic version", flags.newK8sVersionStr)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	flags.newK8sVersion = k8sVer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Automatically add the "v" prefix to the string representation in case it doesn't exist
 | 
					 | 
				
			||||||
	if !strings.HasPrefix(flags.newK8sVersionStr, "v") {
 | 
					 | 
				
			||||||
		flags.newK8sVersionStr = fmt.Sprintf("v%s", flags.newK8sVersionStr)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,6 @@ package upgrade
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"k8s.io/kubernetes/pkg/util/version"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestSetImplicitFlags(t *testing.T) {
 | 
					func TestSetImplicitFlags(t *testing.T) {
 | 
				
			||||||
@@ -38,7 +36,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             false,
 | 
									dryRun:             false,
 | 
				
			||||||
				force:              false,
 | 
									force:              false,
 | 
				
			||||||
				nonInteractiveMode: false,
 | 
									nonInteractiveMode: false,
 | 
				
			||||||
@@ -53,7 +50,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             false,
 | 
									dryRun:             false,
 | 
				
			||||||
				force:              false,
 | 
									force:              false,
 | 
				
			||||||
				nonInteractiveMode: true,
 | 
									nonInteractiveMode: true,
 | 
				
			||||||
@@ -68,7 +64,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             true,
 | 
									dryRun:             true,
 | 
				
			||||||
				force:              false,
 | 
									force:              false,
 | 
				
			||||||
				nonInteractiveMode: true,
 | 
									nonInteractiveMode: true,
 | 
				
			||||||
@@ -83,7 +78,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             false,
 | 
									dryRun:             false,
 | 
				
			||||||
				force:              true,
 | 
									force:              true,
 | 
				
			||||||
				nonInteractiveMode: true,
 | 
									nonInteractiveMode: true,
 | 
				
			||||||
@@ -98,7 +92,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             true,
 | 
									dryRun:             true,
 | 
				
			||||||
				force:              true,
 | 
									force:              true,
 | 
				
			||||||
				nonInteractiveMode: true,
 | 
									nonInteractiveMode: true,
 | 
				
			||||||
@@ -113,7 +106,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
								expectedFlags: applyFlags{
 | 
				
			||||||
				newK8sVersionStr:   "v1.8.0",
 | 
									newK8sVersionStr:   "v1.8.0",
 | 
				
			||||||
				newK8sVersion:      version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
				dryRun:             true,
 | 
									dryRun:             true,
 | 
				
			||||||
				force:              true,
 | 
									force:              true,
 | 
				
			||||||
				nonInteractiveMode: true,
 | 
									nonInteractiveMode: true,
 | 
				
			||||||
@@ -128,45 +120,6 @@ func TestSetImplicitFlags(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			errExpected: true,
 | 
								errExpected: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{ // if the new version is invalid; it should error out
 | 
					 | 
				
			||||||
			flags: &applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "foo",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "foo",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errExpected: true,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{ // if the new version is valid but without the "v" prefix; it parse and prepend v
 | 
					 | 
				
			||||||
			flags: &applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "1.8.0",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "v1.8.0",
 | 
					 | 
				
			||||||
				newK8sVersion:    version.MustParseSemantic("v1.8.0"),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errExpected: false,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{ // valid version should succeed
 | 
					 | 
				
			||||||
			flags: &applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "v1.8.1",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "v1.8.1",
 | 
					 | 
				
			||||||
				newK8sVersion:    version.MustParseSemantic("v1.8.1"),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errExpected: false,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{ // valid version should succeed
 | 
					 | 
				
			||||||
			flags: &applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "1.8.0-alpha.3",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			expectedFlags: applyFlags{
 | 
					 | 
				
			||||||
				newK8sVersionStr: "v1.8.0-alpha.3",
 | 
					 | 
				
			||||||
				newK8sVersion:    version.MustParseSemantic("v1.8.0-alpha.3"),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errExpected: false,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, rt := range tests {
 | 
						for _, rt := range tests {
 | 
				
			||||||
		actualErr := SetImplicitFlags(rt.flags)
 | 
							actualErr := SetImplicitFlags(rt.flags)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,7 @@ func NewDaemonSetPrepuller(client clientset.Interface, waiter apiclient.Waiter,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateFunc creates a DaemonSet for making the image available on every relevant node
 | 
					// CreateFunc creates a DaemonSet for making the image available on every relevant node
 | 
				
			||||||
func (d *DaemonSetPrepuller) CreateFunc(component string) error {
 | 
					func (d *DaemonSetPrepuller) CreateFunc(component string) error {
 | 
				
			||||||
	image := images.GetCoreImage(component, d.cfg.ImageRepository, d.cfg.KubernetesVersion, d.cfg.UnifiedControlPlaneImage)
 | 
						image := images.GetCoreImage(component, d.cfg.GetControlPlaneImageRepository(), d.cfg.KubernetesVersion, d.cfg.UnifiedControlPlaneImage)
 | 
				
			||||||
	ds := buildPrePullDaemonSet(component, image)
 | 
						ds := buildPrePullDaemonSet(component, image)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create the DaemonSet in the API Server
 | 
						// Create the DaemonSet in the API Server
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,25 +45,11 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	cfg.API.AdvertiseAddress = ip.String()
 | 
						cfg.API.AdvertiseAddress = ip.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images
 | 
						// Resolve possible version labels and validate version string
 | 
				
			||||||
	if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) {
 | 
						err = NormalizeKubernetesVersion(cfg)
 | 
				
			||||||
		cfg.CIImageRepository = kubeadmconstants.DefaultCIImageRepository
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// Validate version argument
 | 
					 | 
				
			||||||
	ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cfg.KubernetesVersion = ver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Parse the given kubernetes version and make sure it's higher than the lowest supported
 | 
					 | 
				
			||||||
	k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if k8sVersion.LessThan(kubeadmconstants.MinimumControlPlaneVersion) {
 | 
					 | 
				
			||||||
		return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if cfg.Token == "" {
 | 
						if cfg.Token == "" {
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
@@ -123,3 +109,30 @@ func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return internalcfg, nil
 | 
						return internalcfg, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NormalizeKubernetesVersion resolves version labels, sets alternative
 | 
				
			||||||
 | 
					// image registry if requested for CI builds, and validates minimal
 | 
				
			||||||
 | 
					// version that kubeadm supports.
 | 
				
			||||||
 | 
					func NormalizeKubernetesVersion(cfg *kubeadmapi.MasterConfiguration) error {
 | 
				
			||||||
 | 
						// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images
 | 
				
			||||||
 | 
						if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) {
 | 
				
			||||||
 | 
							cfg.CIImageRepository = kubeadmconstants.DefaultCIImageRepository
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse and validate the version argument and resolve possible CI version labels
 | 
				
			||||||
 | 
						ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cfg.KubernetesVersion = ver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the given kubernetes version and make sure it's higher than the lowest supported
 | 
				
			||||||
 | 
						k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if k8sVersion.LessThan(kubeadmconstants.MinimumControlPlaneVersion) {
 | 
				
			||||||
 | 
							return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,17 +49,22 @@ var (
 | 
				
			|||||||
//  latest-1    (latest release in 1.x, including alpha/beta)
 | 
					//  latest-1    (latest release in 1.x, including alpha/beta)
 | 
				
			||||||
//  latest-1.0  (and similarly 1.1, 1.2, 1.3, ...)
 | 
					//  latest-1.0  (and similarly 1.1, 1.2, 1.3, ...)
 | 
				
			||||||
func KubernetesReleaseVersion(version string) (string, error) {
 | 
					func KubernetesReleaseVersion(version string) (string, error) {
 | 
				
			||||||
	if kubeReleaseRegex.MatchString(version) {
 | 
						ver := normalizedBuildVersion(version)
 | 
				
			||||||
		if strings.HasPrefix(version, "v") {
 | 
						if len(ver) != 0 {
 | 
				
			||||||
			return version, nil
 | 
							return ver, nil
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return "v" + version, nil
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bucketURL, versionLabel, err := splitVersion(version)
 | 
						bucketURL, versionLabel, err := splitVersion(version)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// revalidate, if exact build from e.g. CI bucket requested.
 | 
				
			||||||
 | 
						ver = normalizedBuildVersion(versionLabel)
 | 
				
			||||||
 | 
						if len(ver) != 0 {
 | 
				
			||||||
 | 
							return ver, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if kubeReleaseLabelRegex.MatchString(versionLabel) {
 | 
						if kubeReleaseLabelRegex.MatchString(versionLabel) {
 | 
				
			||||||
		url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
 | 
							url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
 | 
				
			||||||
		body, err := fetchFromURL(url)
 | 
							body, err := fetchFromURL(url)
 | 
				
			||||||
@@ -92,6 +97,18 @@ func KubernetesIsCIVersion(version string) bool {
 | 
				
			|||||||
	return false
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Internal helper: returns normalized build version (with "v" prefix if needed)
 | 
				
			||||||
 | 
					// If input doesn't match known version pattern, returns empty string.
 | 
				
			||||||
 | 
					func normalizedBuildVersion(version string) string {
 | 
				
			||||||
 | 
						if kubeReleaseRegex.MatchString(version) {
 | 
				
			||||||
 | 
							if strings.HasPrefix(version, "v") {
 | 
				
			||||||
 | 
								return version
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return "v" + version
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Internal helper: split version parts,
 | 
					// Internal helper: split version parts,
 | 
				
			||||||
// Return base URL and cleaned-up version
 | 
					// Return base URL and cleaned-up version
 | 
				
			||||||
func splitVersion(version string) (string, string, error) {
 | 
					func splitVersion(version string) (string, string, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -220,6 +220,8 @@ func TestKubernetesIsCIVersion(t *testing.T) {
 | 
				
			|||||||
		// CI builds
 | 
							// CI builds
 | 
				
			||||||
		{"ci/latest-1", true},
 | 
							{"ci/latest-1", true},
 | 
				
			||||||
		{"ci-cross/latest", true},
 | 
							{"ci-cross/latest", true},
 | 
				
			||||||
 | 
							{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
 | 
							{"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tc := range cases {
 | 
						for _, tc := range cases {
 | 
				
			||||||
@@ -231,3 +233,58 @@ func TestKubernetesIsCIVersion(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate KubernetesReleaseVersion but with bucket prefixes
 | 
				
			||||||
 | 
					func TestCIBuildVersion(t *testing.T) {
 | 
				
			||||||
 | 
						type T struct {
 | 
				
			||||||
 | 
							input    string
 | 
				
			||||||
 | 
							expected string
 | 
				
			||||||
 | 
							valid    bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cases := []T{
 | 
				
			||||||
 | 
							// Official releases
 | 
				
			||||||
 | 
							{"v1.7.0", "v1.7.0", true},
 | 
				
			||||||
 | 
							{"release/v1.8.0", "v1.8.0", true},
 | 
				
			||||||
 | 
							{"1.4.0-beta.0", "v1.4.0-beta.0", true},
 | 
				
			||||||
 | 
							{"release/0invalid", "", false},
 | 
				
			||||||
 | 
							// CI or custom builds
 | 
				
			||||||
 | 
							{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
 | 
							{"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
 | 
							{"ci/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
 | 
							{"ci-cross/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
 | 
				
			||||||
 | 
							{"ci/0invalid", "", false},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							ver, err := KubernetesReleaseVersion(tc.input)
 | 
				
			||||||
 | 
							t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case err != nil && tc.valid:
 | 
				
			||||||
 | 
								t.Errorf("KubernetesReleaseVersion: unexpected error for input %q. Error: %v", tc.input, err)
 | 
				
			||||||
 | 
							case err == nil && !tc.valid:
 | 
				
			||||||
 | 
								t.Errorf("KubernetesReleaseVersion: error expected for input %q, but result is %q", tc.input, ver)
 | 
				
			||||||
 | 
							case ver != tc.expected:
 | 
				
			||||||
 | 
								t.Errorf("KubernetesReleaseVersion: unexpected result for input %q. Expected: %q Actual: %q", tc.input, tc.expected, ver)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNormalizedBuildVersionVersion(t *testing.T) {
 | 
				
			||||||
 | 
						type T struct {
 | 
				
			||||||
 | 
							input    string
 | 
				
			||||||
 | 
							expected string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cases := []T{
 | 
				
			||||||
 | 
							{"v1.7.0", "v1.7.0"},
 | 
				
			||||||
 | 
							{"v1.8.0-alpha.2.1231+afabd012389d53a", "v1.8.0-alpha.2.1231+afabd012389d53a"},
 | 
				
			||||||
 | 
							{"1.7.0", "v1.7.0"},
 | 
				
			||||||
 | 
							{"unknown-1", ""},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							output := normalizedBuildVersion(tc.input)
 | 
				
			||||||
 | 
							if output != tc.expected {
 | 
				
			||||||
 | 
								t.Errorf("normalizedBuildVersion: unexpected output %q for input %q. Expected: %q", output, tc.input, tc.expected)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user