From 505413581e2fb0c8a11136664bd932ce420cfd00 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Mon, 1 Oct 2018 21:48:11 +0300 Subject: [PATCH] kubeadm: handle stable-1 as the default version The default version in kubeadm is now `stable-1`. This will pull a version from the `stable-1.txt` endpoint which might end up being newer than the version of the client by a magnitude of MINOR or even a MAJOR release. To be able to prevent this scenario add the new helper function: validateStableVersion() This function determines if the remote version is newer than the local client version and if that's the case it returns `stable-X.xx` that conforms with the version of the client. If not it returns the remote version. --- cmd/kubeadm/app/util/version.go | 44 +++++++++++++++-- cmd/kubeadm/app/util/version_test.go | 72 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/cmd/kubeadm/app/util/version.go b/cmd/kubeadm/app/util/version.go index 98109d76264..c9246484d55 100644 --- a/cmd/kubeadm/app/util/version.go +++ b/cmd/kubeadm/app/util/version.go @@ -78,6 +78,10 @@ func KubernetesReleaseVersion(version string) (string, error) { // kubeReleaseLabelRegex matches labels such as: latest, latest-1, latest-1.10 if kubeReleaseLabelRegex.MatchString(versionLabel) { + var clientVersion string + // Try to obtain a client version. + clientVersion, _ = kubeadmVersion(pkgversion.Get().String()) + // Fetch version from the internet. url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel) body, err := fetchFromURL(url, getReleaseVersionTimeout) if err != nil { @@ -87,11 +91,13 @@ func KubernetesReleaseVersion(version string) (string, error) { } // Handle air-gapped environments by falling back to the client version. glog.Infof("could not fetch a Kubernetes version from the internet: %v", err) - body, err = kubeadmVersion(pkgversion.Get().String()) - if err != nil { - return "", err - } - glog.Infof("falling back to the local client version: %s", body) + glog.Infof("falling back to the local client version: %s", clientVersion) + return KubernetesReleaseVersion(clientVersion) + } + // both the client and the remote version are obtained; validate them and pick a stable version + body, err = validateStableVersion(body, clientVersion) + if err != nil { + return "", err } // Re-validate received version and return. return KubernetesReleaseVersion(body) @@ -204,3 +210,31 @@ func kubeadmVersion(info string) (string, error) { vStr := fmt.Sprintf("v%d.%d.%d%s", v.Major(), v.Minor(), patch, pre) return vStr, nil } + +// Validate if the remote version is one Minor release newer than the client version. +// This is done to conform with "stable-X" and only allow remote versions from +// the same Patch level release. +func validateStableVersion(remoteVersion, clientVersion string) (string, error) { + if clientVersion == "" { + glog.Infof("could not obtain client version; using remote version: %s", remoteVersion) + return remoteVersion, nil + } + + verRemote, err := versionutil.ParseGeneric(remoteVersion) + if err != nil { + return "", fmt.Errorf("remote version error: %v", err) + } + verClient, err := versionutil.ParseGeneric(clientVersion) + if err != nil { + return "", fmt.Errorf("client version error: %v", err) + } + // If the remote Major version is bigger or if the Major versions are the same, + // but the remote Minor is bigger use the client version release. This handles Major bumps too. + if verClient.Major() < verRemote.Major() || + (verClient.Major() == verRemote.Major()) && verClient.Minor() < verRemote.Minor() { + estimatedRelease := fmt.Sprintf("stable-%d.%d", verClient.Major(), verClient.Minor()) + glog.Infof("remote version is much newer: %s; falling back to: %s", remoteVersion, estimatedRelease) + return estimatedRelease, nil + } + return remoteVersion, nil +} diff --git a/cmd/kubeadm/app/util/version_test.go b/cmd/kubeadm/app/util/version_test.go index 1027edd7e6f..3444d6c0a22 100644 --- a/cmd/kubeadm/app/util/version_test.go +++ b/cmd/kubeadm/app/util/version_test.go @@ -380,3 +380,75 @@ func TestKubeadmVersion(t *testing.T) { }) } } + +func TestValidateStableVersion(t *testing.T) { + type T struct { + name string + remoteVersion string + clientVersion string + output string + expectedError bool + } + cases := []T{ + { + name: "valid: remote version is newer; return stable label [1]", + remoteVersion: "v1.12.0", + clientVersion: "v1.11.0", + output: "stable-1.11", + }, + { + name: "valid: remote version is newer; return stable label [2]", + remoteVersion: "v2.0.0", + clientVersion: "v1.11.0", + output: "stable-1.11", + }, + { + name: "valid: remote version is newer; return stable label [3]", + remoteVersion: "v2.1.5", + clientVersion: "v1.11.5", + output: "stable-1.11", + }, + { + name: "valid: return the remote version as it is part of the same release", + remoteVersion: "v1.11.5", + clientVersion: "v1.11.0", + output: "v1.11.5", + }, + { + name: "valid: return the same version", + remoteVersion: "v1.11.0", + clientVersion: "v1.11.0", + output: "v1.11.0", + }, + { + name: "valid: client version is empty; use remote version", + remoteVersion: "v1.12.1", + clientVersion: "", + output: "v1.12.1", + }, + { + name: "invalid: error parsing the remote version", + remoteVersion: "invalid-version", + clientVersion: "v1.12.0", + expectedError: true, + }, + { + name: "invalid: error parsing the client version", + remoteVersion: "v1.12.0", + clientVersion: "invalid-version", + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + output, err := validateStableVersion(tc.remoteVersion, tc.clientVersion) + if (err != nil) != tc.expectedError { + t.Fatalf("expected error: %v, got: %v", tc.expectedError, err != nil) + } + if output != tc.output { + t.Fatalf("expected output: %s, got: %s", tc.output, output) + } + }) + } +}