1
16
17 package printers
18
19 import (
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
35 var _ ResourcePrinter = &HumanReadablePrinter{}
36
37 type printHandler struct {
38 columnDefinitions []metav1.TableColumnDefinition
39 printFunc reflect.Value
40 }
41
42 var (
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"}
66 )
67
68
69
70
71
72 type HumanReadablePrinter struct {
73 options PrintOptions
74 lastType interface{}
75 lastColumns []metav1.TableColumnDefinition
76 printedHeaders bool
77 }
78
79
80 func NewTablePrinter(options PrintOptions) ResourcePrinter {
81 printer := &HumanReadablePrinter{
82 options: options,
83 }
84 return printer
85 }
86
87 func 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
95 func (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
110
111 if table, ok := obj.(*metav1.Table); ok {
112
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
120
121 table.ColumnDefinitions = h.lastColumns
122 } else if !reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
123
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
147
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
170
171
172 func printTable(table *metav1.Table, output io.Writer, options PrintOptions) error {
173 if !options.NoHeaders {
174
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
198
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
216
217 breakchar := strings.IndexAny(print, "\f\n\r")
218 if breakchar >= 0 {
219 truncated = true
220 print = print[:breakchar]
221 }
222 WriteEscaped(output, print)
223 if truncated {
224 fmt.Fprint(output, "...")
225 }
226 default:
227 WriteEscaped(output, fmt.Sprint(val))
228 }
229 }
230 }
231 fmt.Fprintln(output)
232 }
233 return nil
234 }
235
236 type cellValueFunc func(metav1.TableRow) (interface{}, error)
237
238 type columnAddPosition int
239
240 const (
241 beginning columnAddPosition = 1
242 end columnAddPosition = 2
243 )
244
245 func addColumns(pos columnAddPosition, table *metav1.Table, columns []metav1.TableColumnDefinition, valueFuncs []cellValueFunc) error {
246 if len(columns) != len(valueFuncs) {
247 return fmt.Errorf("cannot prepend columns, unmatched value functions")
248 }
249 if len(columns) == 0 {
250 return nil
251 }
252
253
254 newRows := make([][]interface{}, len(table.Rows))
255 for i := range table.Rows {
256 newCells := make([]interface{}, 0, len(columns)+len(table.Rows[i].Cells))
257
258 if pos == end {
259
260
261 newCells = append(newCells, table.Rows[i].Cells...)
262 for len(newCells) < len(table.ColumnDefinitions) {
263 newCells = append(newCells, nil)
264 }
265 }
266
267
268 for _, f := range valueFuncs {
269 newCell, err := f(table.Rows[i])
270 if err != nil {
271 return err
272 }
273 newCells = append(newCells, newCell)
274 }
275
276 if pos == beginning {
277
278 newCells = append(newCells, table.Rows[i].Cells...)
279 }
280
281
282 newRows[i] = newCells
283 }
284
285
286 newColumns := make([]metav1.TableColumnDefinition, 0, len(columns)+len(table.ColumnDefinitions))
287 switch pos {
288 case beginning:
289 newColumns = append(newColumns, columns...)
290 newColumns = append(newColumns, table.ColumnDefinitions...)
291 case end:
292 newColumns = append(newColumns, table.ColumnDefinitions...)
293 newColumns = append(newColumns, columns...)
294 default:
295 return fmt.Errorf("invalid column add position: %v", pos)
296 }
297 table.ColumnDefinitions = newColumns
298 for i := range table.Rows {
299 table.Rows[i].Cells = newRows[i]
300 }
301
302 return nil
303 }
304
305
306
307
308
309 func decorateTable(table *metav1.Table, options PrintOptions) error {
310 width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
311 if options.WithNamespace {
312 width++
313 }
314 if options.ShowLabels {
315 width++
316 }
317
318 columns := table.ColumnDefinitions
319
320 nameColumn := -1
321 if options.WithKind && !options.Kind.Empty() {
322 for i := range columns {
323 if columns[i].Format == "name" && columns[i].Type == "string" {
324 nameColumn = i
325 break
326 }
327 }
328 }
329
330 if width != len(table.ColumnDefinitions) {
331 columns = make([]metav1.TableColumnDefinition, 0, width)
332 if options.WithNamespace {
333 columns = append(columns, metav1.TableColumnDefinition{
334 Name: "Namespace",
335 Type: "string",
336 })
337 }
338 columns = append(columns, table.ColumnDefinitions...)
339 for _, label := range formatLabelHeaders(options.ColumnLabels) {
340 columns = append(columns, metav1.TableColumnDefinition{
341 Name: label,
342 Type: "string",
343 })
344 }
345 if options.ShowLabels {
346 columns = append(columns, metav1.TableColumnDefinition{
347 Name: "Labels",
348 Type: "string",
349 })
350 }
351 }
352
353 rows := table.Rows
354
355 includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
356 if includeLabels || options.WithNamespace || nameColumn != -1 {
357 for i := range rows {
358 row := rows[i]
359
360 if nameColumn != -1 {
361 row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
362 }
363
364 var m metav1.Object
365 if obj := row.Object.Object; obj != nil {
366 if acc, err := meta.Accessor(obj); err == nil {
367 m = acc
368 }
369 }
370
371 if m == nil {
372 if options.WithNamespace {
373 r := make([]interface{}, 1, width)
374 row.Cells = append(r, row.Cells...)
375 }
376 for j := 0; j < width-len(row.Cells); j++ {
377 row.Cells = append(row.Cells, nil)
378 }
379 rows[i] = row
380 continue
381 }
382
383 if options.WithNamespace {
384 r := make([]interface{}, 1, width)
385 r[0] = m.GetNamespace()
386 row.Cells = append(r, row.Cells...)
387 }
388 if includeLabels {
389 row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
390 }
391 rows[i] = row
392 }
393 }
394
395 table.ColumnDefinitions = columns
396 table.Rows = rows
397 return nil
398 }
399
400
401
402
403 func printRowsForHandlerEntry(output io.Writer, handler *printHandler, eventType string, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
404 var results []reflect.Value
405
406 args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
407 results = handler.printFunc.Call(args)
408 if !results[1].IsNil() {
409 return results[1].Interface().(error)
410 }
411
412 if includeHeaders {
413 var headers []string
414 for _, column := range handler.columnDefinitions {
415 if column.Priority != 0 && !options.Wide {
416 continue
417 }
418 headers = append(headers, strings.ToUpper(column.Name))
419 }
420 headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
421
422 headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
423
424 if options.WithNamespace {
425 headers = append(withNamespacePrefixColumns, headers...)
426 }
427
428 if len(eventType) > 0 {
429 headers = append(withEventTypePrefixColumns, headers...)
430 }
431 printHeader(headers, output)
432 }
433
434 if results[1].IsNil() {
435 rows := results[0].Interface().([]metav1.TableRow)
436 printRows(output, eventType, rows, options)
437 return nil
438 }
439 return results[1].Interface().(error)
440 }
441
442 var formattedEventType = map[string]string{
443 string(watch.Added): "ADDED ",
444 string(watch.Modified): "MODIFIED",
445 string(watch.Deleted): "DELETED ",
446 string(watch.Error): "ERROR ",
447 }
448
449 func formatEventType(eventType string) string {
450 if formatted, ok := formattedEventType[eventType]; ok {
451 return formatted
452 }
453 return eventType
454 }
455
456
457 func printRows(output io.Writer, eventType string, rows []metav1.TableRow, options PrintOptions) {
458 for _, row := range rows {
459 if len(eventType) > 0 {
460 fmt.Fprint(output, formatEventType(eventType))
461 fmt.Fprint(output, "\t")
462 }
463 if options.WithNamespace {
464 if obj := row.Object.Object; obj != nil {
465 if m, err := meta.Accessor(obj); err == nil {
466 fmt.Fprint(output, m.GetNamespace())
467 }
468 }
469 fmt.Fprint(output, "\t")
470 }
471
472 for i, cell := range row.Cells {
473 if i != 0 {
474 fmt.Fprint(output, "\t")
475 } else {
476
477 if options.WithKind && !options.Kind.Empty() {
478 fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
479 continue
480 }
481 }
482 fmt.Fprint(output, cell)
483 }
484
485 hasLabels := len(options.ColumnLabels) > 0
486 if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
487 if m, err := meta.Accessor(obj); err == nil {
488 for _, value := range labelValues(m.GetLabels(), options) {
489 output.Write([]byte("\t"))
490 output.Write([]byte(value))
491 }
492 }
493 }
494
495 output.Write([]byte("\n"))
496 }
497 }
498
499 func formatLabelHeaders(columnLabels []string) []string {
500 formHead := make([]string, len(columnLabels))
501 for i, l := range columnLabels {
502 p := strings.Split(l, "/")
503 formHead[i] = strings.ToUpper(p[len(p)-1])
504 }
505 return formHead
506 }
507
508
509 func formatShowLabelsHeader(showLabels bool) []string {
510 if showLabels {
511 return []string{"LABELS"}
512 }
513 return nil
514 }
515
516
517 func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
518 var values []string
519 for _, key := range opts.ColumnLabels {
520 values = append(values, itemLabels[key])
521 }
522 if opts.ShowLabels {
523 values = append(values, labels.FormatLabels(itemLabels))
524 }
525 return values
526 }
527
528
529
530 func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
531 for _, key := range opts.ColumnLabels {
532 values = append(values, itemLabels[key])
533 }
534 if opts.ShowLabels {
535 values = append(values, labels.FormatLabels(itemLabels))
536 }
537 return values
538 }
539
540 func printStatus(obj runtime.Object, options PrintOptions) ([]metav1.TableRow, error) {
541 status, ok := obj.(*metav1.Status)
542 if !ok {
543 return nil, fmt.Errorf("expected *v1.Status, got %T", obj)
544 }
545 return []metav1.TableRow{{
546 Object: runtime.RawExtension{Object: obj},
547 Cells: []interface{}{status.Status, status.Reason, status.Message},
548 }}, nil
549 }
550
551 func printObjectMeta(obj runtime.Object, options PrintOptions) ([]metav1.TableRow, error) {
552 if meta.IsListType(obj) {
553 rows := make([]metav1.TableRow, 0, 16)
554 err := meta.EachListItem(obj, func(obj runtime.Object) error {
555 nestedRows, err := printObjectMeta(obj, options)
556 if err != nil {
557 return err
558 }
559 rows = append(rows, nestedRows...)
560 return nil
561 })
562 if err != nil {
563 return nil, err
564 }
565 return rows, nil
566 }
567
568 rows := make([]metav1.TableRow, 0, 1)
569 m, err := meta.Accessor(obj)
570 if err != nil {
571 return nil, err
572 }
573 row := metav1.TableRow{
574 Object: runtime.RawExtension{Object: obj},
575 }
576 row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
577 rows = append(rows, row)
578 return rows, nil
579 }
580
581
582
583 func translateTimestampSince(timestamp metav1.Time) string {
584 if timestamp.IsZero() {
585 return "<unknown>"
586 }
587
588 return duration.HumanDuration(time.Since(timestamp.Time))
589 }
590
View as plain text