Repository: kubernetes/minikube (go)

Issue:
list all currently supported Kubernetes versions
### What Happened?

users requested the ability to list all supported Kubernetes versions by their minikube, as per https://minikube.sigs.k8s.io/docs/, it should be the _latest Kubernetes release (+6 previous minor versions)_

ref: https://github.com/kubernetes/minikube/issues/1201, https://github.com/kubernetes/minikube/issues/4121

### Attach the log file

n/a

### Operating System

_No response_

### Driver

_No response_

Pull request:
list all currently supported Kubernetes versions
fixes #13773

### examples:

- `minikube config defaults`
```
$ minikube config defaults --help
list displays all valid default settings for PROPERTY_NAME
Acceptable fields: 

 * driver
 * kubernetes-version

Options:
  -o, --output='': Output format. Accepted values: [json, yaml]

Usage:
  minikube config defaults PROPERTY_NAME [flags] [options]

Use "minikube options" for a list of global command-line options (applies to all commands).
```
```
$ minikube config defaults kubernetes-version
* v1.23.5
* v1.23.4
* v1.23.3
...
* v1.16.2
* v1.16.1
* v1.16.0
```
```
$ minikube config defaults kubernetes-version --output json
["v1.23.5","v1.23.4","v1.23.3","v1.23.2","v1.23.1","v1.23.0","v1.23.0-rc.1","v1.23.0-rc.0","v1.23.0-beta.0","v1.23.0-alpha.4","v1.23.0-alpha.3","v1.23.0-alpha.2","v1.23.0-alpha.1","v1.22.8","v1.22.7","v1.22.6","v1.22.5","v1.22.4","v1.22.3","v1.22.2","v1.22.1","v1.22.0","v1.22.0-rc.0","v1.22.0-beta.2","v1.22.0-beta.1","v1.22.0-beta.0","v1.22.0-alpha.3","v1.22.0-alpha.2","v1.22.0-alpha.1","v1.21.11","v1.21.10","v1.21.9","v1.21.8","v1.21.7","v1.21.6","v1.21.5","v1.21.4","v1.21.3","v1.21.2","v1.21.1","v1.21.0","v1.21.0-rc.0","v1.21.0-beta.1","v1.21.0-beta.0","v1.21.0-alpha.3","v1.21.0-alpha.2","v1.21.0-alpha.1","v1.20.15","v1.20.14","v1.20.13","v1.20.12","v1.20.11","v1.20.10","v1.20.9","v1.20.8","v1.20.7","v1.20.6","v1.20.5","v1.20.4","v1.20.3","v1.20.2","v1.20.1","v1.20.0","v1.20.0-rc.0","v1.20.0-beta.2","v1.20.0-beta.1","v1.20.0-beta.0","v1.20.0-alpha.3","v1.20.0-alpha.2","v1.20.0-alpha.1","v1.19.16","v1.19.15","v1.19.14","v1.19.13","v1.19.12","v1.19.11","v1.19.10","v1.19.9","v1.19.8","v1.19.7","v1.19.6","v1.19.5","v1.19.4","v1.19.3","v1.19.2","v1.19.1","v1.19.0","v1.19.0-rc.4","v1.19.0-rc.3","v1.19.0-rc.2","v1.19.0-rc.1","v1.19.0-beta.2","v1.19.0-beta.1","v1.19.0-beta.0","v1.19.0-alpha.3","v1.19.0-alpha.2","v1.19.0-alpha.1","v1.18.20","v1.18.19","v1.18.18","v1.18.17","v1.18.16","v1.18.15","v1.18.14","v1.18.13","v1.18.12","v1.18.10","v1.18.9","v1.18.8","v1.18.6","v1.18.5","v1.18.5-rc.1","v1.18.4","v1.18.3","v1.18.2","v1.18.1","v1.18.0","v1.18.0-rc.1","v1.18.0-beta.2","v1.18.0-beta.1","v1.18.0-alpha.5","v1.18.0-alpha.3","v1.18.0-alpha.2","v1.18.0-alpha.1","v1.17.17","v1.17.16","v1.17.15","v1.17.14","v1.17.13","v1.17.12","v1.17.11","v1.17.9","v1.17.8","v1.17.8-rc.1","v1.17.7","v1.17.6","v1.17.5","v1.17.4","v1.17.3","v1.17.2","v1.17.1","v1.17.0","v1.17.0-rc.2","v1.17.0-rc.1","v1.17.0-beta.2","v1.17.0-beta.1","v1.17.0-alpha.3","v1.17.0-alpha.2","v1.17.0-alpha.1","v1.16.15","v1.16.14","v1.16.13","v1.16.12","v1.16.12-rc.1","v1.16.11","v1.16.10","v1.16.9","v1.16.8","v1.16.7","v1.16.6","v1.16.5","v1.16.4","v1.16.3","v1.16.2","v1.16.1","v1.16.0"]
```
```
$ minikube config defaults kubernetes-version --output yaml
- v1.23.5
- v1.23.4
- v1.23.3
...
- v1.16.2
- v1.16.1
- v1.16.0
```

