From 6bd0c38908daed2d4e59a3b2540d3f0564f87068 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 25 Jun 2017 14:43:05 -0500 Subject: [PATCH] Add a new default printer handler for HumanReadable Refactors and removes the need for the more complex old code, temporarily limits what output is shown for truly unknown objects (a follow up change will allow server side handling and generic fallback), and removes all of the generic printers in favor of a single code path. --- pkg/printers/BUILD | 10 - pkg/printers/humanreadable.go | 272 ++++++++---------- pkg/printers/humanreadable_test.go | 90 ------ pkg/printers/internalversion/BUILD | 1 - pkg/printers/internalversion/printers.go | 154 +++------- pkg/printers/internalversion/printers_test.go | 107 +++++++ 6 files changed, 275 insertions(+), 359 deletions(-) delete mode 100644 pkg/printers/humanreadable_test.go diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index e305750db9c..cc22aa2c112 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -25,8 +25,6 @@ go_library( ], tags = ["automanaged"], deps = [ - "//pkg/util/slice:go_default_library", - "//vendor/github.com/fatih/camelcase:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -69,11 +67,3 @@ filegroup( ], tags = ["automanaged"], ) - -go_test( - name = "go_default_test", - srcs = ["humanreadable_test.go"], - library = ":go_default_library", - tags = ["automanaged"], - deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"], -) diff --git a/pkg/printers/humanreadable.go b/pkg/printers/humanreadable.go index 59bcf7e76ea..a4b1945746c 100644 --- a/pkg/printers/humanreadable.go +++ b/pkg/printers/humanreadable.go @@ -21,12 +21,9 @@ import ( "fmt" "io" "reflect" - "sort" "strings" "text/tabwriter" - "github.com/fatih/camelcase" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1" @@ -34,7 +31,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/util/slice" ) type TablePrinter interface { @@ -44,6 +40,7 @@ type TablePrinter interface { type PrintHandler interface { Handler(columns, columnsWithWide []string, printFunc interface{}) error TableHandler(columns []metav1alpha1.TableColumnDefinition, printFunc interface{}) error + DefaultTableHandler(columns []metav1alpha1.TableColumnDefinition, printFunc interface{}) error } var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. @@ -60,12 +57,13 @@ type handlerEntry struct { // will only be printed if the object type changes. This makes it useful for printing items // received from watches. type HumanReadablePrinter struct { - handlerMap map[reflect.Type]*handlerEntry - options PrintOptions - lastType reflect.Type - skipTabWriter bool - encoder runtime.Encoder - decoder runtime.Decoder + handlerMap map[reflect.Type]*handlerEntry + defaultHandler *handlerEntry + options PrintOptions + lastType interface{} + skipTabWriter bool + encoder runtime.Encoder + decoder runtime.Decoder } var _ PrintHandler = &HumanReadablePrinter{} @@ -188,6 +186,25 @@ func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1alpha1.Tab return nil } +// DefaultTableHandler registers a set of columns and a print func that is given a chance to process +// any object without an explicit handler. Only the most recently set print handler is used. +// See ValidateRowPrintHandlerFunc for required method signature. +func (h *HumanReadablePrinter) DefaultTableHandler(columnDefinitions []metav1alpha1.TableColumnDefinition, printFunc interface{}) error { + printFuncValue := reflect.ValueOf(printFunc) + if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err)) + return err + } + entry := &handlerEntry{ + columnDefinitions: columnDefinitions, + printRows: true, + printFunc: printFuncValue, + } + + h.defaultHandler = entry + return nil +} + // ValidateRowPrintHandlerFunc validates print handler signature. // printFunc is the function that will be called to print an object. // It must be of the following type: @@ -266,7 +283,7 @@ func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { return err } -func (h *HumanReadablePrinter) printHeader(columnNames []string, w io.Writer) error { +func printHeader(columnNames []string, w io.Writer) error { if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil { return err } @@ -299,141 +316,24 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er obj, _ = decodeUnknownObject(obj, h.encoder, h.decoder) } + // print with a registered handler t := reflect.TypeOf(obj) if handler := h.handlerMap[t]; handler != nil { - if !h.options.NoHeaders && t != h.lastType { - var headers []string - for _, column := range handler.columnDefinitions { - if column.Priority != 0 && !h.options.Wide { - continue - } - headers = append(headers, strings.ToUpper(column.Name)) - } - headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...) - // LABELS is always the last column. - headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...) - if h.options.WithNamespace { - headers = append(withNamespacePrefixColumns, headers...) - } - h.printHeader(headers, output) - h.lastType = t - } - - if handler.printRows { - args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(h.options)} - results := handler.printFunc.Call(args) - if results[1].IsNil() { - rows := results[0].Interface().([]metav1alpha1.TableRow) - for _, row := range rows { - - if h.options.WithNamespace { - if obj := row.Object.Object; obj != nil { - if m, err := meta.Accessor(obj); err == nil { - fmt.Fprint(output, m.GetNamespace()) - } - } - fmt.Fprint(output, "\t") - } - - for i, cell := range row.Cells { - if i != 0 { - fmt.Fprint(output, "\t") - } else { - // TODO: remove this once we drop the legacy printers - if h.options.WithKind && len(h.options.Kind) > 0 { - fmt.Fprintf(output, "%s/%s", h.options.Kind, cell) - continue - } - } - fmt.Fprint(output, cell) - } - - hasLabels := len(h.options.ColumnLabels) > 0 - if obj := row.Object.Object; obj != nil && (hasLabels || h.options.ShowLabels) { - if m, err := meta.Accessor(obj); err == nil { - for _, value := range labelValues(m.GetLabels(), h.options) { - output.Write([]byte("\t")) - output.Write([]byte(value)) - } - } - } - - output.Write([]byte("\n")) - } - return nil - } - return results[1].Interface().(error) - } - - // TODO: this code path is deprecated and will be removed when all handlers are row printers - args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(output), reflect.ValueOf(h.options)} - resultValue := handler.printFunc.Call(args)[0] - if resultValue.IsNil() { - return nil - } - return resultValue.Interface().(error) - } - - if _, err := meta.Accessor(obj); err == nil { - // we don't recognize this type, but we can still attempt to print some reasonable information about. - unstructured, ok := obj.(runtime.Unstructured) - if !ok { - return fmt.Errorf("error: unknown type %T, expected unstructured in %#v", obj, h.handlerMap) - } - - content := unstructured.UnstructuredContent() - - // we'll elect a few more fields to print depending on how much columns are already taken - maxDiscoveredFieldsToPrint := 3 - maxDiscoveredFieldsToPrint = maxDiscoveredFieldsToPrint - len(h.options.ColumnLabels) - if h.options.WithNamespace { // where's my ternary - maxDiscoveredFieldsToPrint-- - } - if h.options.ShowLabels { - maxDiscoveredFieldsToPrint-- - } - if maxDiscoveredFieldsToPrint < 0 { - maxDiscoveredFieldsToPrint = 0 - } - - var discoveredFieldNames []string // we want it predictable so this will be used to sort - ignoreIfDiscovered := []string{"kind", "apiVersion"} // these are already covered - for field, value := range content { - if slice.ContainsString(ignoreIfDiscovered, field, nil) { - continue - } - switch value.(type) { - case map[string]interface{}: - // just simpler types - continue - } - discoveredFieldNames = append(discoveredFieldNames, field) - } - sort.Strings(discoveredFieldNames) - if len(discoveredFieldNames) > maxDiscoveredFieldsToPrint { - discoveredFieldNames = discoveredFieldNames[:maxDiscoveredFieldsToPrint] - } - - if !h.options.NoHeaders && t != h.lastType { - headers := []string{"NAME", "KIND"} - for _, discoveredField := range discoveredFieldNames { - fieldAsHeader := strings.ToUpper(strings.Join(camelcase.Split(discoveredField), " ")) - headers = append(headers, fieldAsHeader) - } - headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...) - // LABELS is always the last column. - headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...) - if h.options.WithNamespace { - headers = append(withNamespacePrefixColumns, headers...) - } - h.printHeader(headers, output) - h.lastType = t - } - - // if the error isn't nil, report the "I don't recognize this" error - if err := printUnstructured(unstructured, output, discoveredFieldNames, h.options); err != nil { + includeHeaders := h.lastType != t && !h.options.NoHeaders + if err := printRowsForHandlerEntry(output, handler, obj, h.options, includeHeaders); err != nil { return err } + h.lastType = t + return nil + } + + // print with the default handler if set, and use the columns from the last time + if h.defaultHandler != nil { + includeHeaders := h.lastType != h.defaultHandler && !h.options.NoHeaders + if err := printRowsForHandlerEntry(output, h.defaultHandler, obj, h.options, includeHeaders); err != nil { + return err + } + h.lastType = h.defaultHandler return nil } @@ -631,6 +531,87 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio return table, nil } +// printRowsForHandlerEntry prints the incremental table output (headers if the current type is +// different from lastType) including all the rows in the object. It returns the current type +// or an error, if any. +func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runtime.Object, options PrintOptions, includeHeaders bool) error { + if includeHeaders { + var headers []string + for _, column := range handler.columnDefinitions { + if column.Priority != 0 && !options.Wide { + continue + } + headers = append(headers, strings.ToUpper(column.Name)) + } + headers = append(headers, formatLabelHeaders(options.ColumnLabels)...) + // LABELS is always the last column. + headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...) + if options.WithNamespace { + headers = append(withNamespacePrefixColumns, headers...) + } + printHeader(headers, output) + } + + if !handler.printRows { + // TODO: this code path is deprecated and will be removed when all handlers are row printers + args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(output), reflect.ValueOf(options)} + resultValue := handler.printFunc.Call(args)[0] + if resultValue.IsNil() { + return nil + } + return resultValue.Interface().(error) + } + + args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)} + results := handler.printFunc.Call(args) + if results[1].IsNil() { + rows := results[0].Interface().([]metav1alpha1.TableRow) + printRows(output, rows, options) + return nil + } + return results[1].Interface().(error) + +} + +// printRows writes the provided rows to output. +func printRows(output io.Writer, rows []metav1alpha1.TableRow, options PrintOptions) { + for _, row := range rows { + if options.WithNamespace { + if obj := row.Object.Object; obj != nil { + if m, err := meta.Accessor(obj); err == nil { + fmt.Fprint(output, m.GetNamespace()) + } + } + fmt.Fprint(output, "\t") + } + + for i, cell := range row.Cells { + if i != 0 { + fmt.Fprint(output, "\t") + } else { + // TODO: remove this once we drop the legacy printers + if options.WithKind && len(options.Kind) > 0 { + fmt.Fprintf(output, "%s/%s", options.Kind, cell) + continue + } + } + fmt.Fprint(output, cell) + } + + hasLabels := len(options.ColumnLabels) > 0 + if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) { + if m, err := meta.Accessor(obj); err == nil { + for _, value := range labelValues(m.GetLabels(), options) { + output.Write([]byte("\t")) + output.Write([]byte(value)) + } + } + } + + output.Write([]byte("\n")) + } +} + // legacyPrinterToTable uses the old printFunc with tabbed writer to generate a table. // TODO: remove when all legacy printers are removed. func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler *handlerEntry) (*metav1alpha1.Table, error) { @@ -754,12 +735,9 @@ func formatLabelHeaders(columnLabels []string) []string { } // headers for --show-labels=true -func formatShowLabelsHeader(showLabels bool, t reflect.Type) []string { +func formatShowLabelsHeader(showLabels bool) []string { if showLabels { - // TODO: this is all sorts of hack, fix - if t.String() != "*api.ThirdPartyResource" && t.String() != "*api.ThirdPartyResourceList" { - return []string{"LABELS"} - } + return []string{"LABELS"} } return nil } diff --git a/pkg/printers/humanreadable_test.go b/pkg/printers/humanreadable_test.go deleted file mode 100644 index 2649ba34e23..00000000000 --- a/pkg/printers/humanreadable_test.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package printers - -import ( - "bytes" - "regexp" - "testing" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -func TestPrintUnstructuredObject(t *testing.T) { - tests := []struct { - expected string - options PrintOptions - }{ - { - expected: "NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\\s+ITEMS\nMyName\\s+Test\\.v1\\.\\s+present\\s+present\\s+1 item\\(s\\)", - }, - { - options: PrintOptions{ - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+present", - }, - { - options: PrintOptions{ - ShowLabels: true, - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+LABELS\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+", - }, - } - out := bytes.NewBuffer([]byte{}) - - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Test", - "dummy1": "present", - "dummy2": "present", - "metadata": map[string]interface{}{ - "name": "MyName", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - }, - "items": []interface{}{ - map[string]interface{}{ - "itemBool": true, - "itemInt": 42, - }, - }, - "url": "http://localhost", - "status": "ok", - }, - } - - for _, test := range tests { - printer := &HumanReadablePrinter{ - options: test.options, - } - printer.PrintObj(obj, out) - - matches, err := regexp.MatchString(test.expected, out.String()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !matches { - t.Errorf("wanted %s, got %s", test.expected, out) - } - } -} diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index 39332c8dfa8..80dedce03b6 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -76,7 +76,6 @@ go_library( "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/apis/rbac:go_default_library", - "//pkg/apis/settings:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/apis/storage/util:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library", diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 9fc37422a21..030b1c8014f 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -30,6 +30,7 @@ import ( batchv2alpha1 "k8s.io/api/batch/v2alpha1" apiv1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1" "k8s.io/apimachinery/pkg/labels" @@ -48,7 +49,6 @@ import ( "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/rbac" - "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/apis/storage" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/controller" @@ -69,8 +69,6 @@ var ( nodeColumns = []string{"NAME", "STATUS", "AGE", "VERSION"} nodeWideColumns = []string{"EXTERNAL-IP", "OS-IMAGE", "KERNEL-VERSION", "CONTAINER-RUNTIME"} eventColumns = []string{"LASTSEEN", "FIRSTSEEN", "COUNT", "NAME", "KIND", "SUBOBJECT", "TYPE", "REASON", "SOURCE", "MESSAGE"} - limitRangeColumns = []string{"NAME", "AGE"} - resourceQuotaColumns = []string{"NAME", "AGE"} namespaceColumns = []string{"NAME", "STATUS", "AGE"} secretColumns = []string{"NAME", "TYPE", "DATA", "AGE"} serviceAccountColumns = []string{"NAME", "SECRETS", "AGE"} @@ -78,10 +76,8 @@ var ( persistentVolumeClaimColumns = []string{"NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESSMODES", "STORAGECLASS", "AGE"} componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"} thirdPartyResourceColumns = []string{"NAME", "DESCRIPTION", "VERSION(S)"} - roleColumns = []string{"NAME", "AGE"} roleBindingColumns = []string{"NAME", "AGE"} roleBindingWideColumns = []string{"ROLE", "USERS", "GROUPS", "SERVICEACCOUNTS"} - clusterRoleColumns = []string{"NAME", "AGE"} clusterRoleBindingColumns = []string{"NAME", "AGE"} clusterRoleBindingWideColumns = []string{"ROLE", "USERS", "GROUPS", "SERVICEACCOUNTS"} storageClassColumns = []string{"NAME", "PROVISIONER"} @@ -215,10 +211,6 @@ func AddHandlers(h printers.PrintHandler) { h.Handler(nodeColumns, nodeWideColumns, printNodeList) h.Handler(eventColumns, nil, printEvent) h.Handler(eventColumns, nil, printEventList) - h.Handler(limitRangeColumns, nil, printLimitRange) - h.Handler(limitRangeColumns, nil, printLimitRangeList) - h.Handler(resourceQuotaColumns, nil, printResourceQuota) - h.Handler(resourceQuotaColumns, nil, printResourceQuotaList) h.Handler(namespaceColumns, nil, printNamespace) h.Handler(namespaceColumns, nil, printNamespaceList) h.Handler(secretColumns, nil, printSecret) @@ -249,23 +241,59 @@ func AddHandlers(h printers.PrintHandler) { h.Handler(networkPolicyColumns, nil, printExtensionsNetworkPolicyList) h.Handler(networkPolicyColumns, nil, printNetworkPolicy) h.Handler(networkPolicyColumns, nil, printNetworkPolicyList) - h.Handler(roleColumns, nil, printRole) - h.Handler(roleColumns, nil, printRoleList) h.Handler(roleBindingColumns, roleBindingWideColumns, printRoleBinding) h.Handler(roleBindingColumns, roleBindingWideColumns, printRoleBindingList) - h.Handler(clusterRoleColumns, nil, printClusterRole) - h.Handler(clusterRoleColumns, nil, printClusterRoleList) h.Handler(clusterRoleBindingColumns, clusterRoleBindingWideColumns, printClusterRoleBinding) h.Handler(clusterRoleBindingColumns, clusterRoleBindingWideColumns, printClusterRoleBindingList) h.Handler(certificateSigningRequestColumns, nil, printCertificateSigningRequest) h.Handler(certificateSigningRequestColumns, nil, printCertificateSigningRequestList) h.Handler(storageClassColumns, nil, printStorageClass) h.Handler(storageClassColumns, nil, printStorageClassList) - h.Handler(podPresetColumns, nil, printPodPreset) - h.Handler(podPresetColumns, nil, printPodPresetList) h.Handler(statusColumns, nil, printStatus) h.Handler(controllerRevisionColumns, nil, printControllerRevision) h.Handler(controllerRevisionColumns, nil, printControllerRevisionList) + + AddDefaultHandlers(h) +} + +// AddDefaultHandlers adds handlers that can work with most Kubernetes objects. +func AddDefaultHandlers(h printers.PrintHandler) { + // types without defined columns + objectMetaColumnDefinitions := []metav1alpha1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + } + h.DefaultTableHandler(objectMetaColumnDefinitions, printObjectMeta) +} + +func printObjectMeta(obj runtime.Object, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { + if meta.IsListType(obj) { + rows := make([]metav1alpha1.TableRow, 0, 16) + err := meta.EachListItem(obj, func(obj runtime.Object) error { + nestedRows, err := printObjectMeta(obj, options) + if err != nil { + return err + } + rows = append(rows, nestedRows...) + return nil + }) + if err != nil { + return nil, err + } + return rows, nil + } + + rows := make([]metav1alpha1.TableRow, 0, 1) + m, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + row := metav1alpha1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + row.Cells = append(row.Cells, m.GetName(), translateTimestamp(m.GetCreationTimestamp())) + rows = append(rows, row) + return rows, nil } // Pass ports=nil for all ports. @@ -1238,72 +1266,6 @@ func printEventList(list *api.EventList, w io.Writer, options printers.PrintOpti return nil } -func printLimitRange(limitRange *api.LimitRange, w io.Writer, options printers.PrintOptions) error { - return printObjectMeta(limitRange.ObjectMeta, w, options, true) -} - -// Prints the LimitRangeList in a human-friendly format. -func printLimitRangeList(list *api.LimitRangeList, w io.Writer, options printers.PrintOptions) error { - for i := range list.Items { - if err := printLimitRange(&list.Items[i], w, options); err != nil { - return err - } - } - return nil -} - -// printObjectMeta prints the object metadata of a given resource. -func printObjectMeta(meta metav1.ObjectMeta, w io.Writer, options printers.PrintOptions, namespaced bool) error { - name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind) - - if namespaced && options.WithNamespace { - if _, err := fmt.Fprintf(w, "%s\t", meta.Namespace); err != nil { - return err - } - } - - if _, err := fmt.Fprintf( - w, "%s\t%s", - name, - translateTimestamp(meta.CreationTimestamp), - ); err != nil { - return err - } - if _, err := fmt.Fprint(w, printers.AppendLabels(meta.Labels, options.ColumnLabels)); err != nil { - return err - } - _, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, meta.Labels)) - return err -} - -func printResourceQuota(resourceQuota *api.ResourceQuota, w io.Writer, options printers.PrintOptions) error { - return printObjectMeta(resourceQuota.ObjectMeta, w, options, true) -} - -// Prints the ResourceQuotaList in a human-friendly format. -func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer, options printers.PrintOptions) error { - for i := range list.Items { - if err := printResourceQuota(&list.Items[i], w, options); err != nil { - return err - } - } - return nil -} - -func printRole(role *rbac.Role, w io.Writer, options printers.PrintOptions) error { - return printObjectMeta(role.ObjectMeta, w, options, true) -} - -// Prints the Role in a human-friendly format. -func printRoleList(list *rbac.RoleList, w io.Writer, options printers.PrintOptions) error { - for i := range list.Items { - if err := printRole(&list.Items[i], w, options); err != nil { - return err - } - } - return nil -} - func printRoleBinding(roleBinding *rbac.RoleBinding, w io.Writer, options printers.PrintOptions) error { meta := roleBinding.ObjectMeta name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind) @@ -1352,23 +1314,6 @@ func printRoleBindingList(list *rbac.RoleBindingList, w io.Writer, options print return nil } -func printClusterRole(clusterRole *rbac.ClusterRole, w io.Writer, options printers.PrintOptions) error { - if options.WithNamespace { - return fmt.Errorf("clusterRole is not namespaced") - } - return printObjectMeta(clusterRole.ObjectMeta, w, options, false) -} - -// Prints the ClusterRole in a human-friendly format. -func printClusterRoleList(list *rbac.ClusterRoleList, w io.Writer, options printers.PrintOptions) error { - for i := range list.Items { - if err := printClusterRole(&list.Items[i], w, options); err != nil { - return err - } - } - return nil -} - func printClusterRoleBinding(clusterRoleBinding *rbac.ClusterRoleBinding, w io.Writer, options printers.PrintOptions) error { meta := clusterRoleBinding.ObjectMeta name := printers.FormatResourceName(options.Kind, meta.Name, options.WithKind) @@ -1871,19 +1816,6 @@ func printStorageClassList(scList *storage.StorageClassList, w io.Writer, option return nil } -func printPodPreset(podPreset *settings.PodPreset, w io.Writer, options printers.PrintOptions) error { - return printObjectMeta(podPreset.ObjectMeta, w, options, false) -} - -func printPodPresetList(list *settings.PodPresetList, w io.Writer, options printers.PrintOptions) error { - for i := range list.Items { - if err := printPodPreset(&list.Items[i], w, options); err != nil { - return err - } - } - return nil -} - func printStatus(status *metav1.Status, w io.Writer, options printers.PrintOptions) error { if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", status.Status, status.Reason, status.Message); err != nil { return err diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index ffc0649fa1e..f55f1f24877 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "reflect" + "regexp" "strconv" "strings" "testing" @@ -103,6 +104,112 @@ func TestPrintDefault(t *testing.T) { } } +func TestPrintUnstructuredObject(t *testing.T) { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + /*"items": []interface{}{ + map[string]interface{}{ + "itemBool": true, + "itemInt": 42, + }, + },*/ + "url": "http://localhost", + "status": "ok", + }, + } + + tests := []struct { + expected string + options printers.PrintOptions + object runtime.Object + }{ + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + ShowLabels: true, + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other", + object: obj, + }, + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "items": []interface{}{ + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + }, + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName2", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": "badlabel", + }, + }, + }, + "url": "http://localhost", + "status": "ok", + }, + }, + }, + } + out := bytes.NewBuffer([]byte{}) + + for _, test := range tests { + out.Reset() + printer := printers.NewHumanReadablePrinter(nil, nil, test.options).With(AddDefaultHandlers) + printer.PrintObj(test.object, out) + + matches, err := regexp.MatchString(test.expected, out.String()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !matches { + t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) + } + } +} + type TestPrintType struct { Data string }