Skip to content

Commit

Permalink
Add json output for status
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Woodcock committed Oct 13, 2019
1 parent 63f1a3a commit ea6a7b1
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 13 deletions.
85 changes: 72 additions & 13 deletions cmd/minikube/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.
package cmd

import (
"encoding/json"
"fmt"
"os"
"strings"
"text/template"

"github.com/docker/machine/libmachine/state"
Expand All @@ -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 (
Expand All @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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"
Expand All @@ -130,22 +148,63 @@ 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)
},
}

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))
}
42 changes: 42 additions & 0 deletions test/integration/functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -83,6 +84,7 @@ func TestFunctional(t *testing.T) {
{"ConfigCmd", validateConfigCmd},
{"DashboardCmd", validateDashboardCmd},
{"DNS", validateDNS},
{"StatusCmd", validateStatusCmd},
{"LogsCmd", validateLogsCmd},
{"MountCmd", validateMountCmd},
{"ProfileCmd", validateProfileCmd},
Expand Down Expand Up @@ -173,6 +175,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"}
Expand Down

0 comments on commit ea6a7b1

Please sign in to comment.