mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
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.
This commit is contained in:
parent
3662184786
commit
6bd0c38908
@ -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"],
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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+<none>",
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user