From 6aaed9249613671b4c5ae1174a494f88a6802495 Mon Sep 17 00:00:00 2001 From: Josh Woodcock Date: Sun, 13 Oct 2019 10:24:40 -0500 Subject: [PATCH 1/4] Add json output for status --- cmd/minikube/cmd/status.go | 85 ++++++++++++++++++++++++----- test/integration/functional_test.go | 41 ++++++++++++++ 2 files changed, 113 insertions(+), 13 deletions(-) diff --git a/cmd/minikube/cmd/status.go b/cmd/minikube/cmd/status.go index 217166107839..80eb8d2a7e19 100644 --- a/cmd/minikube/cmd/status.go +++ b/cmd/minikube/cmd/status.go @@ -17,7 +17,10 @@ limitations under the License. package cmd import ( + "encoding/json" + "fmt" "os" + "strings" "text/template" "github.com/docker/machine/libmachine/state" @@ -35,13 +38,20 @@ import ( ) var statusFormat string +var output string + +type KubeconfigStatus struct { + Correct bool + IP string +} // Status represents the status type Status struct { - Host string - Kubelet string - APIServer string - Kubeconfig string + Host string + Kubelet string + APIServer string + Kubeconfig string + KubeconfigStatus KubeconfigStatus } const ( @@ -63,6 +73,11 @@ var statusCmd = &cobra.Command{ Exit status contains the status of minikube's VM, cluster and kubernetes encoded on it's bits in this order from right to left. Eg: 7 meaning: 1 (for minikube NOK) + 2 (for cluster NOK) + 4 (for kubernetes NOK)`, Run: func(cmd *cobra.Command, args []string) { + + if output != "text" && statusFormat != defaultStatusFormat { + exit.UsageT("Cannot use both --output and --format options") + } + var returnCode = 0 api, err := machine.NewAPIClient() if err != nil { @@ -78,6 +93,8 @@ var statusCmd = &cobra.Command{ kubeletSt := state.None.String() kubeconfigSt := state.None.String() apiserverSt := state.None.String() + var ks bool + var ipString = "" if hostSt == state.Running.String() { clusterBootstrapper, err := getClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper)) @@ -110,12 +127,13 @@ var statusCmd = &cobra.Command{ returnCode |= clusterNotRunningStatusFlag } - ks, err := kubeconfig.IsClusterInConfig(ip, config.GetMachineName()) + ks, err = kubeconfig.IsClusterInConfig(ip, config.GetMachineName()) if err != nil { glog.Errorln("Error kubeconfig status:", err) } if ks { kubeconfigSt = "Correctly Configured: pointing to minikube-vm at " + ip.String() + ipString = ip.String() } else { kubeconfigSt = "Misconfigured: pointing to stale minikube-vm." + "\nTo fix the kubectl context, run minikube update-context" @@ -130,14 +148,19 @@ var statusCmd = &cobra.Command{ Kubelet: kubeletSt, APIServer: apiserverSt, Kubeconfig: kubeconfigSt, + KubeconfigStatus: KubeconfigStatus{ + Correct: ks, + IP: ipString, + }, } - tmpl, err := template.New("status").Parse(statusFormat) - if err != nil { - exit.WithError("Error creating status template", err) - } - err = tmpl.Execute(os.Stdout, status) - if err != nil { - exit.WithError("Error executing status template", err) + + switch strings.ToLower(output) { + case "text": + printStatusText(status) + case "json": + printStatusJSON(status) + default: + exit.WithCodeT(exit.BadUsage, fmt.Sprintf("invalid output format: %s. Valid values: 'text', 'json'", output)) } os.Exit(returnCode) @@ -145,7 +168,43 @@ var statusCmd = &cobra.Command{ } func init() { - statusCmd.Flags().StringVar(&statusFormat, "format", defaultStatusFormat, + statusCmd.Flags().StringVarP(&statusFormat, "format", "f", defaultStatusFormat, `Go template format string for the status output. The format for Go templates can be found here: https://golang.org/pkg/text/template/ For the list accessible variables for the template, see the struct values here: https://godoc.org/k8s.io/minikube/cmd/minikube/cmd#Status`) + statusCmd.Flags().StringVarP(&output, "output", "o", "text", + `minikube status --output OUTPUT. json, text`) +} + +var printStatusText = func(status Status) { + tmpl, err := template.New("status").Parse(statusFormat) + if err != nil { + exit.WithError("Error creating status template", err) + } + err = tmpl.Execute(os.Stdout, status) + if err != nil { + exit.WithError("Error executing status template", err) + } +} + +var printStatusJSON = func(status Status) { + + var kubeConfigStatus interface{} + if status.Kubeconfig != state.None.String() { + kubeConfigStatus = status.KubeconfigStatus + } else { + kubeConfigStatus = nil + } + + var outputObject = map[string]interface{}{ + "Host": status.Host, + "Kubelet": status.Kubelet, + "APIServer": status.APIServer, + "Kubeconfig": kubeConfigStatus, + } + + jsonString, err := json.Marshal(outputObject) + if err != nil { + exit.WithError("Error converting status to json", err) + } + out.String(string(jsonString)) } diff --git a/test/integration/functional_test.go b/test/integration/functional_test.go index 5d03ca370df4..676fe04db195 100644 --- a/test/integration/functional_test.go +++ b/test/integration/functional_test.go @@ -84,6 +84,7 @@ func TestFunctional(t *testing.T) { {"ConfigCmd", validateConfigCmd}, {"DashboardCmd", validateDashboardCmd}, {"DNS", validateDNS}, + {"StatusCmd", validateStatusCmd}, {"LogsCmd", validateLogsCmd}, {"MountCmd", validateMountCmd}, {"ProfileCmd", validateProfileCmd}, @@ -175,6 +176,46 @@ func validateComponentHealth(ctx context.Context, t *testing.T, profile string) } } +func validateStatusCmd(ctx context.Context, t *testing.T, profile string) { + rr, err := Run(t, exec.CommandContext(ctx, Target(), "status")) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + + // Custom format + rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubectl:{{.Kubeconfig}}")) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + match, _ := regexp.MatchString(`host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubectl:([A-z]|[\s]|:|-|[0-9]|.)+`, rr.Stdout.String()) + if !match { + t.Errorf("%s failed: %v. Output for custom format did not match", rr.Args, err) + } + + // Json output + rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-o", "json")) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + var jsonObject map[string]interface{} + err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + if _, ok := jsonObject["Host"]; !ok { + t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Host") + } + if _, ok := jsonObject["Kubelet"]; !ok { + t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Kubelet") + } + if _, ok := jsonObject["APIServer"]; !ok { + t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "APIServer") + } + if _, ok := jsonObject["Kubeconfig"]; !ok { + t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Kubeconfig") + } +} + // validateDashboardCmd asserts that the dashboard command works func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) { args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"} From ca7d378aaaef66ef69c4f88303e2ec29e10f47b3 Mon Sep 17 00:00:00 2001 From: Josh Woodcock Date: Wed, 16 Oct 2019 08:38:38 -0500 Subject: [PATCH 2/4] Change default status output to not include the ip. Simplify json output --- cmd/minikube/cmd/status.go | 53 +++++++++++++------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/cmd/minikube/cmd/status.go b/cmd/minikube/cmd/status.go index 80eb8d2a7e19..65f0fbf1dbd3 100644 --- a/cmd/minikube/cmd/status.go +++ b/cmd/minikube/cmd/status.go @@ -40,18 +40,20 @@ import ( var statusFormat string var output string -type KubeconfigStatus struct { - Correct bool - IP string +var KubeconfigStatus = struct { + Configured string + Misconfigured string +}{ + Configured: `Configured`, + Misconfigured: `Misconfigured`, } // Status represents the status type Status struct { - Host string - Kubelet string - APIServer string - Kubeconfig string - KubeconfigStatus KubeconfigStatus + Host string + Kubelet string + APIServer string + Kubeconfig string } const ( @@ -61,7 +63,7 @@ const ( defaultStatusFormat = `host: {{.Host}} kubelet: {{.Kubelet}} apiserver: {{.APIServer}} -kubectl: {{.Kubeconfig}} +kubeconfig: {{.Kubeconfig}} ` ) @@ -93,8 +95,6 @@ var statusCmd = &cobra.Command{ kubeletSt := state.None.String() kubeconfigSt := state.None.String() apiserverSt := state.None.String() - var ks bool - var ipString = "" if hostSt == state.Running.String() { clusterBootstrapper, err := getClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper)) @@ -127,16 +127,14 @@ var statusCmd = &cobra.Command{ returnCode |= clusterNotRunningStatusFlag } - ks, err = kubeconfig.IsClusterInConfig(ip, config.GetMachineName()) + ks, err := kubeconfig.IsClusterInConfig(ip, config.GetMachineName()) if err != nil { glog.Errorln("Error kubeconfig status:", err) } if ks { - kubeconfigSt = "Correctly Configured: pointing to minikube-vm at " + ip.String() - ipString = ip.String() + kubeconfigSt = KubeconfigStatus.Configured } else { - kubeconfigSt = "Misconfigured: pointing to stale minikube-vm." + - "\nTo fix the kubectl context, run minikube update-context" + kubeconfigSt = KubeconfigStatus.Misconfigured returnCode |= k8sNotRunningStatusFlag } } else { @@ -148,10 +146,6 @@ var statusCmd = &cobra.Command{ Kubelet: kubeletSt, APIServer: apiserverSt, Kubeconfig: kubeconfigSt, - KubeconfigStatus: KubeconfigStatus{ - Correct: ks, - IP: ipString, - }, } switch strings.ToLower(output) { @@ -184,25 +178,14 @@ var printStatusText = func(status Status) { if err != nil { exit.WithError("Error executing status template", err) } + if status.Kubeconfig == KubeconfigStatus.Misconfigured { + out.WarningT("Warning: Your kubectl is pointing to stale minikube-vm.\nTo fix the kubectl context, run `minikube update-context`") + } } var printStatusJSON = func(status Status) { - var kubeConfigStatus interface{} - if status.Kubeconfig != state.None.String() { - kubeConfigStatus = status.KubeconfigStatus - } else { - kubeConfigStatus = nil - } - - var outputObject = map[string]interface{}{ - "Host": status.Host, - "Kubelet": status.Kubelet, - "APIServer": status.APIServer, - "Kubeconfig": kubeConfigStatus, - } - - jsonString, err := json.Marshal(outputObject) + jsonString, err := json.Marshal(status) if err != nil { exit.WithError("Error converting status to json", err) } From dc411abf97feac001652c3b4bb4383a1b6a8bf00 Mon Sep 17 00:00:00 2001 From: Josh Woodcock Date: Thu, 17 Oct 2019 16:10:25 -0500 Subject: [PATCH 3/4] Update validate status integration test to reflect changes to default output --- test/integration/functional_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/functional_test.go b/test/integration/functional_test.go index 676fe04db195..5a7894115dd2 100644 --- a/test/integration/functional_test.go +++ b/test/integration/functional_test.go @@ -183,11 +183,11 @@ func validateStatusCmd(ctx context.Context, t *testing.T, profile string) { } // Custom format - rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubectl:{{.Kubeconfig}}")) + rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}")) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) } - match, _ := regexp.MatchString(`host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubectl:([A-z]|[\s]|:|-|[0-9]|.)+`, rr.Stdout.String()) + match, _ := regexp.MatchString(`host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+)`, rr.Stdout.String()) if !match { t.Errorf("%s failed: %v. Output for custom format did not match", rr.Args, err) } From 144654e40d1e12b234fa3f6ec424ce98ee386b5d Mon Sep 17 00:00:00 2001 From: Josh Woodcock Date: Mon, 21 Oct 2019 13:00:01 -0500 Subject: [PATCH 4/4] Add profile argument to command for validateStatusCmd test --- test/integration/functional_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/functional_test.go b/test/integration/functional_test.go index 5a7894115dd2..b3af56ec5c07 100644 --- a/test/integration/functional_test.go +++ b/test/integration/functional_test.go @@ -177,13 +177,13 @@ func validateComponentHealth(ctx context.Context, t *testing.T, profile string) } func validateStatusCmd(ctx context.Context, t *testing.T, profile string) { - rr, err := Run(t, exec.CommandContext(ctx, Target(), "status")) + rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status")) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) } // Custom format - rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}")) + rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}")) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) } @@ -193,7 +193,7 @@ func validateStatusCmd(ctx context.Context, t *testing.T, profile string) { } // Json output - rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-o", "json")) + rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-o", "json")) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) }