From 78e28923ba2d13e3a81d562e366a57263969a709 Mon Sep 17 00:00:00 2001 From: Alexander Kanevskiy Date: Mon, 21 Nov 2016 16:56:10 +0200 Subject: [PATCH 1/3] Implement support for symbolic labels in --use-kubernetes-version Now, defaults can be pointing to "stable" and users will always get latest available stable build of Kubernetes via kubeadm. There is no need anymore to hardcode version string inside kubeadm binary. It is also possible to use labels like "latest" or point to exact branch: "stable-1.4" --- .../app/apis/kubeadm/v1alpha1/defaults.go | 2 +- cmd/kubeadm/app/cmd/init.go | 8 +++ cmd/kubeadm/app/util/BUILD | 1 + cmd/kubeadm/app/util/version.go | 71 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 cmd/kubeadm/app/util/version.go diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index 1c44d2dbb95..c269d3c2356 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -23,7 +23,7 @@ import ( const ( DefaultServiceDNSDomain = "cluster.local" DefaultServicesSubnet = "10.96.0.0/12" - DefaultKubernetesVersion = "v1.4.4" + DefaultKubernetesVersion = "stable" DefaultAPIBindPort = 6443 DefaultDiscoveryBindPort = 9898 ) diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 6107f589427..8e0f053e5f7 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -193,6 +193,14 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight fmt.Println("Skipping pre-flight checks") } + // validate version argument + ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion) + if err != nil { + return nil, err + } + cfg.KubernetesVersion = ver + fmt.Println("Using Kubernetes version:", ver) + // TODO(phase1+) create a custom flag if cfg.CloudProvider != "" { if cloudprovider.IsCloudProvider(cfg.CloudProvider) { diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 26711473cce..2921fb26f6d 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -16,6 +16,7 @@ go_library( "error.go", "kubeconfig.go", "tokens.go", + "version.go", ], tags = ["automanaged"], deps = [ diff --git a/cmd/kubeadm/app/util/version.go b/cmd/kubeadm/app/util/version.go new file mode 100644 index 00000000000..86946024bd3 --- /dev/null +++ b/cmd/kubeadm/app/util/version.go @@ -0,0 +1,71 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +var ( + kubeReleaseBucketURL = "https://storage.googleapis.com/kubernetes-release/release" + kubeReleaseRegex = regexp.MustCompile(`^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?$`) + kubeReleaseLabelRegex = regexp.MustCompile(`^[[:lower:]]+(-[-\w_\.]+)?$`) +) + +// KubernetesReleaseVersion is helper function that can fetch +// available version information from release servers based on +// label names, like "stable" or "latest". +// +// If argument is already semantic version string, it +// will return same string. +// +// In case of labels, it tries to fetch from release +// servers and then return actual semantic version. +// +// Available names on release servers: +// stable (latest stable release) +// stable-1 (latest stable release in 1.x) +// stable-1.0 (and similarly 1.1, 1.2, 1.3, ...) +// latest (latest release, including alpha/beta) +// latest-1 (latest release in 1.x, including alpha/beta) +// latest-1.0 (and similarly 1.1, 1.2, 1.3, ...) +func KubernetesReleaseVersion(version string) (string, error) { + if kubeReleaseRegex.MatchString(version) { + return version, nil + } else if kubeReleaseLabelRegex.MatchString(version) { + url := fmt.Sprintf("%s/%s.txt", kubeReleaseBucketURL, version) + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("Error: unable to get URL %q: %s", url, err.Error()) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("Error: unable to fetch release information. URL: %q Status: %v", url, resp.Status) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error: unable to read content of URL %q: %s", url, err.Error()) + } + // Re-validate received version and return. + return KubernetesReleaseVersion(strings.Trim(string(body), " \t\n")) + } + return "", fmt.Errorf("Error: version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version) +} From 39f55cb4df37e1c809b5fcf48f7fb997a59378be Mon Sep 17 00:00:00 2001 From: Alexander Kanevskiy Date: Wed, 30 Nov 2016 17:26:53 +0200 Subject: [PATCH 2/3] Added test case for KubernetesReleaseVersion --- cmd/kubeadm/app/util/BUILD | 1 + cmd/kubeadm/app/util/version_test.go | 122 +++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 cmd/kubeadm/app/util/version_test.go diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 2921fb26f6d..c537e8d1dd2 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -35,6 +35,7 @@ go_test( "error_test.go", "kubeconfig_test.go", "tokens_test.go", + "version_test.go", ], library = "go_default_library", tags = ["automanaged"], diff --git a/cmd/kubeadm/app/util/version_test.go b/cmd/kubeadm/app/util/version_test.go new file mode 100644 index 00000000000..d8444bc0c0c --- /dev/null +++ b/cmd/kubeadm/app/util/version_test.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "net/http" + "net/http/httptest" + "path" + "strings" + "testing" +) + +func TestEmptyVersion(t *testing.T) { + + ver, err := KubernetesReleaseVersion("") + if err == nil { + t.Error("KubernetesReleaseVersion returned succesfully, but error expected") + } + if ver != "" { + t.Error("KubernetesReleaseVersion returned value, expected only error") + } +} + +func TestValidVersion(t *testing.T) { + validVersions := []string{ + "v1.3.0", + "v1.4.0-alpha.0", + "v1.4.5", + "v1.4.0-beta.0", + "v2.0.0", + "v1.6.0-alpha.0.536+d60d9f3269288f", + "v1.5.0-alpha.0.1078+1044b6822497da-pull", + "v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci", + } + for _, s := range validVersions { + ver, err := KubernetesReleaseVersion(s) + t.Log("Valid: ", s, ver, err) + if err != nil { + t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err) + } + if ver != s { + t.Errorf("KubernetesReleaseVersion should return same valid version string. %q != %q", s, ver) + } + } +} + +func TestInvalidVersion(t *testing.T) { + invalidVersions := []string{ + "v1.3", + "1.4.0", + "1.4.5+git", + "something1.2", + } + for _, s := range invalidVersions { + ver, err := KubernetesReleaseVersion(s) + t.Log("Invalid: ", s, ver, err) + if err == nil { + t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned succesfully", s) + } + if ver != "" { + t.Errorf("KubernetesReleaseVersion should return empty string in case of error. Returned %q for version %q", ver, s) + } + } +} + +func TestVersionFromNetwork(t *testing.T) { + type T struct { + Content string + Status int + Expected string + ErrorExpected bool + } + cases := map[string]T{ + "stable": {"stable-1", http.StatusOK, "v1.4.6", false}, // recursive pointer to stable-1 + "stable-1": {"v1.4.6", http.StatusOK, "v1.4.6", false}, + "stable-1.3": {"v1.3.10", http.StatusOK, "v1.3.10", false}, + "latest": {"v1.6.0-alpha.0", http.StatusOK, "v1.6.0-alpha.0", false}, + "latest-1.3": {"v1.3.11-beta.0", http.StatusOK, "v1.3.11-beta.0", false}, + "empty": {"", http.StatusOK, "", true}, + "garbage": {"NoSuchKeyThe specified key does not exist.", http.StatusOK, "", true}, + "unknown": {"The requested URL was not found on this server.", http.StatusNotFound, "", true}, + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + key := strings.TrimSuffix(path.Base(r.URL.Path), ".txt") + res, found := cases[key] + if found { + http.Error(w, res.Content, res.Status) + } else { + http.Error(w, "Unknown test case key!", http.StatusNotFound) + } + })) + defer server.Close() + + kubeReleaseBucketURL = server.URL + + for k, v := range cases { + ver, err := KubernetesReleaseVersion(k) + t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err) + switch { + case err != nil && !v.ErrorExpected: + t.Errorf("KubernetesReleaseVersion: unexpected error for %q. Error: %v", k, err) + case err == nil && v.ErrorExpected: + t.Errorf("KubernetesReleaseVersion: error expected for key %q, but result is %q", k, ver) + case ver != v.Expected: + t.Errorf("KubernetesReleaseVersion: unexpected result for key %q. Expected: %q Actual: %q", k, v.Expected, ver) + } + } +} From 6338b7fda0ef0bdf8ea35efc5fec71d2caffd264 Mon Sep 17 00:00:00 2001 From: Alexander Kanevskiy Date: Wed, 30 Nov 2016 17:57:28 +0200 Subject: [PATCH 3/3] Fallback to known good stable version in case of network errors. Hardcoded known stable version will be returned if user didn't request specific version and kubeadm for some reason not able to fetch latest stable information from release servers. For now, fallback version is v1.4.6 --- cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go | 11 ++++++----- cmd/kubeadm/app/cmd/init.go | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index c269d3c2356..6dfa9614331 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -21,11 +21,12 @@ import ( ) const ( - DefaultServiceDNSDomain = "cluster.local" - DefaultServicesSubnet = "10.96.0.0/12" - DefaultKubernetesVersion = "stable" - DefaultAPIBindPort = 6443 - DefaultDiscoveryBindPort = 9898 + DefaultServiceDNSDomain = "cluster.local" + DefaultServicesSubnet = "10.96.0.0/12" + DefaultKubernetesVersion = "stable" + DefaultKubernetesFallbackVersion = "v1.4.6" + DefaultAPIBindPort = 6443 + DefaultDiscoveryBindPort = 9898 ) func addDefaultingFuncs(scheme *runtime.Scheme) error { diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 8e0f053e5f7..aa889816701 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -196,7 +196,11 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight // validate version argument ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion) if err != nil { - return nil, err + if cfg.KubernetesVersion != kubeadmapiext.DefaultKubernetesVersion { + return nil, err + } else { + ver = kubeadmapiext.DefaultKubernetesFallbackVersion + } } cfg.KubernetesVersion = ver fmt.Println("Using Kubernetes version:", ver)