tableprinter.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package printers
  14. import (
  15. "fmt"
  16. "io"
  17. "reflect"
  18. "strings"
  19. "time"
  20. "github.com/liggitt/tabwriter"
  21. "k8s.io/apimachinery/pkg/api/meta"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
  24. "k8s.io/apimachinery/pkg/labels"
  25. "k8s.io/apimachinery/pkg/runtime"
  26. "k8s.io/apimachinery/pkg/util/duration"
  27. )
  28. var _ ResourcePrinter = &HumanReadablePrinter{}
  29. var (
  30. defaultHandlerEntry = &handlerEntry{
  31. columnDefinitions: objectMetaColumnDefinitions,
  32. printFunc: reflect.ValueOf(printObjectMeta),
  33. }
  34. objectMetaColumnDefinitions = []metav1beta1.TableColumnDefinition{
  35. {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
  36. {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
  37. }
  38. withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
  39. )
  40. // NewTablePrinter creates a printer suitable for calling PrintObj().
  41. // TODO(seans3): Change return type to ResourcePrinter interface once we no longer need
  42. // to constuct the "handlerMap".
  43. func NewTablePrinter(options PrintOptions) *HumanReadablePrinter {
  44. printer := &HumanReadablePrinter{
  45. handlerMap: make(map[reflect.Type]*handlerEntry),
  46. options: options,
  47. }
  48. return printer
  49. }
  50. func printHeader(columnNames []string, w io.Writer) error {
  51. if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
  52. return err
  53. }
  54. return nil
  55. }
  56. // PrintObj prints the obj in a human-friendly format according to the type of the obj.
  57. func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
  58. w, found := output.(*tabwriter.Writer)
  59. if !found {
  60. w = GetNewTabWriter(output)
  61. output = w
  62. defer w.Flush()
  63. }
  64. // Case 1: Parameter "obj" is a table from server; print it.
  65. // display tables following the rules of options
  66. if table, ok := obj.(*metav1beta1.Table); ok {
  67. // Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
  68. localOptions := h.options
  69. if len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
  70. localOptions.NoHeaders = true
  71. }
  72. if len(table.ColumnDefinitions) == 0 {
  73. // If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
  74. // This is done when receiving tables in watch events to save bandwidth.
  75. localOptions.NoHeaders = true
  76. table.ColumnDefinitions = h.lastColumns
  77. } else {
  78. // If this table has column definitions, remember them for future use.
  79. h.lastColumns = table.ColumnDefinitions
  80. }
  81. if err := decorateTable(table, localOptions); err != nil {
  82. return err
  83. }
  84. return printTable(table, output, localOptions)
  85. }
  86. // Case 2: Parameter "obj" is not a table; search for a handler to print it.
  87. // TODO(seans3): Remove this case in 1.16, since table should be returned from server-side printing.
  88. // print with a registered handler
  89. t := reflect.TypeOf(obj)
  90. if handler := h.handlerMap[t]; handler != nil {
  91. includeHeaders := h.lastType != t && !h.options.NoHeaders
  92. if h.lastType != nil && h.lastType != t && !h.options.NoHeaders {
  93. fmt.Fprintln(output)
  94. }
  95. if err := printRowsForHandlerEntry(output, handler, obj, h.options, includeHeaders); err != nil {
  96. return err
  97. }
  98. h.lastType = t
  99. return nil
  100. }
  101. // Case 3: Could not find print handler for "obj"; use the default print handler.
  102. // Print with the default handler, and use the columns from the last time
  103. includeHeaders := h.lastType != defaultHandlerEntry && !h.options.NoHeaders
  104. if h.lastType != nil && h.lastType != defaultHandlerEntry && !h.options.NoHeaders {
  105. fmt.Fprintln(output)
  106. }
  107. if err := printRowsForHandlerEntry(output, defaultHandlerEntry, obj, h.options, includeHeaders); err != nil {
  108. return err
  109. }
  110. h.lastType = defaultHandlerEntry
  111. return nil
  112. }
  113. // printTable prints a table to the provided output respecting the filtering rules for options
  114. // for wide columns and filtered rows. It filters out rows that are Completed. You should call
  115. // decorateTable if you receive a table from a remote server before calling printTable.
  116. func printTable(table *metav1beta1.Table, output io.Writer, options PrintOptions) error {
  117. if !options.NoHeaders {
  118. // avoid printing headers if we have no rows to display
  119. if len(table.Rows) == 0 {
  120. return nil
  121. }
  122. first := true
  123. for _, column := range table.ColumnDefinitions {
  124. if !options.Wide && column.Priority != 0 {
  125. continue
  126. }
  127. if first {
  128. first = false
  129. } else {
  130. fmt.Fprint(output, "\t")
  131. }
  132. fmt.Fprint(output, strings.ToUpper(column.Name))
  133. }
  134. fmt.Fprintln(output)
  135. }
  136. for _, row := range table.Rows {
  137. first := true
  138. for i, cell := range row.Cells {
  139. if i >= len(table.ColumnDefinitions) {
  140. // https://issue.k8s.io/66379
  141. // don't panic in case of bad output from the server, with more cells than column definitions
  142. break
  143. }
  144. column := table.ColumnDefinitions[i]
  145. if !options.Wide && column.Priority != 0 {
  146. continue
  147. }
  148. if first {
  149. first = false
  150. } else {
  151. fmt.Fprint(output, "\t")
  152. }
  153. if cell != nil {
  154. fmt.Fprint(output, cell)
  155. }
  156. }
  157. fmt.Fprintln(output)
  158. }
  159. return nil
  160. }
  161. // decorateTable takes a table and attempts to add label columns and the
  162. // namespace column. It will fill empty columns with nil (if the object
  163. // does not expose metadata). It returns an error if the table cannot
  164. // be decorated.
  165. func decorateTable(table *metav1beta1.Table, options PrintOptions) error {
  166. width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
  167. if options.WithNamespace {
  168. width++
  169. }
  170. if options.ShowLabels {
  171. width++
  172. }
  173. columns := table.ColumnDefinitions
  174. nameColumn := -1
  175. if options.WithKind && !options.Kind.Empty() {
  176. for i := range columns {
  177. if columns[i].Format == "name" && columns[i].Type == "string" {
  178. nameColumn = i
  179. break
  180. }
  181. }
  182. }
  183. if width != len(table.ColumnDefinitions) {
  184. columns = make([]metav1beta1.TableColumnDefinition, 0, width)
  185. if options.WithNamespace {
  186. columns = append(columns, metav1beta1.TableColumnDefinition{
  187. Name: "Namespace",
  188. Type: "string",
  189. })
  190. }
  191. columns = append(columns, table.ColumnDefinitions...)
  192. for _, label := range formatLabelHeaders(options.ColumnLabels) {
  193. columns = append(columns, metav1beta1.TableColumnDefinition{
  194. Name: label,
  195. Type: "string",
  196. })
  197. }
  198. if options.ShowLabels {
  199. columns = append(columns, metav1beta1.TableColumnDefinition{
  200. Name: "Labels",
  201. Type: "string",
  202. })
  203. }
  204. }
  205. rows := table.Rows
  206. includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
  207. if includeLabels || options.WithNamespace || nameColumn != -1 {
  208. for i := range rows {
  209. row := rows[i]
  210. if nameColumn != -1 {
  211. row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
  212. }
  213. var m metav1.Object
  214. if obj := row.Object.Object; obj != nil {
  215. if acc, err := meta.Accessor(obj); err == nil {
  216. m = acc
  217. }
  218. }
  219. // if we can't get an accessor, fill out the appropriate columns with empty spaces
  220. if m == nil {
  221. if options.WithNamespace {
  222. r := make([]interface{}, 1, width)
  223. row.Cells = append(r, row.Cells...)
  224. }
  225. for j := 0; j < width-len(row.Cells); j++ {
  226. row.Cells = append(row.Cells, nil)
  227. }
  228. rows[i] = row
  229. continue
  230. }
  231. if options.WithNamespace {
  232. r := make([]interface{}, 1, width)
  233. r[0] = m.GetNamespace()
  234. row.Cells = append(r, row.Cells...)
  235. }
  236. if includeLabels {
  237. row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
  238. }
  239. rows[i] = row
  240. }
  241. }
  242. table.ColumnDefinitions = columns
  243. table.Rows = rows
  244. return nil
  245. }
  246. // printRowsForHandlerEntry prints the incremental table output (headers if the current type is
  247. // different from lastType) including all the rows in the object. It returns the current type
  248. // or an error, if any.
  249. func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
  250. var results []reflect.Value
  251. args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
  252. results = handler.printFunc.Call(args)
  253. if !results[1].IsNil() {
  254. return results[1].Interface().(error)
  255. }
  256. if includeHeaders {
  257. var headers []string
  258. for _, column := range handler.columnDefinitions {
  259. if column.Priority != 0 && !options.Wide {
  260. continue
  261. }
  262. headers = append(headers, strings.ToUpper(column.Name))
  263. }
  264. headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
  265. // LABELS is always the last column.
  266. headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
  267. if options.WithNamespace {
  268. headers = append(withNamespacePrefixColumns, headers...)
  269. }
  270. printHeader(headers, output)
  271. }
  272. if results[1].IsNil() {
  273. rows := results[0].Interface().([]metav1beta1.TableRow)
  274. printRows(output, rows, options)
  275. return nil
  276. }
  277. return results[1].Interface().(error)
  278. }
  279. // printRows writes the provided rows to output.
  280. func printRows(output io.Writer, rows []metav1beta1.TableRow, options PrintOptions) {
  281. for _, row := range rows {
  282. if options.WithNamespace {
  283. if obj := row.Object.Object; obj != nil {
  284. if m, err := meta.Accessor(obj); err == nil {
  285. fmt.Fprint(output, m.GetNamespace())
  286. }
  287. }
  288. fmt.Fprint(output, "\t")
  289. }
  290. for i, cell := range row.Cells {
  291. if i != 0 {
  292. fmt.Fprint(output, "\t")
  293. } else {
  294. // TODO: remove this once we drop the legacy printers
  295. if options.WithKind && !options.Kind.Empty() {
  296. fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
  297. continue
  298. }
  299. }
  300. fmt.Fprint(output, cell)
  301. }
  302. hasLabels := len(options.ColumnLabels) > 0
  303. if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
  304. if m, err := meta.Accessor(obj); err == nil {
  305. for _, value := range labelValues(m.GetLabels(), options) {
  306. output.Write([]byte("\t"))
  307. output.Write([]byte(value))
  308. }
  309. }
  310. }
  311. output.Write([]byte("\n"))
  312. }
  313. }
  314. func formatLabelHeaders(columnLabels []string) []string {
  315. formHead := make([]string, len(columnLabels))
  316. for i, l := range columnLabels {
  317. p := strings.Split(l, "/")
  318. formHead[i] = strings.ToUpper((p[len(p)-1]))
  319. }
  320. return formHead
  321. }
  322. // headers for --show-labels=true
  323. func formatShowLabelsHeader(showLabels bool) []string {
  324. if showLabels {
  325. return []string{"LABELS"}
  326. }
  327. return nil
  328. }
  329. // labelValues returns a slice of value columns matching the requested print options.
  330. func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
  331. var values []string
  332. for _, key := range opts.ColumnLabels {
  333. values = append(values, itemLabels[key])
  334. }
  335. if opts.ShowLabels {
  336. values = append(values, labels.FormatLabels(itemLabels))
  337. }
  338. return values
  339. }
  340. // appendLabelCells returns a slice of value columns matching the requested print options.
  341. // Intended for use with tables.
  342. func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
  343. for _, key := range opts.ColumnLabels {
  344. values = append(values, itemLabels[key])
  345. }
  346. if opts.ShowLabels {
  347. values = append(values, labels.FormatLabels(itemLabels))
  348. }
  349. return values
  350. }
  351. func printObjectMeta(obj runtime.Object, options PrintOptions) ([]metav1beta1.TableRow, error) {
  352. if meta.IsListType(obj) {
  353. rows := make([]metav1beta1.TableRow, 0, 16)
  354. err := meta.EachListItem(obj, func(obj runtime.Object) error {
  355. nestedRows, err := printObjectMeta(obj, options)
  356. if err != nil {
  357. return err
  358. }
  359. rows = append(rows, nestedRows...)
  360. return nil
  361. })
  362. if err != nil {
  363. return nil, err
  364. }
  365. return rows, nil
  366. }
  367. rows := make([]metav1beta1.TableRow, 0, 1)
  368. m, err := meta.Accessor(obj)
  369. if err != nil {
  370. return nil, err
  371. }
  372. row := metav1beta1.TableRow{
  373. Object: runtime.RawExtension{Object: obj},
  374. }
  375. row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
  376. rows = append(rows, row)
  377. return rows, nil
  378. }
  379. // translateTimestampSince returns the elapsed time since timestamp in
  380. // human-readable approximation.
  381. func translateTimestampSince(timestamp metav1.Time) string {
  382. if timestamp.IsZero() {
  383. return "<unknown>"
  384. }
  385. return duration.HumanDuration(time.Since(timestamp.Time))
  386. }