Merge pull request #81404 from seans3/tableprinter-cleanup

Split HumanReadablePrinter struct into generator and printer structs
This commit is contained in:
Kubernetes Prow Robot 2019-08-21 10:36:45 -07:00 committed by GitHub
commit 2cfef837fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 165 additions and 175 deletions

View File

@ -301,15 +301,22 @@ func ErrorPrintHandler(obj *TestPrintType, options printers.PrintOptions) ([]met
func TestCustomTypePrinting(t *testing.T) {
columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}}
printer := printers.NewTablePrinter(printers.PrintOptions{})
printer.TableHandler(columns, PrintCustomType)
generator := printers.NewTableGenerator()
generator.TableHandler(columns, PrintCustomType)
obj := TestPrintType{"test object"}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&obj, buffer)
table, err := generator.GenerateTable(&obj, printers.PrintOptions{})
if err != nil {
t.Fatalf("An error occurred printing the custom type: %#v", err)
t.Fatalf("An error occurred generating the table for custom type: %#v", err)
}
printer := printers.NewTablePrinter(printers.PrintOptions{})
buffer := &bytes.Buffer{}
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing the Table: %#v", err)
}
expectedOutput := "DATA\ntest object\n"
if buffer.String() != expectedOutput {
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String())
@ -318,11 +325,10 @@ func TestCustomTypePrinting(t *testing.T) {
func TestPrintHandlerError(t *testing.T) {
columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}}
printer := printers.NewTablePrinter(printers.PrintOptions{})
printer.TableHandler(columns, ErrorPrintHandler)
generator := printers.NewTableGenerator()
generator.TableHandler(columns, ErrorPrintHandler)
obj := TestPrintType{"test object"}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&obj, buffer)
_, err := generator.GenerateTable(&obj, printers.PrintOptions{})
if err == nil || err.Error() != "ErrorPrintHandler error" {
t.Errorf("Did not get the expected error: %#v", err)
}
@ -587,34 +593,9 @@ func TestPrinters(t *testing.T) {
}
}
}
// a humanreadable printer deals with internal-versioned objects
humanReadablePrinter := map[string]printers.ResourcePrinter{
"humanReadable": printers.NewTablePrinter(printers.PrintOptions{
NoHeaders: true,
}),
"humanReadableHeaders": printers.NewTablePrinter(printers.PrintOptions{}),
}
AddHandlers((humanReadablePrinter["humanReadable"]).(*printers.HumanReadablePrinter))
AddHandlers((humanReadablePrinter["humanReadableHeaders"]).(*printers.HumanReadablePrinter))
for pName, p := range humanReadablePrinter {
for oName, obj := range objects {
b := &bytes.Buffer{}
if err := p.PrintObj(obj, b); err != nil {
if set, found := expectedErrors[pName]; found && set.Has(oName) {
// expected error
continue
}
t.Errorf("printer '%v', object '%v'; error: '%v'", pName, oName, err)
}
}
}
}
func TestPrintEventsResultSorted(t *testing.T) {
// Arrange
printer := printers.NewTablePrinter(printers.PrintOptions{})
AddHandlers(printer)
obj := api.EventList{
Items: []api.Event{
@ -644,22 +625,26 @@ func TestPrintEventsResultSorted(t *testing.T) {
},
},
}
buffer := &bytes.Buffer{}
// Act
err := printer.PrintObj(&obj, buffer)
table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&obj, printers.PrintOptions{})
if err != nil {
t.Fatalf("An error occurred generating the Table: %#v", err)
}
printer := printers.NewTablePrinter(printers.PrintOptions{})
buffer := &bytes.Buffer{}
err = printer.PrintObj(table, buffer)
// Assert
if err != nil {
t.Fatalf("An error occurred printing the EventList: %#v", err)
t.Fatalf("An error occurred printing the Table: %#v", err)
}
out := buffer.String()
VerifyDatesInOrder(out, "\n" /* rowDelimiter */, " " /* columnDelimiter */, t)
}
func TestPrintNodeStatus(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{})
AddHandlers(printer)
table := []struct {
node api.Node
status string
@ -735,9 +720,15 @@ func TestPrintNodeStatus(t *testing.T) {
},
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(printers.PrintOptions{})
for _, test := range table {
table, err := generator.GenerateTable(&test.node, printers.PrintOptions{})
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -748,8 +739,7 @@ func TestPrintNodeStatus(t *testing.T) {
}
func TestPrintNodeRole(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{})
AddHandlers(printer)
table := []struct {
node api.Node
expected string
@ -780,9 +770,15 @@ func TestPrintNodeRole(t *testing.T) {
},
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(printers.PrintOptions{})
for _, test := range table {
table, err := generator.GenerateTable(&test.node, printers.PrintOptions{})
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -793,11 +789,6 @@ func TestPrintNodeRole(t *testing.T) {
}
func TestPrintNodeOSImage(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
ColumnLabels: []string{},
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
@ -825,9 +816,18 @@ func TestPrintNodeOSImage(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -838,11 +838,6 @@ func TestPrintNodeOSImage(t *testing.T) {
}
func TestPrintNodeKernelVersion(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
ColumnLabels: []string{},
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
@ -870,9 +865,18 @@ func TestPrintNodeKernelVersion(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -883,11 +887,6 @@ func TestPrintNodeKernelVersion(t *testing.T) {
}
func TestPrintNodeContainerRuntimeVersion(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
ColumnLabels: []string{},
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
@ -915,9 +914,18 @@ func TestPrintNodeContainerRuntimeVersion(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -928,10 +936,7 @@ func TestPrintNodeContainerRuntimeVersion(t *testing.T) {
}
func TestPrintNodeName(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
Name string
@ -952,9 +957,18 @@ func TestPrintNodeName(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -965,10 +979,7 @@ func TestPrintNodeName(t *testing.T) {
}
func TestPrintNodeExternalIP(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
externalIP string
@ -1000,9 +1011,18 @@ func TestPrintNodeExternalIP(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -1013,10 +1033,7 @@ func TestPrintNodeExternalIP(t *testing.T) {
}
func TestPrintNodeInternalIP(t *testing.T) {
printer := printers.NewTablePrinter(printers.PrintOptions{
Wide: true,
})
AddHandlers(printer)
table := []struct {
node api.Node
internalIP string
@ -1048,9 +1065,18 @@ func TestPrintNodeInternalIP(t *testing.T) {
},
}
options := printers.PrintOptions{
Wide: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for _, test := range table {
table, err := generator.GenerateTable(&test.node, options)
if err != nil {
t.Fatalf("An error occurred generating table for Node: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&test.node, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing Node: %#v", err)
}
@ -1399,12 +1425,6 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
},
isNamespaced: true,
},
{
obj: &api.LimitRange{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName},
},
isNamespaced: true,
},
{
obj: &api.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName},
@ -1419,36 +1439,22 @@ 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,
},
}
//*******//
options := printers.PrintOptions{
WithNamespace: true,
NoHeaders: true,
}
generator := printers.NewTableGenerator().With(AddHandlers)
printer := printers.NewTablePrinter(options)
for i, test := range table {
printer := printers.NewTablePrinter(printers.PrintOptions{
WithNamespace: true,
NoHeaders: true,
})
AddHandlers(printer)
table, err := generator.GenerateTable(test.obj, options)
if err != nil {
t.Fatalf("An error occurred generating table for object: %#v", err)
}
buffer := &bytes.Buffer{}
err := printer.PrintObj(test.obj, buffer)
err = printer.PrintObj(table, buffer)
if err != nil {
t.Fatalf("An error occurred printing object: %#v", err)
}
@ -1488,38 +1494,43 @@ func TestPrintPodTable(t *testing.T) {
},
}
tests := []struct {
obj runtime.Object
opts printers.PrintOptions
expect string
ignoreLegacy bool
obj runtime.Object
opts printers.PrintOptions
expect string
}{
{
obj: runningPod, opts: printers.PrintOptions{},
obj: runningPod,
opts: printers.PrintOptions{},
expect: "NAME READY STATUS RESTARTS AGE\ntest1 1/2 Running 6 <unknown>\n",
},
{
obj: runningPod, opts: printers.PrintOptions{WithKind: true, Kind: schema.GroupKind{Kind: "Pod"}},
obj: runningPod,
opts: printers.PrintOptions{WithKind: true, Kind: schema.GroupKind{Kind: "Pod"}},
expect: "NAME READY STATUS RESTARTS AGE\npod/test1 1/2 Running 6 <unknown>\n",
},
{
obj: runningPod, opts: printers.PrintOptions{ShowLabels: true},
obj: runningPod,
opts: printers.PrintOptions{ShowLabels: true},
expect: "NAME READY STATUS RESTARTS AGE LABELS\ntest1 1/2 Running 6 <unknown> a=1,b=2\n",
},
{
obj: &api.PodList{Items: []api.Pod{*runningPod, *failedPod}}, opts: printers.PrintOptions{ColumnLabels: []string{"a"}},
obj: &api.PodList{Items: []api.Pod{*runningPod, *failedPod}},
opts: printers.PrintOptions{ColumnLabels: []string{"a"}},
expect: "NAME READY STATUS RESTARTS AGE A\ntest1 1/2 Running 6 <unknown> 1\ntest2 1/2 Failed 6 <unknown> \n",
},
{
obj: runningPod, opts: printers.PrintOptions{NoHeaders: true},
obj: runningPod,
opts: printers.PrintOptions{NoHeaders: true},
expect: "test1 1/2 Running 6 <unknown>\n",
},
{
obj: failedPod, opts: printers.PrintOptions{},
expect: "NAME READY STATUS RESTARTS AGE\ntest2 1/2 Failed 6 <unknown>\n",
ignoreLegacy: true, // filtering is not done by the printer in the legacy path
obj: failedPod,
opts: printers.PrintOptions{},
expect: "NAME READY STATUS RESTARTS AGE\ntest2 1/2 Failed 6 <unknown>\n",
},
{
obj: failedPod, opts: printers.PrintOptions{},
obj: failedPod,
opts: printers.PrintOptions{},
expect: "NAME READY STATUS RESTARTS AGE\ntest2 1/2 Failed 6 <unknown>\n",
},
}
@ -1531,24 +1542,13 @@ func TestPrintPodTable(t *testing.T) {
}
verifyTable(t, table)
buf := &bytes.Buffer{}
p := printers.NewTablePrinter(test.opts).With(AddHandlers)
p := printers.NewTablePrinter(test.opts)
if err := p.PrintObj(table, buf); err != nil {
t.Fatal(err)
}
if test.expect != buf.String() {
t.Errorf("%d mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String()))
}
if test.ignoreLegacy {
continue
}
buf.Reset()
if err := p.PrintObj(test.obj, buf); err != nil {
t.Fatal(err)
}
if test.expect != buf.String() {
t.Errorf("%d legacy mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String()))
}
}
}

View File

@ -43,30 +43,25 @@ type handlerEntry struct {
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
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
// HumanReadableGenerator is an implementation of TableGenerator used to generate
// a table for a specific resource. The table is printed with a TablePrinter using
// PrintObj().
type HumanReadableGenerator struct {
handlerMap map[reflect.Type]*handlerEntry
}
var _ TableGenerator = &HumanReadablePrinter{}
var _ PrintHandler = &HumanReadablePrinter{}
var _ TableGenerator = &HumanReadableGenerator{}
var _ PrintHandler = &HumanReadableGenerator{}
// NewTableGenerator creates a HumanReadablePrinter suitable for calling GenerateTable().
func NewTableGenerator() *HumanReadablePrinter {
return &HumanReadablePrinter{
// NewTableGenerator creates a HumanReadableGenerator suitable for calling GenerateTable().
func NewTableGenerator() *HumanReadableGenerator {
return &HumanReadableGenerator{
handlerMap: make(map[reflect.Type]*handlerEntry),
}
}
// With method - accepts a list of builder functions that modify HumanReadablePrinter
func (h *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter {
// With method - accepts a list of builder functions that modify HumanReadableGenerator
func (h *HumanReadableGenerator) With(fns ...func(PrintHandler)) *HumanReadableGenerator {
for _, fn := range fns {
fn(h)
}
@ -76,7 +71,7 @@ func (h *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePri
// 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) {
func (h *HumanReadableGenerator) GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) {
t := reflect.TypeOf(obj)
handler, ok := h.handlerMap[t]
if !ok {
@ -120,15 +115,17 @@ func (h *HumanReadablePrinter) GenerateTable(obj runtime.Object, options PrintOp
table.SelfLink = m.GetSelfLink()
}
}
// TODO(seans3): Remove the following decorateTable call. This should only be
// called in the table printer.
if err := decorateTable(table, options); err != nil {
return nil, err
}
return table, nil
}
// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance.
// TableHandler adds a print handler with a given set of columns to HumanReadableGenerator instance.
// See ValidateRowPrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
func (h *HumanReadableGenerator) 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))

View File

@ -61,13 +61,23 @@ var (
withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
)
// 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 {
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
}
// NewTablePrinter creates a printer suitable for calling PrintObj().
// TODO(seans3): Change return type to ResourcePrinter interface once we no longer need
// to constuct the "handlerMap".
func NewTablePrinter(options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
options: options,
options: options,
}
return printer
}
@ -81,6 +91,7 @@ func printHeader(columnNames []string, w io.Writer) error {
// 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)
@ -94,7 +105,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
obj = event.Object.Object
}
// Case 1: Parameter "obj" is a table from server; print it.
// Parameter "obj" is a table from server; print it.
// display tables following the rules of options
if table, ok := obj.(*metav1beta1.Table); ok {
// Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
@ -131,25 +142,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
return printTable(table, output, localOptions)
}
// Case 2: Parameter "obj" is not a table; search for a handler to print it.
// TODO(seans3): Remove this case in 1.16, since table should be returned from server-side printing.
// 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, eventType, obj, h.options, includeHeaders); err != nil {
return err
}
h.lastType = t
return nil
}
// Case 3: Could not find print handler for "obj"; use the default or status print handler.
// Could not find print handler for "obj"; use the default or status print handler.
// Print with the default or status handler, and use the columns from the last time
var handler *handlerEntry
if _, isStatus := obj.(*metav1.Status); isStatus {