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

tableprinter: simplifies default printer handler #77148

Merged
merged 1 commit into from
May 4, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/printers/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/github.com/liggitt/tabwriter:go_default_library",
],
Expand Down
1 change: 0 additions & 1 deletion pkg/printers/internalversion/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ go_library(
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/api/scheduling/v1:go_default_library",
"//staging/src/k8s.io/api/storage/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
Expand Down
43 changes: 0 additions & 43 deletions pkg/printers/internalversion/printers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
schedulingv1 "k8s.io/api/scheduling/v1"
storagev1 "k8s.io/api/storage/v1"
"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/labels"
Expand Down Expand Up @@ -468,48 +467,6 @@ func AddHandlers(h printers.PrintHandler) {
}
h.TableHandler(volumeAttachmentColumnDefinitions, printVolumeAttachment)
h.TableHandler(volumeAttachmentColumnDefinitions, printVolumeAttachmentList)

AddDefaultHandlers(h)
}

// AddDefaultHandlers adds handlers that can work with most Kubernetes objects.
func AddDefaultHandlers(h printers.PrintHandler) {
// types without defined columns
objectMetaColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.DefaultTableHandler(objectMetaColumnDefinitions, printObjectMeta)
}

func printObjectMeta(obj runtime.Object, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
if meta.IsListType(obj) {
rows := make([]metav1beta1.TableRow, 0, 16)
err := meta.EachListItem(obj, func(obj runtime.Object) error {
nestedRows, err := printObjectMeta(obj, options)
if err != nil {
return err
}
rows = append(rows, nestedRows...)
return nil
})
if err != nil {
return nil, err
}
return rows, nil
}

rows := make([]metav1beta1.TableRow, 0, 1)
m, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
rows = append(rows, row)
return rows, nil
}

// Pass ports=nil for all ports.
Expand Down
62 changes: 38 additions & 24 deletions pkg/printers/internalversion/printers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func TestPrintUnstructuredObject(t *testing.T) {

for _, test := range tests {
out.Reset()
printer := printers.NewTablePrinter(test.options).With(AddDefaultHandlers)
printer := printers.NewTablePrinter(test.options)
printer.PrintObj(test.object, out)

matches, err := regexp.MatchString(test.expected, out.String())
Expand Down Expand Up @@ -1339,7 +1339,8 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}},
Ports: []api.EndpointPort{{Port: 8080}},
},
}},
},
},
isNamespaced: true,
},
{
Expand Down Expand Up @@ -1370,7 +1371,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
},
{
obj: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName},
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: api.PersistentVolumeSpec{},
},
isNamespaced: false,
Expand Down Expand Up @@ -1414,33 +1415,46 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
},
isNamespaced: false,
},
{
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Foo",
"apiVersion": "example.com/v1",
"metadata": map[string]interface{}{"name": "test", "namespace": namespaceName},
},
},
isNamespaced: true,
},
{
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Foo",
"apiVersion": "example.com/v1",
"metadata": map[string]interface{}{"name": "test"},
},
},
isNamespaced: false,
},
}

for i, test := range table {
printer := printers.NewTablePrinter(printers.PrintOptions{
WithNamespace: true,
NoHeaders: true,
})
AddHandlers(printer)
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
if err != nil {
t.Fatalf("An error occurred printing object: %#v", err)
}
if test.isNamespaced {
// Expect output to include namespace when requested.
printer := printers.NewTablePrinter(printers.PrintOptions{
WithNamespace: true,
})
AddHandlers(printer)
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
if err != nil {
t.Fatalf("An error occurred printing object: %#v", err)
}
matched := contains(strings.Fields(buffer.String()), fmt.Sprintf("%s", namespaceName))
if !matched {
t.Errorf("%d: Expect printing object to contain namespace: %#v", i, test.obj)
if !strings.HasPrefix(buffer.String(), namespaceName+" ") {
t.Errorf("%d: Expect printing object %T to contain namespace %q, got %s", i, test.obj, namespaceName, buffer.String())
}
} else {
// Expect error when trying to get all namespaces for un-namespaced object.
printer := printers.NewTablePrinter(printers.PrintOptions{
WithNamespace: true,
})
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
if err == nil {
t.Errorf("Expected error when printing un-namespaced type")
if !strings.HasPrefix(buffer.String(), " ") {
t.Errorf("%d: Expect printing object %T to not contain namespace got %s", i, test.obj, buffer.String())
}
}
}
Expand Down
28 changes: 4 additions & 24 deletions pkg/printers/tablegenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ type TableGenerator interface {
// PrintHandler - interface to handle printing provided an array of metav1beta1.TableColumnDefinition
type PrintHandler interface {
TableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
DefaultTableHandler(columns []metav1beta1.TableColumnDefinition, printFunc interface{}) error
}

type handlerEntry struct {
Expand All @@ -49,11 +48,10 @@ type handlerEntry struct {
// 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{}
lastColumns []metav1beta1.TableColumnDefinition
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
}

var _ TableGenerator = &HumanReadablePrinter{}
Expand Down Expand Up @@ -149,24 +147,6 @@ func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.Tabl
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,
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:
Expand Down
80 changes: 65 additions & 15 deletions pkg/printers/tableprinter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,32 @@ import (
"io"
"reflect"
"strings"
"time"

"github.com/liggitt/tabwriter"
"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/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
)

var _ ResourcePrinter = &HumanReadablePrinter{}
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.

var (
defaultHandlerEntry = &handlerEntry{
columnDefinitions: objectMetaColumnDefinitions,
printFunc: reflect.ValueOf(printObjectMeta),
}

objectMetaColumnDefinitions = []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}

withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
)

// NewTablePrinter creates a printer suitable for calling PrintObj().
// TODO(seans3): Change return type to ResourcePrinter interface once we no longer need
Expand Down Expand Up @@ -104,23 +119,18 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
}

// Case 3: Could not find print handler for "obj"; use the default print handler.
// 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
// Print with the default handler, and use the columns from the last time
includeHeaders := h.lastType != defaultHandlerEntry && !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
if h.lastType != nil && h.lastType != defaultHandlerEntry && !h.options.NoHeaders {
fmt.Fprintln(output)
}

// we failed all reasonable printing efforts, report failure
return fmt.Errorf("error: unknown type %#v", obj)
if err := printRowsForHandlerEntry(output, defaultHandlerEntry, obj, h.options, includeHeaders); err != nil {
return err
}
h.lastType = defaultHandlerEntry
return nil
}

// PrintTable prints a table to the provided output respecting the filtering rules for options
Expand Down Expand Up @@ -384,3 +394,43 @@ func appendLabelCells(values []interface{}, itemLabels map[string]string, opts P
}
return values
}

func printObjectMeta(obj runtime.Object, options PrintOptions) ([]metav1beta1.TableRow, error) {
if meta.IsListType(obj) {
rows := make([]metav1beta1.TableRow, 0, 16)
err := meta.EachListItem(obj, func(obj runtime.Object) error {
nestedRows, err := printObjectMeta(obj, options)
if err != nil {
return err
}
rows = append(rows, nestedRows...)
return nil
})
if err != nil {
return nil, err
}
return rows, nil
}

rows := make([]metav1beta1.TableRow, 0, 1)
m, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
rows = append(rows, row)
return rows, nil
}

// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}

return duration.HumanDuration(time.Since(timestamp.Time))
}