diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index 1c44d2dbb95..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 = "v1.4.4" - 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 6107f589427..aa889816701 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -193,6 +193,18 @@ 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 { + if cfg.KubernetesVersion != kubeadmapiext.DefaultKubernetesVersion { + return nil, err + } else { + ver = kubeadmapiext.DefaultKubernetesFallbackVersion + } + } + 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..c537e8d1dd2 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 = [ @@ -34,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.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) +} 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) + } + } +}