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 }