Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds human-friendly table printing for resource listing #85

Merged
merged 5 commits into from
May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions pkg/printers/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
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.
*/

// The following is a subset of original implementation
// at https://github.com/kubernetes/kubernetes/blob/v1.15.0-alpha.2/pkg/printers/interface.go

package printers

import (
"io"

"k8s.io/apimachinery/pkg/runtime"
)

// ResourcePrinter is an interface that knows how to print runtime objects.
type ResourcePrinter interface {
// Print receives a runtime object, formats it and prints it to a writer.
PrintObj(runtime.Object, io.Writer) error
}

// ResourcePrinterFunc is a function that can print objects
type ResourcePrinterFunc func(runtime.Object, io.Writer) error

// PrintObj implements ResourcePrinter
func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
return fn(obj, w)
}

// PrintOptions for different table printing options
type PrintOptions struct {
//TODO: Add options for eg: with-kind, server-printing, wide etc
}
150 changes: 150 additions & 0 deletions pkg/printers/tablegenerator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
Copyright 2019 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.
*/

// The following is a subset of original implementation
// at https://github.com/kubernetes/kubernetes/blob/v1.15.0-alpha.2/pkg/printers/tablegenerator.go

package printers

import (
"fmt"
"reflect"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)

type TableGenerator interface {
GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error)
}

type PrintHandler interface {
TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
}

type handlerEntry struct {
columnDefinitions []metav1beta1.TableColumnDefinition
printFunc reflect.Value
args []reflect.Value
}

// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
// more elegant output.
type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
}

var _ TableGenerator = &HumanReadablePrinter{}
var _ PrintHandler = &HumanReadablePrinter{}

// NewTableGenerator creates a HumanReadablePrinter suitable for calling GenerateTable().
func NewTableGenerator() *HumanReadablePrinter {
return &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
}
}

// GenerateTable 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) GenerateTable(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)
}

args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
results := handler.printFunc.Call(args)
if !results[1].IsNil() {
return nil, results[1].Interface().(error)
}

var columns []metav1beta1.TableColumnDefinition

columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions))
for i := range handler.columnDefinitions {
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()
}
}
return table, 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,
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
}

// 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
}
108 changes: 108 additions & 0 deletions pkg/printers/tableprinter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
Copyright 2019 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.
*/

// The following is a subset of original implementation
// at https://github.com/kubernetes/kubernetes/blob/v1.15.0-alpha.2/pkg/printers/tableprinter.go

package printers

import (
"fmt"
"io"
"reflect"
"strings"

metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"text/tabwriter"
)

var _ ResourcePrinter = &HumanReadablePrinter{}

// NewTablePrinter creates a printer suitable for calling PrintObj().
func NewTablePrinter(options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
options: options,
}
return printer
}

// 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 {
w = GetNewTabWriter(output)
output = w
defer w.Flush()
}

// Search for a handler registered handler to print it
t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil {

if err := printRowsForHandlerEntry(output, handler, obj, h.options); err != nil {
return err
}
return nil
}

// we failed all reasonable printing efforts, report failure
return fmt.Errorf("error: unknown type %#v", obj)
}

// printRowsForHandlerEntry prints the incremental table output
// 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) error {
var results []reflect.Value

args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)}
results = handler.printFunc.Call(args)
if !results[1].IsNil() {
return results[1].Interface().(error)
}

var headers []string
for _, column := range handler.columnDefinitions {
headers = append(headers, strings.ToUpper(column.Name))
}
printHeader(headers, output)

if results[1].IsNil() {
rows := results[0].Interface().([]metav1beta1.TableRow)
printRows(output, rows, options)
return nil
}
return results[1].Interface().(error)
}

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
}

// printRows writes the provided rows to output.
func printRows(output io.Writer, rows []metav1beta1.TableRow, options PrintOptions) {
for _, row := range rows {
for i, cell := range row.Cells {
if i != 0 {
fmt.Fprint(output, "\t")
}
fmt.Fprint(output, cell)
}
output.Write([]byte("\n"))
}
}
36 changes: 36 additions & 0 deletions pkg/printers/tabwriter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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.
*/

// The following is a subset of original implementation
// at https://github.com/kubernetes/kubernetes/blob/v1.15.0-alpha.2/pkg/printers/tabwriter.go

package printers

import (
"io"

"text/tabwriter"
)

const (
tabwriterMinWidth = 6
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '
tabwriterFlags = tabwriter.TabIndent
)

// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text.
func GetNewTabWriter(output io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
}
12 changes: 6 additions & 6 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,13 @@ k8s.io/api/storage/v1beta1
k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/apis/meta/v1
k8s.io/apimachinery/pkg/runtime/schema
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/apis/meta/v1beta1
k8s.io/apimachinery/pkg/runtime
k8s.io/apimachinery/pkg/util/runtime
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/validation
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
k8s.io/apimachinery/pkg/runtime
k8s.io/apimachinery/pkg/util/intstr
k8s.io/apimachinery/pkg/util/sets
k8s.io/apimachinery/pkg/util/validation
Expand All @@ -210,24 +213,21 @@ k8s.io/apimachinery/pkg/conversion
k8s.io/apimachinery/pkg/fields
k8s.io/apimachinery/pkg/labels
k8s.io/apimachinery/pkg/selection
k8s.io/apimachinery/pkg/util/runtime
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/util/json
k8s.io/apimachinery/pkg/util/net
k8s.io/apimachinery/pkg/util/yaml
k8s.io/apimachinery/pkg/util/errors
k8s.io/apimachinery/pkg/apis/meta/v1/validation
k8s.io/apimachinery/pkg/util/validation/field
k8s.io/apimachinery/pkg/conversion/queryparams
k8s.io/apimachinery/pkg/util/naming
k8s.io/apimachinery/pkg/apis/meta/v1/validation
k8s.io/apimachinery/pkg/util/validation/field
k8s.io/apimachinery/pkg/runtime/serializer/json
k8s.io/apimachinery/pkg/runtime/serializer/protobuf
k8s.io/apimachinery/pkg/runtime/serializer/recognizer
k8s.io/apimachinery/pkg/runtime/serializer/versioning
k8s.io/apimachinery/pkg/api/errors
k8s.io/apimachinery/pkg/runtime/serializer/streaming
k8s.io/apimachinery/third_party/forked/golang/reflect
k8s.io/apimachinery/pkg/apis/meta/v1beta1
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme
k8s.io/apimachinery/pkg/version
k8s.io/apimachinery/pkg/util/strategicpatch
Expand Down