Skip to content

Commit d33f3b2

Browse files
author
Josh Woodcock
committed
Add json output for status
1 parent 63f1a3a commit d33f3b2

File tree

2 files changed

+114
-13
lines changed

2 files changed

+114
-13
lines changed

Diff for: cmd/minikube/cmd/status.go

+72-13
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
"encoding/json"
21+
"fmt"
2022
"os"
23+
"strings"
2124
"text/template"
2225

2326
"github.com/docker/machine/libmachine/state"
@@ -35,13 +38,20 @@ import (
3538
)
3639

3740
var statusFormat string
41+
var output string
42+
43+
type KubeconfigStatus struct {
44+
Correct bool
45+
IP string
46+
}
3847

3948
// Status represents the status
4049
type Status struct {
41-
Host string
42-
Kubelet string
43-
APIServer string
44-
Kubeconfig string
50+
Host string
51+
Kubelet string
52+
APIServer string
53+
Kubeconfig string
54+
KubeconfigStatus KubeconfigStatus
4555
}
4656

4757
const (
@@ -63,6 +73,11 @@ var statusCmd = &cobra.Command{
6373
Exit status contains the status of minikube's VM, cluster and kubernetes encoded on it's bits in this order from right to left.
6474
Eg: 7 meaning: 1 (for minikube NOK) + 2 (for cluster NOK) + 4 (for kubernetes NOK)`,
6575
Run: func(cmd *cobra.Command, args []string) {
76+
77+
if output != "text" && statusFormat != defaultStatusFormat {
78+
exit.UsageT("Cannot use both --output and --format options")
79+
}
80+
6681
var returnCode = 0
6782
api, err := machine.NewAPIClient()
6883
if err != nil {
@@ -78,6 +93,8 @@ var statusCmd = &cobra.Command{
7893
kubeletSt := state.None.String()
7994
kubeconfigSt := state.None.String()
8095
apiserverSt := state.None.String()
96+
var ks bool
97+
var ipString = ""
8198

8299
if hostSt == state.Running.String() {
83100
clusterBootstrapper, err := getClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper))
@@ -110,12 +127,13 @@ var statusCmd = &cobra.Command{
110127
returnCode |= clusterNotRunningStatusFlag
111128
}
112129

113-
ks, err := kubeconfig.IsClusterInConfig(ip, config.GetMachineName())
130+
ks, err = kubeconfig.IsClusterInConfig(ip, config.GetMachineName())
114131
if err != nil {
115132
glog.Errorln("Error kubeconfig status:", err)
116133
}
117134
if ks {
118135
kubeconfigSt = "Correctly Configured: pointing to minikube-vm at " + ip.String()
136+
ipString = ip.String()
119137
} else {
120138
kubeconfigSt = "Misconfigured: pointing to stale minikube-vm." +
121139
"\nTo fix the kubectl context, run minikube update-context"
@@ -130,22 +148,63 @@ var statusCmd = &cobra.Command{
130148
Kubelet: kubeletSt,
131149
APIServer: apiserverSt,
132150
Kubeconfig: kubeconfigSt,
151+
KubeconfigStatus: KubeconfigStatus{
152+
Correct: ks,
153+
IP: ipString,
154+
},
133155
}
134-
tmpl, err := template.New("status").Parse(statusFormat)
135-
if err != nil {
136-
exit.WithError("Error creating status template", err)
137-
}
138-
err = tmpl.Execute(os.Stdout, status)
139-
if err != nil {
140-
exit.WithError("Error executing status template", err)
156+
157+
switch strings.ToLower(output) {
158+
case "text":
159+
printStatusText(status)
160+
case "json":
161+
printStatusJSON(status)
162+
default:
163+
exit.WithCodeT(exit.BadUsage, fmt.Sprintf("invalid output format: %s. Valid values: 'text', 'json'", output))
141164
}
142165

143166
os.Exit(returnCode)
144167
},
145168
}
146169

147170
func init() {
148-
statusCmd.Flags().StringVar(&statusFormat, "format", defaultStatusFormat,
171+
statusCmd.Flags().StringVarP(&statusFormat, "format", "f", defaultStatusFormat,
149172
`Go template format string for the status output. The format for Go templates can be found here: https://golang.org/pkg/text/template/
150173
For the list accessible variables for the template, see the struct values here: https://godoc.org/k8s.io/minikube/cmd/minikube/cmd#Status`)
174+
statusCmd.Flags().StringVarP(&output, "output", "o", "text",
175+
`minikube status --output OUTPUT. json, text`)
176+
}
177+
178+
var printStatusText = func(status Status) {
179+
tmpl, err := template.New("status").Parse(statusFormat)
180+
if err != nil {
181+
exit.WithError("Error creating status template", err)
182+
}
183+
err = tmpl.Execute(os.Stdout, status)
184+
if err != nil {
185+
exit.WithError("Error executing status template", err)
186+
}
187+
}
188+
189+
var printStatusJSON = func(status Status) {
190+
191+
var kubeConfigStatus interface{}
192+
if status.Kubeconfig != state.None.String() {
193+
kubeConfigStatus = status.KubeconfigStatus
194+
} else {
195+
kubeConfigStatus = nil
196+
}
197+
198+
var outputObject = map[string]interface{}{
199+
"Host": status.Host,
200+
"Kubelet": status.Kubelet,
201+
"APIServer": status.APIServer,
202+
"Kubeconfig": kubeConfigStatus,
203+
}
204+
205+
jsonString, err := json.Marshal(outputObject)
206+
if err != nil {
207+
exit.WithError("Error converting status to json", err)
208+
}
209+
out.String(string(jsonString))
151210
}

Diff for: test/integration/functional_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"os"
3030
"os/exec"
3131
"path/filepath"
32+
"regexp"
3233
"strings"
3334
"testing"
3435
"time"
@@ -83,6 +84,7 @@ func TestFunctional(t *testing.T) {
8384
{"ConfigCmd", validateConfigCmd},
8485
{"DashboardCmd", validateDashboardCmd},
8586
{"DNS", validateDNS},
87+
{"StatusCmd", validateStatusCmd},
8688
{"LogsCmd", validateLogsCmd},
8789
{"MountCmd", validateMountCmd},
8890
{"ProfileCmd", validateProfileCmd},
@@ -173,6 +175,46 @@ func validateComponentHealth(ctx context.Context, t *testing.T, profile string)
173175
}
174176
}
175177

