diff --git a/cmd/cloudcfg/cloudcfg.go b/cmd/cloudcfg/cloudcfg.go index 29a34240b6c..f495de79fc3 100644 --- a/cmd/cloudcfg/cloudcfg.go +++ b/cmd/cloudcfg/cloudcfg.go @@ -40,6 +40,8 @@ var ( portSpec = flag.String("p", "", "The port spec, comma-separated list of :,...") 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") + 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() { @@ -63,6 +65,15 @@ func main() { var request *http.Request 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) if err != nil { log.Fatalf("Error loading auth: %#v", err) @@ -124,5 +135,9 @@ func main() { if err != nil { 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") } diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go new file mode 100644 index 00000000000..5ad9fb7c14b --- /dev/null +++ b/pkg/cloudcfg/resource_printer.go @@ -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) + } +} diff --git a/pkg/registry/controller_registry.go b/pkg/registry/controller_registry.go index 126b006d1a9..38abbb51b7b 100644 --- a/pkg/registry/controller_registry.go +++ b/pkg/registry/controller_registry.go @@ -59,6 +59,7 @@ func (storage *ControllerRegistryStorage) Delete(id string) error { func (storage *ControllerRegistryStorage) Extract(body string) (interface{}, error) { result := ReplicationController{} err := json.Unmarshal([]byte(body), &result) + result.Kind = "cluster#replicationController" return result, err } diff --git a/pkg/registry/controller_registry_test.go b/pkg/registry/controller_registry_test.go index 0f99b1117b9..9df4bc524e3 100644 --- a/pkg/registry/controller_registry_test.go +++ b/pkg/registry/controller_registry_test.go @@ -124,9 +124,9 @@ func TestExtractControllerJson(t *testing.T) { expectNoError(t, err) controllerOut, err := storage.Extract(string(body)) expectNoError(t, err) - jsonOut, err := json.Marshal(controllerOut) - expectNoError(t, err) - if string(body) != string(jsonOut) { + // Extract adds a Kind + controller.Kind = "cluster#replicationController" + if !reflect.DeepEqual(controller, controllerOut) { t.Errorf("Expected %#v, found %#v", controller, controllerOut) } } diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index 3f2541e2de7..e25d7f74620 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -101,6 +101,7 @@ func (storage *PodRegistryStorage) Delete(id string) error { func (storage *PodRegistryStorage) Extract(body string) (interface{}, error) { pod := Pod{} err := json.Unmarshal([]byte(body), &pod) + pod.Kind = "cluster#pod" return pod, err } diff --git a/pkg/registry/pod_registry_test.go b/pkg/registry/pod_registry_test.go index 81b3468fd7d..c5bfc4d4ea6 100644 --- a/pkg/registry/pod_registry_test.go +++ b/pkg/registry/pod_registry_test.go @@ -18,6 +18,7 @@ package registry import ( "encoding/json" "fmt" + "reflect" "testing" . "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -127,9 +128,9 @@ func TestExtractJson(t *testing.T) { expectNoError(t, err) podOut, err := storage.Extract(string(body)) expectNoError(t, err) - jsonOut, err := json.Marshal(podOut) - expectNoError(t, err) - if string(body) != string(jsonOut) { + // Extract adds in a kind + pod.Kind = "cluster#pod" + if !reflect.DeepEqual(pod, podOut) { t.Errorf("Expected %#v, found %#v", pod, podOut) } } diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index 542219f001f..39c3c61be89 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -78,6 +78,7 @@ func (sr *ServiceRegistryStorage) Delete(id string) error { func (sr *ServiceRegistryStorage) Extract(body string) (interface{}, error) { var svc Service err := json.Unmarshal([]byte(body), &svc) + svc.Kind = "cluster#service" return svc, err }