|
| 1 | +/* |
| 2 | +Copyright 2019 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package printers |
| 18 | + |
| 19 | +import ( |
| 20 | + "fmt" |
| 21 | + "reflect" |
| 22 | + |
| 23 | + "k8s.io/apimachinery/pkg/api/meta" |
| 24 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 25 | + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" |
| 26 | + "k8s.io/apimachinery/pkg/runtime" |
| 27 | + utilruntime "k8s.io/apimachinery/pkg/util/runtime" |
| 28 | +) |
| 29 | + |
| 30 | +type TableGenerator interface { |
| 31 | + GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) |
| 32 | +} |
| 33 | + |
| 34 | +type PrintHandler interface { |
| 35 | + TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error |
| 36 | + DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error |
| 37 | +} |
| 38 | + |
| 39 | +type handlerEntry struct { |
| 40 | + columnDefinitions []metav1beta1.TableColumnDefinition |
| 41 | + printFunc reflect.Value |
| 42 | + args []reflect.Value |
| 43 | +} |
| 44 | + |
| 45 | +// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide |
| 46 | +// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers |
| 47 | +// will only be printed if the object type changes. This makes it useful for printing items |
| 48 | +// received from watches. |
| 49 | +type HumanReadablePrinter struct { |
| 50 | + handlerMap map[reflect.Type]*handlerEntry |
| 51 | + defaultHandler *handlerEntry |
| 52 | + options PrintOptions |
| 53 | + lastType interface{} |
| 54 | + lastColumns []metav1beta1.TableColumnDefinition |
| 55 | +} |
| 56 | + |
| 57 | +var _ TableGenerator = &HumanReadablePrinter{} |
| 58 | +var _ PrintHandler = &HumanReadablePrinter{} |
| 59 | + |
| 60 | +// NewTableGenerator creates a HumanReadablePrinter suitable for calling GenerateTable(). |
| 61 | +func NewTableGenerator() *HumanReadablePrinter { |
| 62 | + return &HumanReadablePrinter{ |
| 63 | + handlerMap: make(map[reflect.Type]*handlerEntry), |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +func (a *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter { |
| 68 | + for _, fn := range fns { |
| 69 | + fn(a) |
| 70 | + } |
| 71 | + return a |
| 72 | +} |
| 73 | + |
| 74 | +// GenerateTable returns a table for the provided object, using the printer registered for that type. It returns |
| 75 | +// a table that includes all of the information requested by options, but will not remove rows or columns. The |
| 76 | +// caller is responsible for applying rules related to filtering rows or columns. |
| 77 | +func (h *HumanReadablePrinter) GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) { |
| 78 | + t := reflect.TypeOf(obj) |
| 79 | + handler, ok := h.handlerMap[t] |
| 80 | + if !ok { |
| 81 | + return nil, fmt.Errorf("no table handler registered for this type %v", t) |
| 82 | + } |
| 83 | + |
| 84 | + args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)} |
| 85 | + results := handler.printFunc.Call(args) |
| 86 | + if !results[1].IsNil() { |
| 87 | + return nil, results[1].Interface().(error) |
| 88 | + } |
| 89 | + |
| 90 | + var columns []metav1beta1.TableColumnDefinition |
| 91 | + if !options.NoHeaders { |
| 92 | + columns = handler.columnDefinitions |
| 93 | + if !options.Wide { |
| 94 | + columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions)) |
| 95 | + for i := range handler.columnDefinitions { |
| 96 | + if handler.columnDefinitions[i].Priority != 0 { |
| 97 | + continue |
| 98 | + } |
| 99 | + columns = append(columns, handler.columnDefinitions[i]) |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + table := &metav1beta1.Table{ |
| 104 | + ListMeta: metav1.ListMeta{ |
| 105 | + ResourceVersion: "", |
| 106 | + }, |
| 107 | + ColumnDefinitions: columns, |
| 108 | + Rows: results[0].Interface().([]metav1beta1.TableRow), |
| 109 | + } |
| 110 | + if m, err := meta.ListAccessor(obj); err == nil { |
| 111 | + table.ResourceVersion = m.GetResourceVersion() |
| 112 | + table.SelfLink = m.GetSelfLink() |
| 113 | + table.Continue = m.GetContinue() |
| 114 | + } else { |
| 115 | + if m, err := meta.CommonAccessor(obj); err == nil { |
| 116 | + table.ResourceVersion = m.GetResourceVersion() |
| 117 | + table.SelfLink = m.GetSelfLink() |
| 118 | + } |
| 119 | + } |
| 120 | + if err := decorateTable(table, options); err != nil { |
| 121 | + return nil, err |
| 122 | + } |
| 123 | + return table, nil |
| 124 | +} |
| 125 | + |
| 126 | +// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance. |
| 127 | +// See ValidateRowPrintHandlerFunc for required method signature. |
| 128 | +func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error { |
| 129 | + printFuncValue := reflect.ValueOf(printFunc) |
| 130 | + if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil { |
| 131 | + utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err)) |
| 132 | + return err |
| 133 | + } |
| 134 | + entry := &handlerEntry{ |
| 135 | + columnDefinitions: columnDefinitions, |
| 136 | + printFunc: printFuncValue, |
| 137 | + } |
| 138 | + |
| 139 | + objType := printFuncValue.Type().In(0) |
| 140 | + if _, ok := h.handlerMap[objType]; ok { |
| 141 | + err := fmt.Errorf("registered duplicate printer for %v", objType) |
| 142 | + utilruntime.HandleError(err) |
| 143 | + return err |
| 144 | + } |
| 145 | + h.handlerMap[objType] = entry |
| 146 | + return nil |
| 147 | +} |
| 148 | + |
| 149 | +// DefaultTableHandler registers a set of columns and a print func that is given a chance to process |
| 150 | +// any object without an explicit handler. Only the most recently set print handler is used. |
| 151 | +// See ValidateRowPrintHandlerFunc for required method signature. |
| 152 | +func (h *HumanReadablePrinter) DefaultTableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error { |
| 153 | + printFuncValue := reflect.ValueOf(printFunc) |
| 154 | + if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil { |
| 155 | + utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err)) |
| 156 | + return err |
| 157 | + } |
| 158 | + entry := &handlerEntry{ |
| 159 | + columnDefinitions: columnDefinitions, |
| 160 | + printFunc: printFuncValue, |
| 161 | + } |
| 162 | + |
| 163 | + h.defaultHandler = entry |
| 164 | + return nil |
| 165 | +} |
| 166 | + |
| 167 | +// ValidateRowPrintHandlerFunc validates print handler signature. |
| 168 | +// printFunc is the function that will be called to print an object. |
| 169 | +// It must be of the following type: |
| 170 | +// func printFunc(object ObjectType, options PrintOptions) ([]metav1beta1.TableRow, error) |
| 171 | +// where ObjectType is the type of the object that will be printed, and the first |
| 172 | +// return value is an array of rows, with each row containing a number of cells that |
| 173 | +// match the number of columns defined for that printer function. |
| 174 | +func ValidateRowPrintHandlerFunc(printFunc reflect.Value) error { |
| 175 | + if printFunc.Kind() != reflect.Func { |
| 176 | + return fmt.Errorf("invalid print handler. %#v is not a function", printFunc) |
| 177 | + } |
| 178 | + funcType := printFunc.Type() |
| 179 | + if funcType.NumIn() != 2 || funcType.NumOut() != 2 { |
| 180 | + return fmt.Errorf("invalid print handler." + |
| 181 | + "Must accept 2 parameters and return 2 value.") |
| 182 | + } |
| 183 | + if funcType.In(1) != reflect.TypeOf((*PrintOptions)(nil)).Elem() || |
| 184 | + funcType.Out(0) != reflect.TypeOf((*[]metav1beta1.TableRow)(nil)).Elem() || |
| 185 | + funcType.Out(1) != reflect.TypeOf((*error)(nil)).Elem() { |
| 186 | + return fmt.Errorf("invalid print handler. The expected signature is: "+ |
| 187 | + "func handler(obj %v, options PrintOptions) ([]metav1beta1.TableRow, error)", funcType.In(0)) |
| 188 | + } |
| 189 | + return nil |
| 190 | +} |
0 commit comments