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