diff --git a/hack/update/kubeadm_image_version/update_kubeadm_image_versions.go b/hack/update/kubeadm_image_version/update_kubeadm_image_versions.go new file mode 100644 index 0000000000..8b59265b96 --- /dev/null +++ b/hack/update/kubeadm_image_version/update_kubeadm_image_versions.go @@ -0,0 +1,197 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strings" + "text/template" + "time" + + "golang.org/x/mod/semver" + "k8s.io/klog/v2" + "k8s.io/minikube/hack/update" + "k8s.io/minikube/pkg/minikube/constants" +) + +const ( + // default context timeout + cxTimeout = 300 * time.Second + kubeadmReleaseURL = "https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/amd64/kubeadm" + kubeadmBinaryName = "kubeadm-linux-amd64-%s" + minikubeConstantsFilePath = "pkg/minikube/constants/constants_kubeadm_images.go" + kubeadmImagesTemplate = ` + {{- range $version, $element := .}} + "{{$version}}": { + {{- range $image, $tag := $element}} + "{{$image}}": "{{$tag}}", + {{- end}} + },{{- end}}` +) + +// Data contains kubeadm Images map +type Data struct { + ImageMap string `json:"ImageMap"` +} + +func main() { + + inputVersion := flag.Lookup("kubernetes-version").Value.String() + + imageVersions := make([]string, 0) + + // set a context with defined timeout + ctx, cancel := context.WithTimeout(context.Background(), cxTimeout) + defer cancel() + + if inputVersion == "latest" { + stableImageVersion, latestImageVersion, _, _, err := getK8sVersions(ctx, "kubernetes", "kubernetes") + if err != nil { + klog.Fatal(err) + } + imageVersions = append(imageVersions, stableImageVersion, latestImageVersion) + } else if semver.IsValid(inputVersion) { + imageVersions = append(imageVersions, inputVersion) + } else { + klog.Fatal(errors.New("invalid version")) + } + + for _, imageVersion := range imageVersions { + imageMapString, err := getKubeadmImagesMapString(imageVersion) + if err != nil { + klog.Fatalln(err) + } + + data := Data{ImageMap: imageMapString} + schema := map[string]update.Item{ + minikubeConstantsFilePath: { + Replace: map[string]string{}, + }, + } + + if _, ok := constants.KubeadmImages[imageVersion]; !ok { + schema[minikubeConstantsFilePath].Replace[`KubeadmImages = .*`] = + `KubeadmImages = map[string]map[string]string{ {{.ImageMap}}` + } else { + versionIdentifier := fmt.Sprintf(`"%s": {[^}]+},`, imageVersion) + schema[minikubeConstantsFilePath].Replace[versionIdentifier] = "{{.ImageMap}}" + } + + update.Apply(ctx, schema, data, "", "", -1) + } +} + +func getKubeadmImagesMapString(version string) (string, error) { + url := fmt.Sprintf(kubeadmReleaseURL, version) + fileName := fmt.Sprintf(kubeadmBinaryName, version) + if err := downloadFile(url, fileName); err != nil { + return "", err + } + + kubeadmCommand := fmt.Sprintf("./%s", fileName) + args := []string{"config", "images", "list"} + imageListString, err := executeCommand(kubeadmCommand, args...) + if err != nil { + return "", err + } + + if err := os.Remove(fileName); err != nil { + klog.Errorf("failed to remove binary %s", fileName) + } + + return formatKubeadmImageList(version, imageListString) +} + +func formatKubeadmImageList(version, data string) (string, error) { + templateData := make(map[string]map[string]string) + templateData[version] = make(map[string]string) + lines := strings.Split(data, "\n") + for _, line := range lines { + imageTag := strings.Split(line, ":") + if len(imageTag) == 2 { + templateData[version][imageTag[0]] = imageTag[1] + } + } + + imageTemplate := template.New("kubeadmImage") + t, err := imageTemplate.Parse(kubeadmImagesTemplate) + if err != nil { + return "", err + } + + var bytesBuffer bytes.Buffer + if err := t.Execute(&bytesBuffer, &templateData); err != nil { + return "", err + } + + return bytesBuffer.String(), nil +} + +func downloadFile(url, fileName string) error { + file, err := os.Create(fileName) + if err != nil { + return err + } + defer file.Close() + + response, err := http.Get(url) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("non success status code, while downloading file: %s from: %s", fileName, url) + } + + if _, err := io.Copy(file, response.Body); err != nil { + return err + } + + return os.Chmod(fileName, os.ModePerm) +} + +func executeCommand(command string, args ...string) (string, error) { + output, err := exec.Command(command, args...).Output() + if err != nil { + return "", err + } + return string(output), nil +} + +// getK8sVersion returns Kubernetes versions. +func getK8sVersions(ctx context.Context, owner, repo string) (stable, latest, latestMM, latestP0 string, err error) { + // get Kubernetes versions from GitHub Releases + stable, latest, err = update.GHReleases(ctx, owner, repo) + if err != nil || !semver.IsValid(stable) || !semver.IsValid(latest) { + return "", "", "", "", err + } + latestMM = semver.MajorMinor(latest) + latestP0 = latestMM + ".0" + if semver.Compare(stable, latestP0) == -1 { + latestP0 = latest + } + return stable, latest, latestMM, latestP0, nil +} diff --git a/hack/update/update.go b/hack/update/update.go index dbf7061999..f7732f7bfa 100644 --- a/hack/update/update.go +++ b/hack/update/update.go @@ -62,6 +62,9 @@ func init() { if err := flag.Set("alsologtostderr", "true"); err != nil { klog.Warningf("Unable to set flag value for alsologtostderr: %v", err) } + + // used in update_kubeadm_image_versions.go + flag.String("kubernetes-version", "", "kubernetes-version") flag.Parse() defer klog.Flush() diff --git a/pkg/minikube/bootstrapper/images/images.go b/pkg/minikube/bootstrapper/images/images.go index a3de6b1414..362702407c 100644 --- a/pkg/minikube/bootstrapper/images/images.go +++ b/pkg/minikube/bootstrapper/images/images.go @@ -21,6 +21,8 @@ import ( "fmt" "path" + "k8s.io/minikube/pkg/minikube/constants" + "github.com/blang/semver" "k8s.io/minikube/pkg/version" @@ -31,10 +33,18 @@ func Pause(v semver.Version, mirror string) string { // Should match `PauseVersion` in: // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/constants/constants_unix.go pv := "3.2" + k8sVersion := fmt.Sprintf("v1.%d.0", v.Minor) + imageName := path.Join(kubernetesRepo(mirror), "pause") + + if _, ok := constants.KubeadmImages[k8sVersion]; ok { + pv = constants.KubeadmImages[k8sVersion][imageName] + } + if semver.MustParseRange("<1.18.0-alpha.0")(v) { pv = "3.1" } - return path.Join(kubernetesRepo(mirror), "pause:"+pv) + + return fmt.Sprintf("%s:%s", imageName, pv) } // essentials returns images needed too bootstrap a Kubernetes @@ -60,28 +70,44 @@ func componentImage(name string, v semver.Version, mirror string) string { func coreDNS(v semver.Version, mirror string) string { // Should match `CoreDNSVersion` in // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/constants/constants.go + /*cv := "1.7.0" + + //switch v.Minor { + //case 22: + // cv = "1.8.0" + //case 10, 20, 21: + // cv = "1.7.0" + //case 18: + // cv = "1.6.7" + //case 17: + // cv = "1.6.5" + //case 16: + // cv = "1.6.2" + //case 15, 14: + // cv = "1.3.1" + //case 13: + // cv = "1.2.6" + //case 12: + // cv = "1.2.2" + //case 11: + // cv = "1.1.3" + //} + */ + cv := "1.7.0" - switch v.Minor { - case 22: - cv = "1.8.0" - case 10, 20, 21: - cv = "1.7.0" - case 18: - cv = "1.6.7" - case 17: - cv = "1.6.5" - case 16: - cv = "1.6.2" - case 15, 14: - cv = "1.3.1" - case 13: - cv = "1.2.6" - case 12: - cv = "1.2.2" - case 11: - cv = "1.1.3" + k8sVersion := fmt.Sprintf("v1.%d.0", v.Minor) + imageName := path.Join(kubernetesRepo(mirror), "coredns") + + if v.Minor >= 21 { + imageName = path.Join(imageName, "coredns") } - return path.Join(kubernetesRepo(mirror), "coredns:"+cv) + + if _, ok := constants.KubeadmImages[k8sVersion]; ok { + cv = constants.KubeadmImages[k8sVersion][imageName] + } + + // return path.Join(kubernetesRepo(mirror), "coredns:"+cv) + return fmt.Sprintf("%s:%s", imageName, cv) } // etcd returns the image used for etcd @@ -90,17 +116,24 @@ func etcd(v semver.Version, mirror string) string { // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/constants/constants.go ev := "3.4.13-0" - switch v.Minor { - case 17, 18: - ev = "3.4.3-0" - case 16: - ev = "3.3.15-0" - case 14, 15: - ev = "3.3.10" - case 12, 13: - ev = "3.2.24" - case 11: - ev = "3.2.18" + /*switch v.Minor { + //case 17, 18: + // ev = "3.4.3-0" + //case 16: + // ev = "3.3.15-0" + //case 14, 15: + // ev = "3.3.10" + //case 12, 13: + // ev = "3.2.24" + //case 11: + // ev = "3.2.18" + //} + */ + k8sVersion := fmt.Sprintf("v1.%d.0", v.Minor) + imageName := path.Join(kubernetesRepo(mirror), "etcd") + + if _, ok := constants.KubeadmImages[k8sVersion]; ok { + ev = constants.KubeadmImages[k8sVersion][imageName] } // An awkward special case for v1.19.0 - do not imitate unless necessary @@ -108,7 +141,7 @@ func etcd(v semver.Version, mirror string) string { ev = "3.4.9-1" } - return path.Join(kubernetesRepo(mirror), "etcd:"+ev) + return fmt.Sprintf("%s:%s", imageName, ev) } // auxiliary returns images that are helpful for running minikube diff --git a/pkg/minikube/bootstrapper/images/kubeadm_test.go b/pkg/minikube/bootstrapper/images/kubeadm_test.go index 238c068d38..66f1d95ed0 100644 --- a/pkg/minikube/bootstrapper/images/kubeadm_test.go +++ b/pkg/minikube/bootstrapper/images/kubeadm_test.go @@ -17,6 +17,7 @@ limitations under the License. package images import ( + "fmt" "sort" "testing" @@ -42,17 +43,17 @@ func TestKubeadmImages(t *testing.T) { "docker.io/kubernetesui/dashboard:v2.1.0", "docker.io/kubernetesui/metrics-scraper:v1.0.4", }}, - {"v1.16.1", "mirror.k8s.io", []string{ - "mirror.k8s.io/kube-proxy:v1.16.1", - "mirror.k8s.io/kube-scheduler:v1.16.1", - "mirror.k8s.io/kube-controller-manager:v1.16.1", - "mirror.k8s.io/kube-apiserver:v1.16.1", - "mirror.k8s.io/coredns:1.6.2", - "mirror.k8s.io/etcd:3.3.15-0", - "mirror.k8s.io/pause:3.1", - "mirror.k8s.io/storage-provisioner:" + version.GetStorageProvisionerVersion(), - "mirror.k8s.io/dashboard:v2.1.0", - "mirror.k8s.io/metrics-scraper:v1.0.4", + {"v1.16.0", "k8s.gcr.io", []string{ + "k8s.gcr.io/kube-proxy:v1.16.0", + "k8s.gcr.io/kube-scheduler:v1.16.0", + "k8s.gcr.io/kube-controller-manager:v1.16.0", + "k8s.gcr.io/kube-apiserver:v1.16.0", + "k8s.gcr.io/coredns:1.6.2", + "k8s.gcr.io/etcd:3.3.15-0", + "k8s.gcr.io/pause:3.1", + "k8s.gcr.io/storage-provisioner:" + version.GetStorageProvisionerVersion(), + "k8s.gcr.io/dashboard:v2.1.0", + "k8s.gcr.io/metrics-scraper:v1.0.4", }}, {"v1.15.0", "", []string{ "k8s.gcr.io/kube-proxy:v1.15.0", @@ -111,6 +112,7 @@ func TestKubeadmImages(t *testing.T) { sort.Strings(got) sort.Strings(tc.want) if diff := cmp.Diff(tc.want, got); diff != "" { + fmt.Println(diff) t.Errorf("%s images mismatch (-want +got):\n%s", tc.version, diff) } } diff --git a/pkg/minikube/bootstrapper/images/repo.go b/pkg/minikube/bootstrapper/images/repo.go index ca9e3c366a..437f520f11 100644 --- a/pkg/minikube/bootstrapper/images/repo.go +++ b/pkg/minikube/bootstrapper/images/repo.go @@ -19,6 +19,9 @@ package images // DefaultKubernetesRepo is the default Kubernetes repository const DefaultKubernetesRepo = "k8s.gcr.io" +// DefaultMinikubeRepo is the default Minikube repository +const DefaultMinikubeRepo = "gcr.io/k8s-minikube" + // kubernetesRepo returns the official Kubernetes repository, or an alternate func kubernetesRepo(mirror string) string { if mirror != "" { @@ -32,5 +35,5 @@ func minikubeRepo(mirror string) string { if mirror != "" { return mirror } - return "gcr.io/k8s-minikube" + return DefaultMinikubeRepo } diff --git a/pkg/minikube/bootstrapper/images/repo_test.go b/pkg/minikube/bootstrapper/images/repo_test.go new file mode 100644 index 0000000000..dc0bcf4850 --- /dev/null +++ b/pkg/minikube/bootstrapper/images/repo_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 images + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_kubernetesRepo(t *testing.T) { + tests := []struct { + mirror string + want string + }{ + { + "", + DefaultKubernetesRepo, + }, + { + "mirror.k8s.io", + "mirror.k8s.io", + }, + } + for _, tc := range tests { + got := kubernetesRepo(tc.mirror) + if !cmp.Equal(got, tc.want) { + t.Errorf("mirror miss match, want: %s, got: %s", tc.want, got) + } + } + +} + +func Test_minikubeRepo(t *testing.T) { + tests := []struct { + mirror string + want string + }{ + { + "", + DefaultMinikubeRepo, + }, + { + "mirror.k8s.io", + "mirror.k8s.io", + }, + } + + for _, tc := range tests { + got := minikubeRepo(tc.mirror) + if !cmp.Equal(got, tc.want) { + t.Errorf("mirror miss match, want: %s, got: %s", tc.want, got) + } + } + +} diff --git a/pkg/minikube/constants/constants_kubeadm_images.go b/pkg/minikube/constants/constants_kubeadm_images.go new file mode 100644 index 0000000000..04843cd57b --- /dev/null +++ b/pkg/minikube/constants/constants_kubeadm_images.go @@ -0,0 +1,148 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 constants + +var ( + KubeadmImages = map[string]map[string]string{ + "v1.22.0-rc.0": { + "k8s.gcr.io/coredns/coredns": "v1.8.4", + "k8s.gcr.io/etcd": "3.4.13-0", + "k8s.gcr.io/kube-apiserver": "v1.21.3", + "k8s.gcr.io/kube-controller-manager": "v1.21.3", + "k8s.gcr.io/kube-proxy": "v1.21.3", + "k8s.gcr.io/kube-scheduler": "v1.21.3", + "k8s.gcr.io/pause": "3.5", + }, + "v1.21.3": { + "k8s.gcr.io/coredns/coredns": "v1.8.0", + "k8s.gcr.io/etcd": "3.4.13-0", + "k8s.gcr.io/kube-apiserver": "v1.21.3", + "k8s.gcr.io/kube-controller-manager": "v1.21.3", + "k8s.gcr.io/kube-proxy": "v1.21.3", + "k8s.gcr.io/kube-scheduler": "v1.21.3", + "k8s.gcr.io/pause": "3.4.1", + }, + "v1.21.0": { + "k8s.gcr.io/coredns/coredns": "v1.8.0", + "k8s.gcr.io/etcd": "3.4.13-0", + "k8s.gcr.io/kube-apiserver": "v1.21.3", + "k8s.gcr.io/kube-controller-manager": "v1.21.3", + "k8s.gcr.io/kube-proxy": "v1.21.3", + "k8s.gcr.io/kube-scheduler": "v1.21.3", + "k8s.gcr.io/pause": "3.4.1", + }, + "v1.20.0": { + "k8s.gcr.io/coredns": "1.7.0", + "k8s.gcr.io/etcd": "3.4.13-0", + "k8s.gcr.io/kube-apiserver": "v1.20.9", + "k8s.gcr.io/kube-controller-manager": "v1.20.9", + "k8s.gcr.io/kube-proxy": "v1.20.9", + "k8s.gcr.io/kube-scheduler": "v1.20.9", + "k8s.gcr.io/pause": "3.2", + }, + "v1.19.0": { + "k8s.gcr.io/coredns": "1.7.0", + "k8s.gcr.io/etcd": "3.4.9-1", + "k8s.gcr.io/kube-apiserver": "v1.19.13", + "k8s.gcr.io/kube-controller-manager": "v1.19.13", + "k8s.gcr.io/kube-proxy": "v1.19.13", + "k8s.gcr.io/kube-scheduler": "v1.19.13", + "k8s.gcr.io/pause": "3.2", + }, + "v1.18.0": { + "k8s.gcr.io/coredns": "1.6.7", + "k8s.gcr.io/etcd": "3.4.3-0", + "k8s.gcr.io/kube-apiserver": "v1.18.20", + "k8s.gcr.io/kube-controller-manager": "v1.18.20", + "k8s.gcr.io/kube-proxy": "v1.18.20", + "k8s.gcr.io/kube-scheduler": "v1.18.20", + "k8s.gcr.io/pause": "3.2", + }, + "v1.17.0": { + "k8s.gcr.io/coredns": "1.6.5", + "k8s.gcr.io/etcd": "3.4.3-0", + "k8s.gcr.io/kube-apiserver": "v1.17.17", + "k8s.gcr.io/kube-controller-manager": "v1.17.17", + "k8s.gcr.io/kube-proxy": "v1.17.17", + "k8s.gcr.io/kube-scheduler": "v1.17.17", + "k8s.gcr.io/pause": "3.1", + }, + "v1.16.1": { + "mirror.k8s.io/kube-proxy": "v1.16.1", + "mirror.k8s.io/kube-scheduler": "v1.16.1", + "mirror.k8s.io/kube-controller-manager": "v1.16.1", + "mirror.k8s.io/kube-apiserver": "v1.16.1", + "mirror.k8s.io/coredns": "1.6.2", + "mirror.k8s.io/etcd": "3.3.15-0", + "mirror.k8s.io/pause": "3.1", + }, + "v1.16.0": { + "k8s.gcr.io/coredns": "1.6.2", + "k8s.gcr.io/etcd": "3.3.15-0", + "k8s.gcr.io/kube-apiserver": "v1.16.15", + "k8s.gcr.io/kube-controller-manager": "v1.16.15", + "k8s.gcr.io/kube-proxy": "v1.16.15", + "k8s.gcr.io/kube-scheduler": "v1.16.15", + "k8s.gcr.io/pause": "3.1", + }, + "v1.15.0": { + "k8s.gcr.io/coredns": "1.3.1", + "k8s.gcr.io/etcd": "3.3.10", + "k8s.gcr.io/kube-apiserver": "v1.15.12", + "k8s.gcr.io/kube-controller-manager": "v1.15.12", + "k8s.gcr.io/kube-proxy": "v1.15.12", + "k8s.gcr.io/kube-scheduler": "v1.15.12", + "k8s.gcr.io/pause": "3.1", + }, + "v1.14.0": { + "k8s.gcr.io/coredns": "1.3.1", + "k8s.gcr.io/etcd": "3.3.10", + "k8s.gcr.io/kube-apiserver": "v1.14.10", + "k8s.gcr.io/kube-controller-manager": "v1.14.10", + "k8s.gcr.io/kube-proxy": "v1.14.10", + "k8s.gcr.io/kube-scheduler": "v1.14.10", + "k8s.gcr.io/pause": "3.1", + }, + "v1.13.0": { + "k8s.gcr.io/coredns": "1.2.6", + "k8s.gcr.io/etcd": "3.2.24", + "k8s.gcr.io/kube-apiserver": "v1.13.12", + "k8s.gcr.io/kube-controller-manager": "v1.13.12", + "k8s.gcr.io/kube-proxy": "v1.13.12", + "k8s.gcr.io/kube-scheduler": "v1.13.12", + "k8s.gcr.io/pause": "3.1", + }, + "v1.12.0": { + "k8s.gcr.io/coredns": "1.2.2", + "k8s.gcr.io/etcd": "3.2.24", + "k8s.gcr.io/kube-apiserver": "v1.21.3", + "k8s.gcr.io/kube-controller-manager": "v1.21.3", + "k8s.gcr.io/kube-proxy": "v1.21.3", + "k8s.gcr.io/kube-scheduler": "v1.21.3", + "k8s.gcr.io/pause": "3.1", + }, + "v1.11.0": { + "k8s.gcr.io/coredns": "1.1.3", + "k8s.gcr.io/etcd-amd64": "3.2.18", + "k8s.gcr.io/kube-apiserver-amd64": "v1.11.10", + "k8s.gcr.io/kube-controller-manager-amd64": "v1.11.10", + "k8s.gcr.io/kube-proxy-amd64": "v1.11.10", + "k8s.gcr.io/kube-scheduler-amd64": "v1.11.10", + "k8s.gcr.io/pause-amd64": "3.1", + }, + } +)