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	"io"
22	"reflect"
23	"strings"
24	"time"
25
26	"github.com/liggitt/tabwriter"
27	"k8s.io/apimachinery/pkg/api/meta"
28	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29	"k8s.io/apimachinery/pkg/labels"
30	"k8s.io/apimachinery/pkg/runtime"
31	"k8s.io/apimachinery/pkg/util/duration"
32	"k8s.io/apimachinery/pkg/watch"
33)
34
35var _ ResourcePrinter = &HumanReadablePrinter{}
36
37type printHandler struct {
38	columnDefinitions []metav1.TableColumnDefinition
39	printFunc         reflect.Value
40}
41
42var (
43	statusHandlerEntry = &printHandler{
44		columnDefinitions: statusColumnDefinitions,
45		printFunc:         reflect.ValueOf(printStatus),
46	}
47
48	statusColumnDefinitions = []metav1.TableColumnDefinition{
49		{Name: "Status", Type: "string"},
50		{Name: "Reason", Type: "string"},
51		{Name: "Message", Type: "string"},
52	}
53
54	defaultHandlerEntry = &printHandler{
55		columnDefinitions: objectMetaColumnDefinitions,
56		printFunc:         reflect.ValueOf(printObjectMeta),
57	}
58
59	objectMetaColumnDefinitions = []metav1.TableColumnDefinition{
60		{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
61		{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
62	}
63
64	withEventTypePrefixColumns = []string{"EVENT"}
65	withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
66)
67
68// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
69// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers
70// will only be printed if the object type changes. This makes it useful for printing items
71// received from watches.
72type HumanReadablePrinter struct {
73	options        PrintOptions
74	lastType       interface{}
75	lastColumns    []metav1.TableColumnDefinition
76	printedHeaders bool
77}
78
79// NewTablePrinter creates a printer suitable for calling PrintObj().
80func NewTablePrinter(options PrintOptions) ResourcePrinter {
81	printer := &HumanReadablePrinter{
82		options: options,
83	}
84	return printer
85}
86
87func printHeader(columnNames []string, w io.Writer) error {
88	if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
89		return err
90	}
91	return nil
92}
93
94// PrintObj prints the obj in a human-friendly format according to the type of the obj.
95func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
96
97	if _, found := output.(*tabwriter.Writer); !found {
98		w := GetNewTabWriter(output)
99		output = w
100		defer w.Flush()
101	}
102
103	var eventType string
104	if event, isEvent := obj.(*metav1.WatchEvent); isEvent {
105		eventType = event.Type
106		obj = event.Object.Object
107	}
108
109	// Parameter "obj" is a table from server; print it.
110	// display tables following the rules of options
111	if table, ok := obj.(*metav1.Table); ok {
112		// Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
113		localOptions := h.options
114		if h.printedHeaders && (len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns)) {
115			localOptions.NoHeaders = true
116		}
117
118		if len(table.ColumnDefinitions) == 0 {
119			// If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
120			// This is done when receiving tables in watch events to save bandwidth.
121			table.ColumnDefinitions = h.lastColumns
122		} else if !reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
123			// If this table has column definitions, remember them for future use.
124			h.lastColumns = table.ColumnDefinitions
125			h.printedHeaders = false
126		}
127
128		if len(table.Rows) > 0 {
129			h.printedHeaders = true
130		}
131
132		if err := decorateTable(table, localOptions); err != nil {
133			return err
134		}
135		if len(eventType) > 0 {
136			if err := addColumns(beginning, table,
137				[]metav1.TableColumnDefinition{{Name: "Event", Type: "string"}},
138				[]cellValueFunc{func(metav1.TableRow) (interface{}, error) { return formatEventType(eventType), nil }},
139			); err != nil {
140				return err
141			}
142		}
143		return printTable(table, output, localOptions)
144	}
145
146	// Could not find print handler for "obj"; use the default or status print handler.
147	// Print with the default or status handler, and use the columns from the last time
148	var handler *printHandler
149	if _, isStatus := obj.(*metav1.Status); isStatus {
150		handler = statusHandlerEntry
151	} else {
152		handler = defaultHandlerEntry
153	}
154
155	includeHeaders := h.lastType != handler && !h.options.NoHeaders
156
157	if h.lastType != nil && h.lastType != handler && !h.options.NoHeaders {
158		fmt.Fprintln(output)
159	}
160
161	if err := printRowsForHandlerEntry(output, handler, eventType, obj, h.options, includeHeaders); err != nil {
162		return err
163	}
164	h.lastType = handler
165
166	return nil
167}
168
169// printTable prints a table to the provided output respecting the filtering rules for options
170// for wide columns and filtered rows. It filters out rows that are Completed. You should call
171// decorateTable if you receive a table from a remote server before calling printTable.
172func printTable(table *metav1.Table, output io.Writer, options PrintOptions) error {
173	if !options.NoHeaders {
174		// avoid printing headers if we have no rows to display
175		if len(table.Rows) == 0 {
176			return nil
177		}
178
179		first := true
180		for _, column := range table.ColumnDefinitions {
181			if !options.Wide && column.Priority != 0 {
182				continue
183			}
184			if first {
185				first = false
186			} else {
187				fmt.Fprint(output, "\t")
188			}
189			fmt.Fprint(output, strings.ToUpper(column.Name))
190		}
191		fmt.Fprintln(output)
192	}
193	for _, row := range table.Rows {
194		first := true
195		for i, cell := range row.Cells {
196			if i >= len(table.ColumnDefinitions) {
197				// https://issue.k8s.io/66379
198				// don't panic in case of bad output from the server, with more cells than column definitions
199				break
200			}
201			column := table.ColumnDefinitions[i]
202			if !options.Wide && column.Priority != 0 {
203				continue
204			}
205			if first {
206				first = false
207			} else {
208				fmt.Fprint(output, "\t")
209			}
210			if cell != nil {
211				switch val := cell.(type) {
212				case string:
213					print := val
214					truncated := false
215					// truncate at newlines
216					newline := strings.Index(print, "\n")
217					if newline >= 0 {
218						truncated = true
219						print = print[:newline]
220					}
221					fmt.Fprint(output, print)
222					if truncated {
223						fmt.Fprint(output, "...")
224					}
225				default:
226					fmt.Fprint(output, val)
227				}
228			}
229		}
230		fmt.Fprintln(output)
231	}
232	return nil
233}
234
235type cellValueFunc func(metav1.TableRow) (interface{}, error)
236
237type columnAddPosition int
238
239const (
240	beginning columnAddPosition = 1
241	end       columnAddPosition = 2
242)
243
244func addColumns(pos columnAddPosition, table *metav1.Table, columns []metav1.TableColumnDefinition, valueFuncs []cellValueFunc) error {
245	if len(columns) != len(valueFuncs) {
246		return fmt.Errorf("cannot prepend columns, unmatched value functions")
247	}
248	if len(columns) == 0 {
249		return nil
250	}
251
252	// Compute the new rows
253	newRows := make([][]interface{}, len(table.Rows))
254	for i := range table.Rows {
255		newCells := make([]interface{}, 0, len(columns)+len(table.Rows[i].Cells))
256
257		if pos == end {
258			// If we're appending, start with the existing cells,
259			// then add nil cells to match the number of columns
260			newCells = append(newCells, table.Rows[i].Cells...)
261			for len(newCells) < len(table.ColumnDefinitions) {
262				newCells = append(newCells, nil)
263			}
264		}
265
266		// Compute cells for new columns
267		for _, f := range valueFuncs {
268			newCell, err := f(table.Rows[i])
269			if err != nil {
270				return err
271			}
272			newCells = append(newCells, newCell)
273		}
274
275		if pos == beginning {
276			// If we're prepending, add existing cells
277			newCells = append(newCells, table.Rows[i].Cells...)
278		}
279
280		// Remember the new cells for this row
281		newRows[i] = newCells
282	}
283
284	// All cells successfully computed, now replace columns and rows
285	newColumns := make([]metav1.TableColumnDefinition, 0, len(columns)+len(table.ColumnDefinitions))
286	switch pos {
287	case beginning:
288		newColumns = append(newColumns, columns...)
289		newColumns = append(newColumns, table.ColumnDefinitions...)
290	case end:
291		newColumns = append(newColumns, table.ColumnDefinitions...)
292		newColumns = append(newColumns, columns...)
293	default:
294		return fmt.Errorf("invalid column add position: %v", pos)
295	}
296	table.ColumnDefinitions = newColumns
297	for i := range table.Rows {
298		table.Rows[i].Cells = newRows[i]
299	}
300
301	return nil
302}
303
304// decorateTable takes a table and attempts to add label columns and the
305// namespace column. It will fill empty columns with nil (if the object
306// does not expose metadata). It returns an error if the table cannot
307// be decorated.
308func decorateTable(table *metav1.Table, options PrintOptions) error {
309	width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
310	if options.WithNamespace {
311		width++
312	}
313	if options.ShowLabels {
314		width++
315	}
316
317	columns := table.ColumnDefinitions
318
319	nameColumn := -1
320	if options.WithKind && !options.Kind.Empty() {
321		for i := range columns {
322			if columns[i].Format == "name" && columns[i].Type == "string" {
323				nameColumn = i
324				break
325			}
326		}
327	}
328
329	if width != len(table.ColumnDefinitions) {
330		columns = make([]metav1.TableColumnDefinition, 0, width)
331		if options.WithNamespace {
332			columns = append(columns, metav1.TableColumnDefinition{
333				Name: "Namespace",
334				Type: "string",
335			})
336		}
337		columns = append(columns, table.ColumnDefinitions...)
338		for _, label := range formatLabelHeaders(options.ColumnLabels) {
339			columns = append(columns, metav1.TableColumnDefinition{
340				Name: label,
341				Type: "string",
342			})
343		}
344		if options.ShowLabels {
345			columns = append(columns, metav1.TableColumnDefinition{
346				Name: "Labels",
347				Type: "string",
348			})
349		}
350	}
351
352	rows := table.Rows
353
354	includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
355	if includeLabels || options.WithNamespace || nameColumn != -1 {
356		for i := range rows {
357			row := rows[i]
358
359			if nameColumn != -1 {
360				row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
361			}
362
363			var m metav1.Object
364			if obj := row.Object.Object; obj != nil {
365				if acc, err := meta.Accessor(obj); err == nil {
366					m = acc
367				}
368			}
369			// if we can't get an accessor, fill out the appropriate columns with empty spaces
370			if m == nil {
371				if options.WithNamespace {
372					r := make([]interface{}, 1, width)
373					row.Cells = append(r, row.Cells...)
374				}
375				for j := 0; j < width-len(row.Cells); j++ {
376					row.Cells = append(row.Cells, nil)
377				}
378				rows[i] = row
379				continue
380			}
381
382			if options.WithNamespace {
383				r := make([]interface{}, 1, width)
384				r[0] = m.GetNamespace()
385				row.Cells = append(r, row.Cells...)
386			}
387			if includeLabels {
388				row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
389			}
390			rows[i] = row
391		}
392	}
393
394	table.ColumnDefinitions = columns
395	table.Rows = rows
396	return nil
397}
398
399// printRowsForHandlerEntry prints the incremental table output (headers if the current type is
400// different from lastType) including all the rows in the object. It returns the current type
401// or an error, if any.
402func printRowsForHandlerEntry(output io.Writer, handler *printHandler, eventType string, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
403	var results []reflect.Value
404
405	args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
406	results = handler.printFunc.Call(args)
407	if !results[1].IsNil() {
408		return results[1].Interface().(error)
409	}
410
411	if includeHeaders {
412		var headers []string
413		for _, column := range handler.columnDefinitions {
414			if column.Priority != 0 && !options.Wide {
415				continue
416			}
417			headers = append(headers, strings.ToUpper(column.Name))
418		}
419		headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
420		// LABELS is always the last column.
421		headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
422		// prepend namespace header
423		if options.WithNamespace {
424			headers = append(withNamespacePrefixColumns, headers...)
425		}
426		// prepend event type header
427		if len(eventType) > 0 {
428			headers = append(withEventTypePrefixColumns, headers...)
429		}
430		printHeader(headers, output)
431	}
432
433	if results[1].IsNil() {
434		rows := results[0].Interface().([]metav1.TableRow)
435		printRows(output, eventType, rows, options)
436		return nil
437	}
438	return results[1].Interface().(error)
439}
440
441var formattedEventType = map[string]string{
442	string(watch.Added):    "ADDED   ",
443	string(watch.Modified): "MODIFIED",
444	string(watch.Deleted):  "DELETED ",
445	string(watch.Error):    "ERROR   ",
446}
447
448func formatEventType(eventType string) string {
449	if formatted, ok := formattedEventType[eventType]; ok {
450		return formatted
451	}
452	return string(eventType)
453}
454
455// printRows writes the provided rows to output.
456func printRows(output io.Writer, eventType string, rows []metav1.TableRow, options PrintOptions) {
457	for _, row := range rows {
458		if len(eventType) > 0 {
459			fmt.Fprint(output, formatEventType(eventType))
460			fmt.Fprint(output, "\t")
461		}
462		if options.WithNamespace {
463			if obj := row.Object.Object; obj != nil {
464				if m, err := meta.Accessor(obj); err == nil {
465					fmt.Fprint(output, m.GetNamespace())
466				}
467			}
468			fmt.Fprint(output, "\t")
469		}
470
471		for i, cell := range row.Cells {
472			if i != 0 {
473				fmt.Fprint(output, "\t")
474			} else {
475				// TODO: remove this once we drop the legacy printers
476				if options.WithKind && !options.Kind.Empty() {
477					fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
478					continue
479				}
480			}
481			fmt.Fprint(output, cell)
482		}
483
484		hasLabels := len(options.ColumnLabels) > 0
485		if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
486			if m, err := meta.Accessor(obj); err == nil {
487				for _, value := range labelValues(m.GetLabels(), options) {
488					output.Write([]byte("\t"))
489					output.Write([]byte(value))
490				}
491			}
492		}
493
494		output.Write([]byte("\n"))
495	}
496}
497
498func formatLabelHeaders(columnLabels []string) []string {
499	formHead := make([]string, len(columnLabels))
500	for i, l := range columnLabels {
501		p := strings.Split(l, "/")
502		formHead[i] = strings.ToUpper((p[len(p)-1]))
503	}
504	return formHead
505}
506
507// headers for --show-labels=true
508func formatShowLabelsHeader(showLabels bool) []string {
509	if showLabels {
510		return []string{"LABELS"}
511	}
512	return nil
513}
514
515// labelValues returns a slice of value columns matching the requested print options.
516func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
517	var values []string
518	for _, key := range opts.ColumnLabels {
519		values = append(values, itemLabels[key])
520	}
521	if opts.ShowLabels {
522		values = append(values, labels.FormatLabels(itemLabels))
523	}
524	return values
525}
526
527// appendLabelCells returns a slice of value columns matching the requested print options.
528// Intended for use with tables.
529func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
530	for _, key := range opts.ColumnLabels {
531		values = append(values, itemLabels[key])
532	}
533	if opts.ShowLabels {
534		values = append(values, labels.FormatLabels(itemLabels))
535	}
536	return values
537}
538
539func printStatus(obj runtime.Object, options PrintOptions) ([]metav1.TableRow, error) {
540	status, ok := obj.(*metav1.Status)
541	if !ok {
542		return nil, fmt.Errorf("expected *v1.Status, got %T", obj)
543	}
544	return []metav1.TableRow{{
545		Object: runtime.RawExtension{Object: obj},
546		Cells:  []interface{}{status.Status, status.Reason, status.Message},
547	}}, nil
548}
549
550func printObjectMeta(obj runtime.Object, options PrintOptions) ([]metav1.TableRow, error) {
551	if meta.IsListType(obj) {
552		rows := make([]metav1.TableRow, 0, 16)
553		err := meta.EachListItem(obj, func(obj runtime.Object) error {
554			nestedRows, err := printObjectMeta(obj, options)
555			if err != nil {
556				return err
557			}
558			rows = append(rows, nestedRows...)
559			return nil
560		})
561		if err != nil {
562			return nil, err
563		}
564		return rows, nil
565	}
566
567	rows := make([]metav1.TableRow, 0, 1)
568	m, err := meta.Accessor(obj)
569	if err != nil {
570		return nil, err
571	}
572	row := metav1.TableRow{
573		Object: runtime.RawExtension{Object: obj},
574	}
575	row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
576	rows = append(rows, row)
577	return rows, nil
578}
579
580// translateTimestampSince returns the elapsed time since timestamp in
581// human-readable approximation.
582func translateTimestampSince(timestamp metav1.Time) string {
583	if timestamp.IsZero() {
584		return "<unknown>"
585	}
586
587	return duration.HumanDuration(time.Since(timestamp.Time))
588}
589