- catching cluster created by older minikube cluster then restarted with newer minikube that deprecated support for the original k8s cluster version (similar as for trying to start a new cluster with unsupported k8s version)

```
$ minikube-1.25.2 start --profile k8s-1.16.0 --kubernetes-version v1.16.0 --driver kvm2
😄  [k8s-1.16.0] minikube v1.25.2 on Opensuse-Tumbleweed
✨  Using the kvm2 driver based on user configuration
👍  Starting control plane node k8s-1.16.0 in cluster k8s-1.16.0
🔥  Creating kvm2 VM (CPUs=2, Memory=6000MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.16.0 on Docker 20.10.12 ...
    ▪ kubelet.housekeeping-interval=5m
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "k8s-1.16.0" cluster and "default" namespace by default
```
```
$ minikube-1.25.2 stop --profile k8s-1.16.0
✋  Stopping node "k8s-1.16.0"  ...
🛑  1 node stopped.
```
```
$ minikube start --profile k8s-1.16.0
😄  [k8s-1.16.0] minikube v1.25.2 on Opensuse-Tumbleweed
❗  Specified Kubernetes version 1.16.0 is less than the oldest supported version: v1.17.0. Use `minikube version --kubernetes` for details.
❗  You can force an unsupported Kubernetes version via the --force flag

❌  Exiting due to K8S_OLD_UNSUPPORTED: Kubernetes 1.16.0 is not supported by this release of minikube
```
```
$ minikube start --profile k8s-1.16.0 --force
😄  [k8s-1.16.0] minikube v1.25.2 on Opensuse-Tumbleweed
❗  minikube skips various validations when --force is supplied; this may lead to unexpected behavior
🆕  Kubernetes 1.23.3 is now available. If you would like to upgrade, specify: --kubernetes-version=v1.23.3
✨  Using the kvm2 driver based on existing profile
👍  Starting control plane node k8s-1.16.0 in cluster k8s-1.16.0
🔄  Restarting existing kvm2 VM for "k8s-1.16.0" ...
🐳  Preparing Kubernetes v1.16.0 on Docker 20.10.12 ...
    ▪ kubelet.housekeeping-interval=5m
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass

❗  /home/prezha/bin/k8s/kubectl is version 1.23.4, which may have incompatibilites with Kubernetes 1.16.0.
    ▪ Want kubectl v1.16.0? Try 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "k8s-1.16.0" cluster and "default" namespace by default
```

- start new cluster with _too-new_ k8s version

```
$ minikube start --profile k8s-1.24.0-alpha.3 --kubernetes-version v1.24.0-alpha.3 --driver kvm2
😄  [k8s-1.24.0-alpha.3] minikube v1.25.2 on Opensuse-Tumbleweed
❗  Specified Kubernetes version 1.24.0-alpha.3 is newer than the newest supported version: v1.23.4. Use `minikube version --kubernetes` for details.
✨  Using the kvm2 driver based on user configuration
👍  Starting control plane node k8s-1.24.0-alpha.3 in cluster k8s-1.24.0-alpha.3
🔥  Creating kvm2 VM (CPUs=2, Memory=6000MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.24.0-alpha.3 on Docker 20.10.12 ...
    ▪ kubelet.housekeeping-interval=5m
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "k8s-1.24.0-alpha.3" cluster and "default" namespace by default
```

