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