mirror of https://github.com/k3s-io/k3s
Merge pull request #48033 from smarterclayton/generic_printer
Automatic merge from submit-queue (batch tested with PRs 45467, 48091, 48033, 48498) Refactor and simplify generic printer for unknown objects The first two commits are part of other PRs @kubernetes/sig-cli-pr-reviews part of the general refactoring for server side printpull/6/head
commit
1108738200
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -75,7 +75,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"
|
||||
|
@ -47,7 +48,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"
|
||||
|
@ -68,8 +68,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"}
|
||||
|
@ -77,10 +75,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"}
|
||||
|
@ -214,10 +210,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)
|
||||
|
@ -248,23 +240,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.
|
||||
|
@ -1231,72 +1259,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)
|
||||
|
@ -1345,23 +1307,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)
|
||||
|
@ -1864,19 +1809,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
|
||||
}
|
||||
|
|
|
@ -67,6 +67,9 @@ func GetItemsPtr(list runtime.Object) (interface{}, error) {
|
|||
// EachListItem invokes fn on each runtime.Object in the list. Any error immediately terminates
|
||||
// the loop.
|
||||
func EachListItem(obj runtime.Object, fn func(runtime.Object) error) error {
|
||||
if unstructured, ok := obj.(runtime.Unstructured); ok {
|
||||
return unstructured.EachListItem(fn)
|
||||
}
|
||||
// TODO: Change to an interface call?
|
||||
itemsPtr, err := GetItemsPtr(obj)
|
||||
if err != nil {
|
||||
|
|
|
@ -69,6 +69,39 @@ func (obj *Unstructured) IsList() bool {
|
|||
}
|
||||
func (obj *UnstructuredList) IsList() bool { return true }
|
||||
|
||||
func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error {
|
||||
if obj.Object == nil {
|
||||
return fmt.Errorf("content is not a list")
|
||||
}
|
||||
field, ok := obj.Object["items"]
|
||||
if !ok {
|
||||
return fmt.Errorf("content is not a list")
|
||||
}
|
||||
items, ok := field.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for _, item := range items {
|
||||
child, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("items member is not an object")
|
||||
}
|
||||
if err := fn(&Unstructured{Object: child}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *UnstructuredList) EachListItem(fn func(runtime.Object) error) error {
|
||||
for i := range obj.Items {
|
||||
if err := fn(&obj.Items[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
|
||||
if obj.Object == nil {
|
||||
obj.Object = make(map[string]interface{})
|
||||
|
|
|
@ -242,10 +242,14 @@ type Unstructured interface {
|
|||
// IsUnstructuredObject is a marker interface to allow objects that can be serialized but not introspected
|
||||
// to bypass conversion.
|
||||
IsUnstructuredObject()
|
||||
// IsList returns true if this type is a list or matches the list convention - has an array called "items".
|
||||
IsList() bool
|
||||
// UnstructuredContent returns a non-nil, mutable map of the contents of this object. Values may be
|
||||
// []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to
|
||||
// and from JSON.
|
||||
UnstructuredContent() map[string]interface{}
|
||||
// IsList returns true if this type is a list or matches the list convention - has an array called "items".
|
||||
IsList() bool
|
||||
// EachListItem should pass a single item out of the list as an Object to the provided function. Any
|
||||
// error should terminate the iteration. If IsList() returns false, this method should return an error
|
||||
// instead of calling the provided function.
|
||||
EachListItem(func(Object) error) error
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue