mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
873 lines
27 KiB
Go
873 lines
27 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package printers
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
)
|
|
|
|
type TablePrinter interface {
|
|
PrintTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error)
|
|
}
|
|
|
|
type PrintHandler interface {
|
|
Handler(columns, columnsWithWide []string, printFunc interface{}) error
|
|
TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
|
|
DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
|
|
}
|
|
|
|
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
|
|
|
|
type handlerEntry struct {
|
|
columnDefinitions []metav1beta1.TableColumnDefinition
|
|
printRows bool
|
|
printFunc reflect.Value
|
|
args []reflect.Value
|
|
}
|
|
|
|
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
|
|
// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers
|
|
// will only be printed if the object type changes. This makes it useful for printing items
|
|
// received from watches.
|
|
type HumanReadablePrinter struct {
|
|
handlerMap map[reflect.Type]*handlerEntry
|
|
defaultHandler *handlerEntry
|
|
options PrintOptions
|
|
lastType interface{}
|
|
skipTabWriter bool
|
|
encoder runtime.Encoder
|
|
decoder runtime.Decoder
|
|
}
|
|
|
|
var _ PrintHandler = &HumanReadablePrinter{}
|
|
|
|
// NewHumanReadablePrinter creates a HumanReadablePrinter.
|
|
// If encoder and decoder are provided, an attempt to convert unstructured types to internal types is made.
|
|
func NewHumanReadablePrinter(decoder runtime.Decoder, options PrintOptions) *HumanReadablePrinter {
|
|
printer := &HumanReadablePrinter{
|
|
handlerMap: make(map[reflect.Type]*handlerEntry),
|
|
options: options,
|
|
decoder: decoder,
|
|
}
|
|
return printer
|
|
}
|
|
|
|
// NewTablePrinter creates a HumanReadablePrinter suitable for calling PrintTable().
|
|
func NewTablePrinter() *HumanReadablePrinter {
|
|
return &HumanReadablePrinter{
|
|
handlerMap: make(map[reflect.Type]*handlerEntry),
|
|
}
|
|
}
|
|
|
|
// AddTabWriter sets whether the PrintObj function will format with tabwriter (true
|
|
// by default).
|
|
func (a *HumanReadablePrinter) AddTabWriter(t bool) *HumanReadablePrinter {
|
|
a.skipTabWriter = !t
|
|
return a
|
|
}
|
|
|
|
func (a *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter {
|
|
for _, fn := range fns {
|
|
fn(a)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// EnsurePrintHeaders sets the HumanReadablePrinter option "NoHeaders" to false
|
|
// and removes the .lastType that was printed, which forces headers to be
|
|
// printed in cases where multiple lists of the same resource are printed
|
|
// consecutively, but are separated by non-printer related information.
|
|
func (h *HumanReadablePrinter) EnsurePrintHeaders() {
|
|
h.options.NoHeaders = false
|
|
h.lastType = nil
|
|
}
|
|
|
|
// Handler adds a print handler with a given set of columns to HumanReadablePrinter instance.
|
|
// See ValidatePrintHandlerFunc for required method signature.
|
|
func (h *HumanReadablePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
|
|
var columnDefinitions []metav1beta1.TableColumnDefinition
|
|
for i, column := range columns {
|
|
format := ""
|
|
if i == 0 && strings.EqualFold(column, "name") {
|
|
format = "name"
|
|
}
|
|
|
|
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
|
|
Name: column,
|
|
Description: column,
|
|
Type: "string",
|
|
Format: format,
|
|
})
|
|
}
|
|
for _, column := range columnsWithWide {
|
|
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
|
|
Name: column,
|
|
Description: column,
|
|
Type: "string",
|
|
Priority: 1,
|
|
})
|
|
}
|
|
|
|
printFuncValue := reflect.ValueOf(printFunc)
|
|
if err := ValidatePrintHandlerFunc(printFuncValue); err != nil {
|
|
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
|
|
return err
|
|
}
|
|
|
|
entry := &handlerEntry{
|
|
columnDefinitions: columnDefinitions,
|
|
printFunc: printFuncValue,
|
|
}
|
|
|
|
objType := printFuncValue.Type().In(0)
|
|
if _, ok := h.handlerMap[objType]; ok {
|
|
err := fmt.Errorf("registered duplicate printer for %v", objType)
|
|
utilruntime.HandleError(err)
|
|
return err
|
|
}
|
|
h.handlerMap[objType] = entry
|
|
return nil
|
|
}
|
|
|
|
// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance.
|
|
// See ValidateRowPrintHandlerFunc for required method signature.
|
|
func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
|
|
printFuncValue := reflect.ValueOf(printFunc)
|
|
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
|
|
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
|
|
return err
|
|
}
|
|
entry := &handlerEntry{
|
|
columnDefinitions: columnDefinitions,
|
|
printRows: true,
|
|
printFunc: printFuncValue,
|
|
}
|
|
|
|
objType := printFuncValue.Type().In(0)
|
|
if _, ok := h.handlerMap[objType]; ok {
|
|
err := fmt.Errorf("registered duplicate printer for %v", objType)
|
|
utilruntime.HandleError(err)
|
|
return err
|
|
}
|
|
h.handlerMap[objType] = entry
|
|
return nil
|
|
}
|
|
|
|
// DefaultTableHandler registers a set of columns and a print func that is given a chance to process
|
|
// any object without an explicit handler. Only the most recently set print handler is used.
|
|
// See ValidateRowPrintHandlerFunc for required method signature.
|
|
func (h *HumanReadablePrinter) DefaultTableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
|
|
printFuncValue := reflect.ValueOf(printFunc)
|
|
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
|
|
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))
|
|
return err
|
|
}
|
|
entry := &handlerEntry{
|
|
columnDefinitions: columnDefinitions,
|
|
printRows: true,
|
|
printFunc: printFuncValue,
|
|
}
|
|
|
|
h.defaultHandler = entry
|
|
return nil
|
|
}
|
|
|
|
// ValidateRowPrintHandlerFunc validates print handler signature.
|
|
// printFunc is the function that will be called to print an object.
|
|
// It must be of the following type:
|
|
// func printFunc(object ObjectType, options PrintOptions) ([]metav1beta1.TableRow, error)
|
|
// where ObjectType is the type of the object that will be printed, and the first
|
|
// return value is an array of rows, with each row containing a number of cells that
|
|
// match the number of columns defined for that printer function.
|
|
func ValidateRowPrintHandlerFunc(printFunc reflect.Value) error {
|
|
if printFunc.Kind() != reflect.Func {
|
|
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
|
|
}
|
|
funcType := printFunc.Type()
|
|
if funcType.NumIn() != 2 || funcType.NumOut() != 2 {
|
|
return fmt.Errorf("invalid print handler." +
|
|
"Must accept 2 parameters and return 2 value.")
|
|
}
|
|
if funcType.In(1) != reflect.TypeOf((*PrintOptions)(nil)).Elem() ||
|
|
funcType.Out(0) != reflect.TypeOf((*[]metav1beta1.TableRow)(nil)).Elem() ||
|
|
funcType.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
|
return fmt.Errorf("invalid print handler. The expected signature is: "+
|
|
"func handler(obj %v, options PrintOptions) ([]metav1beta1.TableRow, error)", funcType.In(0))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidatePrintHandlerFunc validates print handler signature.
|
|
// printFunc is the function that will be called to print an object.
|
|
// It must be of the following type:
|
|
// func printFunc(object ObjectType, w io.Writer, options PrintOptions) error
|
|
// where ObjectType is the type of the object that will be printed.
|
|
// DEPRECATED: will be replaced with ValidateRowPrintHandlerFunc
|
|
func ValidatePrintHandlerFunc(printFunc reflect.Value) error {
|
|
if printFunc.Kind() != reflect.Func {
|
|
return fmt.Errorf("invalid print handler. %#v is not a function", printFunc)
|
|
}
|
|
funcType := printFunc.Type()
|
|
if funcType.NumIn() != 3 || funcType.NumOut() != 1 {
|
|
return fmt.Errorf("invalid print handler." +
|
|
"Must accept 3 parameters and return 1 value.")
|
|
}
|
|
if funcType.In(1) != reflect.TypeOf((*io.Writer)(nil)).Elem() ||
|
|
funcType.In(2) != reflect.TypeOf((*PrintOptions)(nil)).Elem() ||
|
|
funcType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
|
|
return fmt.Errorf("invalid print handler. The expected signature is: "+
|
|
"func handler(obj %v, w io.Writer, options PrintOptions) error", funcType.In(0))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *HumanReadablePrinter) HandledResources() []string {
|
|
keys := make([]string, 0)
|
|
|
|
for k := range h.handlerMap {
|
|
// k.String looks like "*api.PodList" and we want just "pod"
|
|
api := strings.Split(k.String(), ".")
|
|
resource := api[len(api)-1]
|
|
if strings.HasSuffix(resource, "List") {
|
|
continue
|
|
}
|
|
resource = strings.ToLower(resource)
|
|
keys = append(keys, resource)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
|
_, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
|
|
return err
|
|
}
|
|
|
|
func printHeader(columnNames []string, w io.Writer) error {
|
|
if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
|
|
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
|
|
w, found := output.(*tabwriter.Writer)
|
|
if !found && !h.skipTabWriter {
|
|
w = GetNewTabWriter(output)
|
|
output = w
|
|
defer w.Flush()
|
|
}
|
|
|
|
// display tables following the rules of options
|
|
if table, ok := obj.(*metav1beta1.Table); ok {
|
|
if err := DecorateTable(table, h.options); err != nil {
|
|
return err
|
|
}
|
|
return PrintTable(table, output, h.options)
|
|
}
|
|
|
|
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
|
|
// trying to print, since the printers are keyed by type. This is extremely expensive.
|
|
if h.decoder != nil {
|
|
obj, _ = decodeUnknownObject(obj, h.decoder)
|
|
}
|
|
|
|
// print with a registered handler
|
|
t := reflect.TypeOf(obj)
|
|
if handler := h.handlerMap[t]; handler != nil {
|
|
includeHeaders := h.lastType != t && !h.options.NoHeaders
|
|
|
|
if h.lastType != nil && h.lastType != t && !h.options.NoHeaders {
|
|
fmt.Fprintln(output)
|
|
}
|
|
|
|
if err := printRowsForHandlerEntry(output, handler, obj, h.options, includeHeaders); err != nil {
|
|
return err
|
|
}
|
|
h.lastType = t
|
|
return nil
|
|
}
|
|
|
|
// print with the default handler if set, and use the columns from the last time
|
|
if h.defaultHandler != nil {
|
|
includeHeaders := h.lastType != h.defaultHandler && !h.options.NoHeaders
|
|
|
|
if h.lastType != nil && h.lastType != h.defaultHandler && !h.options.NoHeaders {
|
|
fmt.Fprintln(output)
|
|
}
|
|
|
|
if err := printRowsForHandlerEntry(output, h.defaultHandler, obj, h.options, includeHeaders); err != nil {
|
|
return err
|
|
}
|
|
h.lastType = h.defaultHandler
|
|
return nil
|
|
}
|
|
|
|
// we failed all reasonable printing efforts, report failure
|
|
return fmt.Errorf("error: unknown type %#v", obj)
|
|
}
|
|
|
|
func hasCondition(conditions []metav1beta1.TableRowCondition, t metav1beta1.RowConditionType) bool {
|
|
for _, condition := range conditions {
|
|
if condition.Type == t {
|
|
return condition.Status == metav1beta1.ConditionTrue
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PrintTable prints a table to the provided output respecting the filtering rules for options
|
|
// for wide columns and filtered rows. It filters out rows that are Completed. You should call
|
|
// DecorateTable if you receive a table from a remote server before calling PrintTable.
|
|
func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions) error {
|
|
if !options.NoHeaders {
|
|
// avoid printing headers if we have no rows to display
|
|
if len(table.Rows) == 0 {
|
|
return nil
|
|
}
|
|
|
|
first := true
|
|
for _, column := range table.ColumnDefinitions {
|
|
if !options.Wide && column.Priority != 0 {
|
|
continue
|
|
}
|
|
if first {
|
|
first = false
|
|
} else {
|
|
fmt.Fprint(output, "\t")
|
|
}
|
|
fmt.Fprint(output, strings.ToUpper(column.Name))
|
|
}
|
|
fmt.Fprintln(output)
|
|
}
|
|
for _, row := range table.Rows {
|
|
first := true
|
|
for i, cell := range row.Cells {
|
|
if i >= len(table.ColumnDefinitions) {
|
|
// https://issue.k8s.io/66379
|
|
// don't panic in case of bad output from the server, with more cells than column definitions
|
|
break
|
|
}
|
|
column := table.ColumnDefinitions[i]
|
|
if !options.Wide && column.Priority != 0 {
|
|
continue
|
|
}
|
|
if first {
|
|
first = false
|
|
} else {
|
|
fmt.Fprint(output, "\t")
|
|
}
|
|
if cell != nil {
|
|
fmt.Fprint(output, cell)
|
|
}
|
|
}
|
|
fmt.Fprintln(output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DecorateTable takes a table and attempts to add label columns and the
|
|
// namespace column. It will fill empty columns with nil (if the object
|
|
// does not expose metadata). It returns an error if the table cannot
|
|
// be decorated.
|
|
func DecorateTable(table *metav1beta1.Table, options PrintOptions) error {
|
|
width := len(table.ColumnDefinitions) + len(options.ColumnLabels)
|
|
if options.WithNamespace {
|
|
width++
|
|
}
|
|
if options.ShowLabels {
|
|
width++
|
|
}
|
|
|
|
columns := table.ColumnDefinitions
|
|
|
|
nameColumn := -1
|
|
if options.WithKind && !options.Kind.Empty() {
|
|
for i := range columns {
|
|
if columns[i].Format == "name" && columns[i].Type == "string" {
|
|
nameColumn = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if width != len(table.ColumnDefinitions) {
|
|
columns = make([]metav1beta1.TableColumnDefinition, 0, width)
|
|
if options.WithNamespace {
|
|
columns = append(columns, metav1beta1.TableColumnDefinition{
|
|
Name: "Namespace",
|
|
Type: "string",
|
|
})
|
|
}
|
|
columns = append(columns, table.ColumnDefinitions...)
|
|
for _, label := range formatLabelHeaders(options.ColumnLabels) {
|
|
columns = append(columns, metav1beta1.TableColumnDefinition{
|
|
Name: label,
|
|
Type: "string",
|
|
})
|
|
}
|
|
if options.ShowLabels {
|
|
columns = append(columns, metav1beta1.TableColumnDefinition{
|
|
Name: "Labels",
|
|
Type: "string",
|
|
})
|
|
}
|
|
}
|
|
|
|
rows := table.Rows
|
|
|
|
includeLabels := len(options.ColumnLabels) > 0 || options.ShowLabels
|
|
if includeLabels || options.WithNamespace || nameColumn != -1 {
|
|
for i := range rows {
|
|
row := rows[i]
|
|
|
|
if nameColumn != -1 {
|
|
row.Cells[nameColumn] = fmt.Sprintf("%s/%s", strings.ToLower(options.Kind.String()), row.Cells[nameColumn])
|
|
}
|
|
|
|
var m metav1.Object
|
|
if obj := row.Object.Object; obj != nil {
|
|
if acc, err := meta.Accessor(obj); err == nil {
|
|
m = acc
|
|
}
|
|
}
|
|
// if we can't get an accessor, fill out the appropriate columns with empty spaces
|
|
if m == nil {
|
|
if options.WithNamespace {
|
|
r := make([]interface{}, 1, width)
|
|
row.Cells = append(r, row.Cells...)
|
|
}
|
|
for j := 0; j < width-len(row.Cells); j++ {
|
|
row.Cells = append(row.Cells, nil)
|
|
}
|
|
rows[i] = row
|
|
continue
|
|
}
|
|
|
|
if options.WithNamespace {
|
|
r := make([]interface{}, 1, width)
|
|
r[0] = m.GetNamespace()
|
|
row.Cells = append(r, row.Cells...)
|
|
}
|
|
if includeLabels {
|
|
row.Cells = appendLabelCells(row.Cells, m.GetLabels(), options)
|
|
}
|
|
rows[i] = row
|
|
}
|
|
}
|
|
|
|
table.ColumnDefinitions = columns
|
|
table.Rows = rows
|
|
return nil
|
|
}
|
|
|
|
// PrintTable returns a table for the provided object, using the printer registered for that type. It returns
|
|
// a table that includes all of the information requested by options, but will not remove rows or columns. The
|
|
// caller is responsible for applying rules related to filtering rows or columns.
|
|
func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) {
|
|
t := reflect.TypeOf(obj)
|
|
handler, ok := h.handlerMap[t]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no table handler registered for this type %v", t)
|
|
}
|
|
if !handler.printRows {
|
|
return h.legacyPrinterToTable(obj, handler)
|
|
}
|
|
|
|
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
|
|
results := handler.printFunc.Call(args)
|
|
if !results[1].IsNil() {
|
|
return nil, results[1].Interface().(error)
|
|
}
|
|
|
|
columns := handler.columnDefinitions
|
|
if !options.Wide {
|
|
columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions))
|
|
for i := range handler.columnDefinitions {
|
|
if handler.columnDefinitions[i].Priority != 0 {
|
|
continue
|
|
}
|
|
columns = append(columns, handler.columnDefinitions[i])
|
|
}
|
|
}
|
|
table := &metav1beta1.Table{
|
|
ListMeta: metav1.ListMeta{
|
|
ResourceVersion: "",
|
|
},
|
|
ColumnDefinitions: columns,
|
|
Rows: results[0].Interface().([]metav1beta1.TableRow),
|
|
}
|
|
if m, err := meta.ListAccessor(obj); err == nil {
|
|
table.ResourceVersion = m.GetResourceVersion()
|
|
table.SelfLink = m.GetSelfLink()
|
|
table.Continue = m.GetContinue()
|
|
} else {
|
|
if m, err := meta.CommonAccessor(obj); err == nil {
|
|
table.ResourceVersion = m.GetResourceVersion()
|
|
table.SelfLink = m.GetSelfLink()
|
|
}
|
|
}
|
|
if err := DecorateTable(table, options); err != nil {
|
|
return nil, err
|
|
}
|
|
return table, nil
|
|
}
|
|
|
|
// printRowsForHandlerEntry prints the incremental table output (headers if the current type is
|
|
// different from lastType) including all the rows in the object. It returns the current type
|
|
// or an error, if any.
|
|
func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runtime.Object, options PrintOptions, includeHeaders bool) error {
|
|
var results []reflect.Value
|
|
if handler.printRows {
|
|
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
|
|
results = handler.printFunc.Call(args)
|
|
if !results[1].IsNil() {
|
|
return results[1].Interface().(error)
|
|
}
|
|
}
|
|
|
|
if includeHeaders {
|
|
var headers []string
|
|
for _, column := range handler.columnDefinitions {
|
|
if column.Priority != 0 && !options.Wide {
|
|
continue
|
|
}
|
|
headers = append(headers, strings.ToUpper(column.Name))
|
|
}
|
|
headers = append(headers, formatLabelHeaders(options.ColumnLabels)...)
|
|
// LABELS is always the last column.
|
|
headers = append(headers, formatShowLabelsHeader(options.ShowLabels)...)
|
|
if options.WithNamespace {
|
|
headers = append(withNamespacePrefixColumns, headers...)
|
|
}
|
|
printHeader(headers, output)
|
|
}
|
|
|
|
if !handler.printRows {
|
|
// TODO: this code path is deprecated and will be removed when all handlers are row printers
|
|
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(output), reflect.ValueOf(options)}
|
|
resultValue := handler.printFunc.Call(args)[0]
|
|
if resultValue.IsNil() {
|
|
return nil
|
|
}
|
|
return resultValue.Interface().(error)
|
|
}
|
|
|
|
if results[1].IsNil() {
|
|
rows := results[0].Interface().([]metav1beta1.TableRow)
|
|
printRows(output, rows, options)
|
|
return nil
|
|
}
|
|
return results[1].Interface().(error)
|
|
}
|
|
|
|
// printRows writes the provided rows to output.
|
|
func printRows(output io.Writer, rows []metav1beta1.TableRow, options PrintOptions) {
|
|
for _, row := range rows {
|
|
if options.WithNamespace {
|
|
if obj := row.Object.Object; obj != nil {
|
|
if m, err := meta.Accessor(obj); err == nil {
|
|
fmt.Fprint(output, m.GetNamespace())
|
|
}
|
|
}
|
|
fmt.Fprint(output, "\t")
|
|
}
|
|
|
|
for i, cell := range row.Cells {
|
|
if i != 0 {
|
|
fmt.Fprint(output, "\t")
|
|
} else {
|
|
// TODO: remove this once we drop the legacy printers
|
|
if options.WithKind && !options.Kind.Empty() {
|
|
fmt.Fprintf(output, "%s/%s", strings.ToLower(options.Kind.String()), cell)
|
|
continue
|
|
}
|
|
}
|
|
fmt.Fprint(output, cell)
|
|
}
|
|
|
|
hasLabels := len(options.ColumnLabels) > 0
|
|
if obj := row.Object.Object; obj != nil && (hasLabels || options.ShowLabels) {
|
|
if m, err := meta.Accessor(obj); err == nil {
|
|
for _, value := range labelValues(m.GetLabels(), options) {
|
|
output.Write([]byte("\t"))
|
|
output.Write([]byte(value))
|
|
}
|
|
}
|
|
}
|
|
|
|
output.Write([]byte("\n"))
|
|
}
|
|
}
|
|
|
|
// legacyPrinterToTable uses the old printFunc with tabbed writer to generate a table.
|
|
// TODO: remove when all legacy printers are removed.
|
|
func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler *handlerEntry) (*metav1beta1.Table, error) {
|
|
printFunc := handler.printFunc
|
|
table := &metav1beta1.Table{
|
|
ColumnDefinitions: handler.columnDefinitions,
|
|
}
|
|
|
|
options := PrintOptions{
|
|
NoHeaders: true,
|
|
Wide: true,
|
|
}
|
|
buf := &bytes.Buffer{}
|
|
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(buf), reflect.ValueOf(options)}
|
|
|
|
if meta.IsListType(obj) {
|
|
listInterface, ok := obj.(metav1.ListInterface)
|
|
if ok {
|
|
table.ListMeta.SelfLink = listInterface.GetSelfLink()
|
|
table.ListMeta.ResourceVersion = listInterface.GetResourceVersion()
|
|
table.ListMeta.Continue = listInterface.GetContinue()
|
|
}
|
|
|
|
// TODO: this uses more memory than it has to, as we refactor printers we should remove the need
|
|
// for this.
|
|
args[0] = reflect.ValueOf(obj)
|
|
resultValue := printFunc.Call(args)[0]
|
|
if !resultValue.IsNil() {
|
|
return nil, resultValue.Interface().(error)
|
|
}
|
|
data := buf.Bytes()
|
|
i := 0
|
|
items, err := meta.ExtractList(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for len(data) > 0 {
|
|
cells, remainder := tabbedLineToCells(data, len(table.ColumnDefinitions))
|
|
table.Rows = append(table.Rows, metav1beta1.TableRow{
|
|
Cells: cells,
|
|
Object: runtime.RawExtension{Object: items[i]},
|
|
})
|
|
data = remainder
|
|
i++
|
|
}
|
|
} else {
|
|
args[0] = reflect.ValueOf(obj)
|
|
resultValue := printFunc.Call(args)[0]
|
|
if !resultValue.IsNil() {
|
|
return nil, resultValue.Interface().(error)
|
|
}
|
|
data := buf.Bytes()
|
|
cells, _ := tabbedLineToCells(data, len(table.ColumnDefinitions))
|
|
table.Rows = append(table.Rows, metav1beta1.TableRow{
|
|
Cells: cells,
|
|
Object: runtime.RawExtension{Object: obj},
|
|
})
|
|
}
|
|
return table, nil
|
|
}
|
|
|
|
// TODO: this method assumes the meta/v1 server API, so should be refactored out of this package
|
|
func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error {
|
|
metadata, err := meta.Accessor(unstructured)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if options.WithNamespace {
|
|
if _, err := fmt.Fprintf(w, "%s\t", metadata.GetNamespace()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
content := unstructured.UnstructuredContent()
|
|
kind := "<missing>"
|
|
if objKind, ok := content["kind"]; ok {
|
|
if str, ok := objKind.(string); ok {
|
|
kind = str
|
|
}
|
|
}
|
|
if objAPIVersion, ok := content["apiVersion"]; ok {
|
|
if str, ok := objAPIVersion.(string); ok {
|
|
version, err := schema.ParseGroupVersion(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kind = kind + "." + version.Version + "." + version.Group
|
|
}
|
|
}
|
|
|
|
name := FormatResourceName(options.Kind, metadata.GetName(), options.WithKind)
|
|
|
|
if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil {
|
|
return err
|
|
}
|
|
for _, field := range additionalFields {
|
|
if value, ok := content[field]; ok {
|
|
var formattedValue string
|
|
switch typedValue := value.(type) {
|
|
case []interface{}:
|
|
formattedValue = fmt.Sprintf("%d item(s)", len(typedValue))
|
|
default:
|
|
formattedValue = fmt.Sprintf("%v", value)
|
|
}
|
|
if _, err := fmt.Fprintf(w, "\t%s", formattedValue); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if _, err := fmt.Fprint(w, AppendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil {
|
|
return err
|
|
}
|
|
if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, metadata.GetLabels())); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func formatLabelHeaders(columnLabels []string) []string {
|
|
formHead := make([]string, len(columnLabels))
|
|
for i, l := range columnLabels {
|
|
p := strings.Split(l, "/")
|
|
formHead[i] = strings.ToUpper((p[len(p)-1]))
|
|
}
|
|
return formHead
|
|
}
|
|
|
|
// headers for --show-labels=true
|
|
func formatShowLabelsHeader(showLabels bool) []string {
|
|
if showLabels {
|
|
return []string{"LABELS"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// labelValues returns a slice of value columns matching the requested print options.
|
|
func labelValues(itemLabels map[string]string, opts PrintOptions) []string {
|
|
var values []string
|
|
for _, key := range opts.ColumnLabels {
|
|
values = append(values, itemLabels[key])
|
|
}
|
|
if opts.ShowLabels {
|
|
values = append(values, labels.FormatLabels(itemLabels))
|
|
}
|
|
return values
|
|
}
|
|
|
|
// appendLabelCells returns a slice of value columns matching the requested print options.
|
|
// Intended for use with tables.
|
|
func appendLabelCells(values []interface{}, itemLabels map[string]string, opts PrintOptions) []interface{} {
|
|
for _, key := range opts.ColumnLabels {
|
|
values = append(values, itemLabels[key])
|
|
}
|
|
if opts.ShowLabels {
|
|
values = append(values, labels.FormatLabels(itemLabels))
|
|
}
|
|
return values
|
|
}
|
|
|
|
// FormatResourceName receives a resource kind, name, and boolean specifying
|
|
// whether or not to update the current name to "kind/name"
|
|
func FormatResourceName(kind schema.GroupKind, name string, withKind bool) string {
|
|
if !withKind || kind.Empty() {
|
|
return name
|
|
}
|
|
|
|
return strings.ToLower(kind.String()) + "/" + name
|
|
}
|
|
|
|
func AppendLabels(itemLabels map[string]string, columnLabels []string) string {
|
|
var buffer bytes.Buffer
|
|
|
|
for _, cl := range columnLabels {
|
|
buffer.WriteString(fmt.Sprint("\t"))
|
|
if il, ok := itemLabels[cl]; ok {
|
|
buffer.WriteString(fmt.Sprint(il))
|
|
} else {
|
|
buffer.WriteString("<none>")
|
|
}
|
|
}
|
|
|
|
return buffer.String()
|
|
}
|
|
|
|
// Append all labels to a single column. We need this even when show-labels flag* is
|
|
// false, since this adds newline delimiter to the end of each row.
|
|
func AppendAllLabels(showLabels bool, itemLabels map[string]string) string {
|
|
var buffer bytes.Buffer
|
|
|
|
if showLabels {
|
|
buffer.WriteString(fmt.Sprint("\t"))
|
|
buffer.WriteString(labels.FormatLabels(itemLabels))
|
|
}
|
|
buffer.WriteString("\n")
|
|
|
|
return buffer.String()
|
|
}
|
|
|
|
// check if the object is unstructured. If so, attempt to convert it to a type we can understand.
|
|
func decodeUnknownObject(obj runtime.Object, decoder runtime.Decoder) (runtime.Object, error) {
|
|
var err error
|
|
switch t := obj.(type) {
|
|
case runtime.Unstructured:
|
|
if objBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj); err == nil {
|
|
if decodedObj, err := runtime.Decode(decoder, objBytes); err == nil {
|
|
obj = decodedObj
|
|
}
|
|
}
|
|
case *runtime.Unknown:
|
|
if decodedObj, err := runtime.Decode(decoder, t.Raw); err == nil {
|
|
obj = decodedObj
|
|
}
|
|
}
|
|
|
|
return obj, err
|
|
}
|
|
|
|
func tabbedLineToCells(data []byte, expected int) ([]interface{}, []byte) {
|
|
var remainder []byte
|
|
max := bytes.Index(data, []byte("\n"))
|
|
if max != -1 {
|
|
remainder = data[max+1:]
|
|
data = data[:max]
|
|
}
|
|
cells := make([]interface{}, expected)
|
|
for i := 0; i < expected; i++ {
|
|
next := bytes.Index(data, []byte("\t"))
|
|
if next == -1 {
|
|
cells[i] = string(data)
|
|
// fill the remainder with empty strings, this indicates a printer bug
|
|
for j := i + 1; j < expected; j++ {
|
|
cells[j] = ""
|
|
}
|
|
break
|
|
}
|
|
cells[i] = string(data[:next])
|
|
data = data[next+1:]
|
|
}
|
|
return cells, remainder
|
|
}
|