Patch:
diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go
index 6271fc0e0..4af7698da 100644
--- a/cmd/minikube/cmd/config/config.go
+++ b/cmd/minikube/cmd/config/config.go
@@ -102,8 +102,9 @@ var settings = []Setting{
 		validations: []setFn{IsValidPath},
 	},
 	{
-		name: "kubernetes-version",
-		set:  SetString,
+		name:          "kubernetes-version",
+		set:           SetString,
+		validDefaults: supportedKubernetesVersions,
 	},
 	{
 		name:        "iso-url",
diff --git a/cmd/minikube/cmd/config/defaults.go b/cmd/minikube/cmd/config/defaults.go
index 180747088..b21733d66 100644
--- a/cmd/minikube/cmd/config/defaults.go
+++ b/cmd/minikube/cmd/config/defaults.go
@@ -21,32 +21,31 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v2"
+	"k8s.io/minikube/pkg/minikube/exit"
 	"k8s.io/minikube/pkg/minikube/out"
+	"k8s.io/minikube/pkg/minikube/reason"
 )
 
+var defaultsOutput string
+
 var configDefaultsCommand = &cobra.Command{
 	Use:   "defaults PROPERTY_NAME",
 	Short: "Lists all valid default values for PROPERTY_NAME",
 	Long: `list displays all valid default settings for PROPERTY_NAME
 Acceptable fields: ` + "\n\n" + fieldsWithDefaults(),
-	RunE: func(cmd *cobra.Command, args []string) error {
-		if len(args) == 0 {
-			cmd.SilenceErrors = true
-			return errors.New("not enough arguments.\nusage: minikube config list PROPERTY_NAME")
-		}
-		if len(args) > 1 {
+	Run: func(cmd *cobra.Command, args []string) {
+		if len(args) != 1 {
 			cmd.SilenceErrors = true
-			return fmt.Errorf("too many arguments (%d)\nusage: minikube config list PROPERTY_NAME", len(args))
+			exit.Message(reason.Usage, "usage: minikube config list PROPERTY_NAME")
 		}
-
 		property := args[0]
 		defaults, err := getDefaults(property)
 		if err != nil {
-			return err
+			exit.Message(reason.Usage, "error getting defaults: {{.error}}", out.V{"error": err})
 		}
-		return printDefaults(defaults)
+		printDefaults(defaults)
 	},
 }
 
@@ -61,19 +60,27 @@ func getDefaults(property string) ([]string, error) {
 	return setting.validDefaults(), nil
 }
 
-func printDefaults(defaults []string) error {
-	if output == "json" {
+func printDefaults(defaults []string) {
+	switch strings.ToLower(defaultsOutput) {
+	case "":
+		for _, d := range defaults {
+			out.Ln("* %s", d)
+		}
+	case "json":
 		encoding, err := json.Marshal(defaults)
 		if err != nil {
-			return errors.Wrap(err, "encoding json")
+			exit.Error(reason.InternalJSONMarshal, "json encoding failure", err)
 		}
 		out.Ln(string(encoding))
-		return nil
-	}
-	for _, d := range defaults {
-		out.Ln("* %s", d)
+	case "yaml":
+		encoding, err := yaml.Marshal(defaults)
+		if err != nil {
+			exit.Error(reason.InternalYamlMarshal, "yaml encoding failure", err)
+		}
+		out.Ln(string(encoding))
+	default:
+		exit.Message(reason.InternalOutputUsage, "error: --output must be 'yaml' or 'json'")
 	}
-	return nil
 }
 
 func fieldsWithDefaults() string {
@@ -87,6 +94,6 @@ func fieldsWithDefaults() string {
 }
 
 func init() {
-	configDefaultsCommand.Flags().StringVar(&output, "output", "", "Output format. Accepted values: [json]")
+	configDefaultsCommand.Flags().StringVarP(&defaultsOutput, "output", "o", "", "Output format. Accepted values: [json, yaml]")
 	ConfigCmd.AddCommand(configDefaultsCommand)
 }
diff --git a/cmd/minikube/cmd/config/kubernetes_version.go b/cmd/minikube/cmd/config/kubernetes_version.go
new file mode 100644
index 000000000..586002c1b
--- /dev/null
+++ b/cmd/minikube/cmd/config/kubernetes_version.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2022 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 config
+
+import (
+	"context"
+	"sort"
+
+	"github.com/google/go-github/v43/github"
+	"golang.org/x/mod/semver"
+	"k8s.io/minikube/pkg/minikube/constants"
+)
+
+// supportedKubernetesVersions returns reverse-sort supported Kubernetes releases from GitHub that are in [constants.OldestKubernetesVersion, constants.NewestKubernetesVersion] range, including prereleases.
+// in case it cannot get it from GitHub, in addition to [constants.NewestKubernetesVersion, constants.OldestKubernetesVersion], 'constants.DefaultKubernetesVersion' is also returned if different from 'constants.NewestKubernetesVersion'.
+func supportedKubernetesVersions() (releases []string) {
+	minver := constants.OldestKubernetesVersion
+	defver := constants.DefaultKubernetesVersion
+	maxver := constants.NewestKubernetesVersion
+
+	ghc := github.NewClient(nil)
+
+	opts := &github.ListOptions{PerPage: 100}
+	for (opts.Page+1)*100 <= 300 {
+		rls, resp, err := ghc.Repositories.ListReleases(context.Background(), "kubernetes", "kubernetes", opts)
+		if err != nil {
+			v := []string{maxver}
+			if defver != maxver {
+				v = append(v, defver)
+			}
+			v = append(v, minver)
+			return v
+		}
+		for _, rl := range rls {
+			ver := rl.GetTagName()
+			if !semver.IsValid(ver) {
+				continue
+			}
+			// skip out-of-range versions
+			if (minver != "" && semver.Compare(minver, ver) == 1) || (maxver != "" && semver.Compare(ver, maxver) == 1) {
+				continue
+			}
+			releases = append(releases, ver)
+		}
+		if resp.NextPage == 0 {
+			break
+		}
+		opts.Page = resp.NextPage
+	}
+	sort.Slice(releases, func(i, j int) bool { return semver.Compare(releases[i], releases[j]) == 1 })
+	return releases
+}
diff --git a/cmd/minikube/cmd/config/profile_list.go b/cmd/minikube/cmd/config/profile_list.go
index d03def871..f7be8ac60 100644
--- a/cmd/minikube/cmd/config/profile_list.go
+++ b/cmd/minikube/cmd/config/profile_list.go
@@ -41,7 +41,7 @@ import (
 	"k8s.io/klog/v2"
 )
 
-var output string
+var profileOutput string
 var isLight bool
 
 var profileListCmd = &cobra.Command{
@@ -49,13 +49,13 @@ var profileListCmd = &cobra.Command{
 	Short: "Lists all minikube profiles.",
 	Long:  "Lists all valid minikube profiles and detects all possible invalid profiles.",
 	Run: func(cmd *cobra.Command, args []string) {
-		switch strings.ToLower(output) {
+		switch strings.ToLower(profileOutput) {
 		case "json":
 			printProfilesJSON()
 		case "table":
 			printProfilesTable()
 		default:
-			exit.Message(reason.Usage, fmt.Sprintf("invalid output format: %s. Valid values: 'table', 'json'", output))
+			exit.Message(reason.Usage, fmt.Sprintf("invalid output format: %s. Valid values: 'table', 'json'", profileOutput))
 		}
 	},
 }
@@ -217,7 +217,7 @@ func profilesOrDefault(profiles []*config.Profile) []*config.Profile {
 }
 
 func init() {
-	profileListCmd.Flags().StringVarP(&output, "output", "o", "table", "The output format. One of 'json', 'table'")
+	profileListCmd.Flags().StringVarP(&profileOutput, "output", "o", "table", "The output format. One of 'json', 'table'")
 	profileListCmd.Flags().BoolVarP(&isLight, "light", "l", false, "If true, returns list of profiles faster by skipping validating the status of the cluster.")
 	ProfileCmd.AddCommand(profileListCmd)
 }
diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go
index d431edd0d..afee89071 100644
--- a/cmd/minikube/cmd/start.go
+++ b/cmd/minikube/cmd/start.go
@@ -1585,10 +1585,10 @@ func validateKubernetesVersion(old *config.ClusterConfig) {
 		exitIfNotForced(reason.KubernetesTooNew, "Kubernetes {{.version}} is not supported by this release of minikube", out.V{"version": nvs})
 	}
 	if nvs.GT(newestVersion) {
-		out.WarningT("Specified Kubernetes version {{.specified}} is newer than the newest supported version: {{.newest}}", out.V{"specified": nvs, "newest": constants.NewestKubernetesVersion})
+		out.WarningT("Specified Kubernetes version {{.specified}} is newer than the newest supported version: {{.newest}}. Use `minikube config defaults kubernetes-version` for details.", out.V{"specified": nvs, "newest": constants.NewestKubernetesVersion})
 	}
 	if nvs.LT(oldestVersion) {
-		out.WarningT("Specified Kubernetes version {{.specified}} is less than the oldest supported version: {{.oldest}}", out.V{"specified": nvs, "oldest": constants.OldestKubernetesVersion})
+		out.WarningT("Specified Kubernetes version {{.specified}} is less than the oldest supported version: {{.oldest}}. Use `minikube config defaults kubernetes-version` for details.", out.V{"specified": nvs, "oldest": constants.OldestKubernetesVersion})
 		if !viper.GetBool(force) {
 			out.WarningT("You can force an unsupported Kubernetes version via the --force flag")
 		}
diff --git a/cmd/minikube/cmd/version.go b/cmd/minikube/cmd/version.go
index fef929f7f..6fcbb92ec 100644
--- a/cmd/minikube/cmd/version.go
+++ b/cmd/minikube/cmd/version.go
@@ -45,7 +45,7 @@ var versionCmd = &cobra.Command{
 	Run: func(command *cobra.Command, args []string) {
 		minikubeVersion := version.GetVersion()
 		gitCommitID := version.GetGitCommitID()
-		data := map[string]string{
+		data := map[string]interface{}{
 			"minikubeVersion": minikubeVersion,
 			"commit":          gitCommitID,
 		}
diff --git a/go.mod b/go.mod
index 4a6f594ba..46e0fce56 100644
--- a/go.mod
+++ b/go.mod
@@ -100,6 +100,7 @@ require (
 require (
 	github.com/Xuanwo/go-locale v1.1.0
 	github.com/docker/go-connections v0.4.0
+	github.com/google/go-github/v43 v43.0.0
 	github.com/opencontainers/runc v1.0.2
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
 )
@@ -153,7 +154,7 @@ require (
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.3 // indirect
 	github.com/google/gnostic v0.5.7-v3refs // indirect
-	github.com/google/go-querystring v1.0.0 // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.1.0 // indirect
 	github.com/googleapis/gax-go/v2 v2.3.0 // indirect
 	github.com/googleapis/go-type-adapters v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 3e02c0b34..095607fb3 100644
--- a/go.sum
+++ b/go.sum
@@ -576,8 +576,11 @@ github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZ
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs=
 github.com/google/go-github/v36 v36.0.0/go.mod h1:LFlKC047IOqiglRGNqNb9s/iAPTnnjtlshm+bxp+kwk=
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
+github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=


Test patch:
diff --git a/cmd/minikube/cmd/config/defaults_test.go b/cmd/minikube/cmd/config/defaults_test.go
index e87b214e2..5fbc69fca 100644
--- a/cmd/minikube/cmd/config/defaults_test.go
+++ b/cmd/minikube/cmd/config/defaults_test.go
@@ -77,12 +77,10 @@ func TestPrintDefaults(t *testing.T) {
 	}
 	for _, tc := range tcs {
 		t.Run(tc.description, func(t *testing.T) {
-			output = tc.format
+			defaultsOutput = tc.format
 			f := tests.NewFakeFile()
 			out.SetOutFile(f)
-			if err := printDefaults(defaults); err != nil {
-				t.Fatalf("error printing defaults: %v", err)
-			}
+			printDefaults(defaults)
 			if f.String() != tc.expected {
 				t.Fatalf("Expected: %v\n Actual: %v\n", tc.expected, f.String())
 			}