mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Merge pull request #26 from brendandburns/ux
Extend the CLI output to allow JSON, YAML and Human Readable output
This commit is contained in:
commit
0b6702e80b
@ -40,6 +40,8 @@ var (
|
|||||||
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
|
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
|
||||||
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
|
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
|
||||||
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user")
|
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user")
|
||||||
|
json = flag.Bool("json", false, "If true, print raw JSON for responses")
|
||||||
|
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
@ -63,6 +65,15 @@ func main() {
|
|||||||
var request *http.Request
|
var request *http.Request
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
var printer cloudcfg.ResourcePrinter
|
||||||
|
if *json {
|
||||||
|
printer = &cloudcfg.IdentityPrinter{}
|
||||||
|
} else if *yaml {
|
||||||
|
printer = &cloudcfg.YAMLPrinter{}
|
||||||
|
} else {
|
||||||
|
printer = &cloudcfg.HumanReadablePrinter{}
|
||||||
|
}
|
||||||
|
|
||||||
auth, err := cloudcfg.LoadAuthInfo(*authConfig)
|
auth, err := cloudcfg.LoadAuthInfo(*authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error loading auth: %#v", err)
|
log.Fatalf("Error loading auth: %#v", err)
|
||||||
@ -124,5 +135,9 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: %#v", err)
|
log.Fatalf("Error: %#v", err)
|
||||||
}
|
}
|
||||||
fmt.Println(body)
|
err = printer.Print(body, os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to print: %#v", err)
|
||||||
|
}
|
||||||
|
fmt.Print("\n")
|
||||||
}
|
}
|
||||||
|
212
pkg/cloudcfg/resource_printer.go
Normal file
212
pkg/cloudcfg/resource_printer.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package cloudcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"gopkg.in/v1/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourcePrinter is an interface that knows how to print API resources
|
||||||
|
type ResourcePrinter interface {
|
||||||
|
// Print receives an arbitrary JSON body, formats it and prints it to a writer
|
||||||
|
Print(string, io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identity printer simply copies the body out to the output stream
|
||||||
|
type IdentityPrinter struct{}
|
||||||
|
|
||||||
|
func (i *IdentityPrinter) Print(data string, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprint(w, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLPrinter parses JSON, and re-formats as YAML
|
||||||
|
type YAMLPrinter struct{}
|
||||||
|
|
||||||
|
func (y *YAMLPrinter) Print(data string, w io.Writer) error {
|
||||||
|
var obj interface{}
|
||||||
|
if err := json.Unmarshal([]byte(data), &obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
output, err := yaml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprint(w, string(output))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HumanReadablePrinter attempts to provide more elegant output
|
||||||
|
type HumanReadablePrinter struct{}
|
||||||
|
|
||||||
|
var podColumns = []string{"Name", "Image(s)", "Host", "Labels"}
|
||||||
|
var replicationControllerColumns = []string{"Name", "Image(s)", "Label Query", "Replicas"}
|
||||||
|
var serviceColumns = []string{"Name", "Label Query", "Port"}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) unknown(data string, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprintf(w, "Unknown object: %s", data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printHeader(columnNames []string, w io.Writer) error {
|
||||||
|
if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var lines []string
|
||||||
|
for _, _ = range columnNames {
|
||||||
|
lines = append(lines, "----------")
|
||||||
|
}
|
||||||
|
_, err := fmt.Fprintf(w, "%s\n", strings.Join(lines, "\t"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) makeImageList(manifest api.ContainerManifest) string {
|
||||||
|
var images []string
|
||||||
|
for _, container := range manifest.Containers {
|
||||||
|
images = append(images, container.Image)
|
||||||
|
}
|
||||||
|
return strings.Join(images, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) makeLabelsList(labels map[string]string) string {
|
||||||
|
var vals []string
|
||||||
|
for key, value := range labels {
|
||||||
|
vals = append(vals, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
return strings.Join(vals, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printPod(pod api.Pod, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
|
||||||
|
pod.ID, h.makeImageList(pod.DesiredState.Manifest), pod.CurrentState.Host, h.makeLabelsList(pod.Labels))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printPodList(podList api.PodList, w io.Writer) error {
|
||||||
|
for _, pod := range podList.Items {
|
||||||
|
if err := h.printPod(pod, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printReplicationController(ctrl api.ReplicationController, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n",
|
||||||
|
ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), h.makeLabelsList(ctrl.DesiredState.ReplicasInSet), ctrl.DesiredState.Replicas)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printReplicationControllerList(list api.ReplicationControllerList, w io.Writer) error {
|
||||||
|
for _, ctrl := range list.Items {
|
||||||
|
if err := h.printReplicationController(ctrl, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printService(svc api.Service, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprintf(w, "%s\t%s\t%d\n", svc.ID, h.makeLabelsList(svc.Labels), svc.Port)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) printServiceList(list api.ServiceList, w io.Writer) error {
|
||||||
|
for _, svc := range list.Items {
|
||||||
|
if err := h.printService(svc, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO replace this with something that returns a concrete printer object, rather than
|
||||||
|
// having the secondary switch below.
|
||||||
|
func (h *HumanReadablePrinter) extractObject(data, kind string) (interface{}, error) {
|
||||||
|
// TODO: I think this can be replaced with some reflection and a map[string]type
|
||||||
|
switch kind {
|
||||||
|
case "cluster#pod":
|
||||||
|
var obj api.Pod
|
||||||
|
if err := json.Unmarshal([]byte(data), &obj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
|
case "cluster#podList":
|
||||||
|
var list api.PodList
|
||||||
|
if err := json.Unmarshal([]byte(data), &list); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
case "cluster#replicationController":
|
||||||
|
var ctrl api.ReplicationController
|
||||||
|
if err := json.Unmarshal([]byte(data), &ctrl); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ctrl, nil
|
||||||
|
case "cluster#replicationControllerList":
|
||||||
|
var list api.ReplicationControllerList
|
||||||
|
if err := json.Unmarshal([]byte(data), &list); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
case "cluster#service":
|
||||||
|
var ctrl api.Service
|
||||||
|
if err := json.Unmarshal([]byte(data), &ctrl); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ctrl, nil
|
||||||
|
case "cluster#serviceList":
|
||||||
|
var list api.ServiceList
|
||||||
|
if err := json.Unmarshal([]byte(data), &list); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown kind: %s", kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HumanReadablePrinter) Print(data string, output io.Writer) error {
|
||||||
|
w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0)
|
||||||
|
defer w.Flush()
|
||||||
|
var obj interface{}
|
||||||
|
if err := json.Unmarshal([]byte(data), &obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, contains := obj.(map[string]interface{})["kind"]; !contains {
|
||||||
|
return fmt.Errorf("Unexpected object with no 'kind' field: %s", data)
|
||||||
|
}
|
||||||
|
kind := (obj.(map[string]interface{})["kind"]).(string)
|
||||||
|
obj, err := h.extractObject(data, kind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch obj.(type) {
|
||||||
|
case api.Pod:
|
||||||
|
h.printHeader(podColumns, w)
|
||||||
|
return h.printPod(obj.(api.Pod), w)
|
||||||
|
case api.PodList:
|
||||||
|
h.printHeader(podColumns, w)
|
||||||
|
return h.printPodList(obj.(api.PodList), w)
|
||||||
|
case api.ReplicationController:
|
||||||
|
h.printHeader(replicationControllerColumns, w)
|
||||||
|
return h.printReplicationController(obj.(api.ReplicationController), w)
|
||||||
|
case api.ReplicationControllerList:
|
||||||
|
h.printHeader(replicationControllerColumns, w)
|
||||||
|
return h.printReplicationControllerList(obj.(api.ReplicationControllerList), w)
|
||||||
|
case api.Service:
|
||||||
|
h.printHeader(serviceColumns, w)
|
||||||
|
return h.printService(obj.(api.Service), w)
|
||||||
|
case api.ServiceList:
|
||||||
|
h.printHeader(serviceColumns, w)
|
||||||
|
return h.printServiceList(obj.(api.ServiceList), w)
|
||||||
|
default:
|
||||||
|
return h.unknown(data, w)
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,7 @@ func (storage *ControllerRegistryStorage) Delete(id string) error {
|
|||||||
func (storage *ControllerRegistryStorage) Extract(body string) (interface{}, error) {
|
func (storage *ControllerRegistryStorage) Extract(body string) (interface{}, error) {
|
||||||
result := ReplicationController{}
|
result := ReplicationController{}
|
||||||
err := json.Unmarshal([]byte(body), &result)
|
err := json.Unmarshal([]byte(body), &result)
|
||||||
|
result.Kind = "cluster#replicationController"
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +124,9 @@ func TestExtractControllerJson(t *testing.T) {
|
|||||||
expectNoError(t, err)
|
expectNoError(t, err)
|
||||||
controllerOut, err := storage.Extract(string(body))
|
controllerOut, err := storage.Extract(string(body))
|
||||||
expectNoError(t, err)
|
expectNoError(t, err)
|
||||||
jsonOut, err := json.Marshal(controllerOut)
|
// Extract adds a Kind
|
||||||
expectNoError(t, err)
|
controller.Kind = "cluster#replicationController"
|
||||||
if string(body) != string(jsonOut) {
|
if !reflect.DeepEqual(controller, controllerOut) {
|
||||||
t.Errorf("Expected %#v, found %#v", controller, controllerOut)
|
t.Errorf("Expected %#v, found %#v", controller, controllerOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,7 @@ func (storage *PodRegistryStorage) Delete(id string) error {
|
|||||||
func (storage *PodRegistryStorage) Extract(body string) (interface{}, error) {
|
func (storage *PodRegistryStorage) Extract(body string) (interface{}, error) {
|
||||||
pod := Pod{}
|
pod := Pod{}
|
||||||
err := json.Unmarshal([]byte(body), &pod)
|
err := json.Unmarshal([]byte(body), &pod)
|
||||||
|
pod.Kind = "cluster#pod"
|
||||||
return pod, err
|
return pod, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package registry
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
. "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
@ -127,9 +128,9 @@ func TestExtractJson(t *testing.T) {
|
|||||||
expectNoError(t, err)
|
expectNoError(t, err)
|
||||||
podOut, err := storage.Extract(string(body))
|
podOut, err := storage.Extract(string(body))
|
||||||
expectNoError(t, err)
|
expectNoError(t, err)
|
||||||
jsonOut, err := json.Marshal(podOut)
|
// Extract adds in a kind
|
||||||
expectNoError(t, err)
|
pod.Kind = "cluster#pod"
|
||||||
if string(body) != string(jsonOut) {
|
if !reflect.DeepEqual(pod, podOut) {
|
||||||
t.Errorf("Expected %#v, found %#v", pod, podOut)
|
t.Errorf("Expected %#v, found %#v", pod, podOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ func (sr *ServiceRegistryStorage) Delete(id string) error {
|
|||||||
func (sr *ServiceRegistryStorage) Extract(body string) (interface{}, error) {
|
func (sr *ServiceRegistryStorage) Extract(body string) (interface{}, error) {
|
||||||
var svc Service
|
var svc Service
|
||||||
err := json.Unmarshal([]byte(body), &svc)
|
err := json.Unmarshal([]byte(body), &svc)
|
||||||
|
svc.Kind = "cluster#service"
|
||||||
return svc, err
|
return svc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user