k8s.io/kubernetes@v1.29.3/pkg/printers/tablegenerator.go (about) 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 "k8s.io/apimachinery/pkg/runtime" 26 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 ) 28 29 // GenerateOptions encapsulates attributes for table generation. 30 type GenerateOptions struct { 31 NoHeaders bool 32 Wide bool 33 } 34 35 // TableGenerator - an interface for generating metav1.Table provided a runtime.Object 36 type 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 41 type PrintHandler interface { 42 TableHandler(columns []metav1.TableColumnDefinition, printFunc interface{}) error 43 } 44 45 type 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(). 53 type HumanReadableGenerator struct { 54 handlerMap map[reflect.Type]*handlerEntry 55 } 56 57 var _ TableGenerator = &HumanReadableGenerator{} 58 var _ PrintHandler = &HumanReadableGenerator{} 59 60 // NewTableGenerator creates a HumanReadableGenerator suitable for calling GenerateTable(). 61 func 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 68 func (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. 78 func (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.Continue = m.GetContinue() 114 table.RemainingItemCount = m.GetRemainingItemCount() 115 } else { 116 if m, err := meta.CommonAccessor(obj); err == nil { 117 table.ResourceVersion = m.GetResourceVersion() 118 } 119 } 120 return table, nil 121 } 122 123 // TableHandler adds a print handler with a given set of columns to HumanReadableGenerator instance. 124 // See ValidateRowPrintHandlerFunc for required method signature. 125 func (h *HumanReadableGenerator) TableHandler(columnDefinitions []metav1.TableColumnDefinition, printFunc interface{}) error { 126 printFuncValue := reflect.ValueOf(printFunc) 127 if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil { 128 utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err)) 129 return err 130 } 131 entry := &handlerEntry{ 132 columnDefinitions: columnDefinitions, 133 printFunc: printFuncValue, 134 } 135 136 objType := printFuncValue.Type().In(0) 137 if _, ok := h.handlerMap[objType]; ok { 138 err := fmt.Errorf("registered duplicate printer for %v", objType) 139 utilruntime.HandleError(err) 140 return err 141 } 142 h.handlerMap[objType] = entry 143 return nil 144 } 145 146 // ValidateRowPrintHandlerFunc validates print handler signature. 147 // printFunc is the function that will be called to print an object. 148 // It must be of the following type: 149 // 150 // func printFunc(object ObjectType, options GenerateOptions) ([]metav1.TableRow, error) 151 // 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. 155 func 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 }