178+
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
179+
rr, err := Run(t, exec.CommandContext(ctx, Target(), "status"))
180+
if err != nil {
181+
t.Errorf("%s failed: %v", rr.Args, err)
182+
}
183+
184+
// Custom format
185+
rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubectl:{{.Kubeconfig}}"))
186+
if err != nil {
187+
t.Errorf("%s failed: %v", rr.Args, err)
188+
}
189+
match, _ := regexp.MatchString(`host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubectl:([A-z]|[\s]|:|-|[0-9]|.)+`, rr.Stdout.String())
190+
if !match {
191+
t.Errorf("%s failed: %v. Output for custom format did not match", rr.Args, err)
192+
}
193+
194+
// Json output
195+
rr, err = Run(t, exec.CommandContext(ctx, Target(), "status", "-o", "json"))
196+
if err != nil {
197+
t.Errorf("%s failed: %v", rr.Args, err)
198+
}
199+
var jsonObject map[string]interface{}
200+
err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
201+
if err != nil {
202+
t.Errorf("%s failed: %v", rr.Args, err)
203+
}
204+
if _, ok := jsonObject["Host"]; !ok {
205+
t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Host")
206+
}
207+
if _, ok := jsonObject["Kubelet"]; !ok {
208+
t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Kubelet")
209+
}
210+
if _, ok := jsonObject["APIServer"]; !ok {
211+
t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "APIServer")
212+
}
213+
if _, ok := jsonObject["Kubeconfig"]; !ok {
214+
t.Errorf("%s failed: %v. Missing key %s in json object", rr.Args, err, "Kubeconfig")
215+
}
216+
}
217+
176218
// validateDashboardCmd asserts that the dashboard command works
177219
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
178220
args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}

0 commit comments

Comments
 (0)