Fix --watch-only of a single item with table output

This commit is contained in:
Jordan Liggitt 2019-06-24 09:34:15 -07:00
parent 2b65c74b94
commit 5d508760e3
5 changed files with 95 additions and 37 deletions

View File

@ -22,6 +22,7 @@ go_library(
"get.go", "get.go",
"get_flags.go", "get_flags.go",
"humanreadable_flags.go", "humanreadable_flags.go",
"skip_printer.go",
"sorter.go", "sorter.go",
"table_printer.go", "table_printer.go",
], ],
@ -60,6 +61,7 @@ go_library(
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/integer:go_default_library", "//vendor/k8s.io/utils/integer:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
"//vendor/vbom.ml/util/sortorder:go_default_library", "//vendor/vbom.ml/util/sortorder:go_default_library",
], ],
) )
@ -97,7 +99,6 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime: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/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",

View File

@ -49,12 +49,13 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
utilpointer "k8s.io/utils/pointer"
) )
// GetOptions contains the input to the get command. // GetOptions contains the input to the get command.
type GetOptions struct { type GetOptions struct {
PrintFlags *PrintFlags PrintFlags *PrintFlags
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error) ToPrinter func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error)
IsHumanReadablePrinter bool IsHumanReadablePrinter bool
PrintWithOpenAPICols bool PrintWithOpenAPICols bool
@ -230,7 +231,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.IsHumanReadablePrinter = true o.IsHumanReadablePrinter = true
} }
o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) { o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
// make a new copy of current flags / opts before mutating // make a new copy of current flags / opts before mutating
printFlags := o.PrintFlags.Copy() printFlags := o.PrintFlags.Copy()
@ -257,6 +258,9 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
if o.Sort { if o.Sort {
printer = &SortingPrinter{Delegate: printer, SortField: sortBy} printer = &SortingPrinter{Delegate: printer, SortField: sortBy}
} }
if outputObjects != nil {
printer = &skipPrinter{delegate: printer, output: outputObjects}
}
if o.ServerPrint { if o.ServerPrint {
printer = &TablePrinter{Delegate: printer} printer = &TablePrinter{Delegate: printer}
} }
@ -539,7 +543,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
fmt.Fprintln(o.ErrOut) fmt.Fprintln(o.ErrOut)
} }
printer, err = o.ToPrinter(mapping, printWithNamespace, printWithKind) printer, err = o.ToPrinter(mapping, nil, printWithNamespace, printWithKind)
if err != nil { if err != nil {
if !errs.Has(err.Error()) { if !errs.Has(err.Error()) {
errs.Insert(err.Error()) errs.Insert(err.Error())
@ -635,7 +639,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
info := infos[0] info := infos[0]
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
printer, err := o.ToPrinter(mapping, o.AllNamespaces, false) outputObjects := utilpointer.BoolPtr(!o.WatchOnly)
printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false)
if err != nil { if err != nil {
return err return err
} }
@ -664,25 +669,29 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind() tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind()
// print the current object // print the current object
if !o.WatchOnly { var objsToPrint []runtime.Object
var objsToPrint []runtime.Object if isList {
objsToPrint, _ = meta.ExtractList(obj)
if isList { } else {
objsToPrint, _ = meta.ExtractList(obj) objsToPrint = append(objsToPrint, obj)
} else { }
objsToPrint = append(objsToPrint, obj) for _, objToPrint := range objsToPrint {
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
// printing anything other than tables always takes the internal version, but the watch event uses externals
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
} }
for _, objToPrint := range objsToPrint { if err := printer.PrintObj(objToPrint, writer); err != nil {
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK { return fmt.Errorf("unable to output the provided object: %v", err)
// printing anything other than tables always takes the internal version, but the watch event uses externals
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
}
if err := printer.PrintObj(objToPrint, writer); err != nil {
return fmt.Errorf("unable to output the provided object: %v", err)
}
} }
writer.Flush() }
writer.Flush()
if isList {
// we can start outputting objects now, watches started from lists don't emit synthetic added events
*outputObjects = true
} else {
// suppress output, since watches started for individual items emit a synthetic ADDED event first
*outputObjects = false
} }
// print watched changes // print watched changes
@ -691,18 +700,11 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
return err return err
} }
first := true
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
intr := interrupt.New(nil, cancel) intr := interrupt.New(nil, cancel)
intr.Run(func() error { intr.Run(func() error {
_, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { _, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
if !isList && first {
// drop the initial watch event in the single resource case
first = false
return false, nil
}
// printing always takes the internal version, but the watch event uses externals // printing always takes the internal version, but the watch event uses externals
// TODO fix printing to use server-side or be version agnostic // TODO fix printing to use server-side or be version agnostic
objToPrint := e.Object objToPrint := e.Object
@ -714,6 +716,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
return false, err return false, err
} }
writer.Flush() writer.Flush()
// after processing at least one event, start outputting objects
*outputObjects = true
return false, nil return false, nil
}) })
return err return err
@ -750,7 +754,7 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
} }
printer, err := o.ToPrinter(nil, false, false) printer, err := o.ToPrinter(nil, nil, false, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,48 @@
/*
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.
*/
package get
import (
"io"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
)
// skipPrinter allows conditionally suppressing object output via the output field.
// table objects are suppressed by setting their Rows to nil (allowing column definitions to propagate to the delegate).
// non-table objects are suppressed by not calling the delegate at all.
type skipPrinter struct {
delegate printers.ResourcePrinter
output *bool
}
func (p *skipPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
if *p.output {
return p.delegate.PrintObj(obj, writer)
}
table, isTable := obj.(*metav1beta1.Table)
if !isTable {
return nil
}
table = table.DeepCopy()
table.Rows = nil
return p.delegate.PrintObj(table, writer)
}

View File

@ -48,10 +48,11 @@ type handlerEntry struct {
// will only be printed if the object type changes. This makes it useful for printing items // will only be printed if the object type changes. This makes it useful for printing items
// received from watches. // received from watches.
type HumanReadablePrinter struct { type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry handlerMap map[reflect.Type]*handlerEntry
options PrintOptions options PrintOptions
lastType interface{} lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
} }
var _ TableGenerator = &HumanReadablePrinter{} var _ TableGenerator = &HumanReadablePrinter{}

View File

@ -80,18 +80,22 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
if table, ok := obj.(*metav1beta1.Table); ok { 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 // Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
localOptions := h.options localOptions := h.options
if len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) { if h.printedHeaders && (len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns)) {
localOptions.NoHeaders = true localOptions.NoHeaders = true
} }
if len(table.ColumnDefinitions) == 0 { if len(table.ColumnDefinitions) == 0 {
// If this table has no column definitions, use the columns from the last table we printed for decoration and layout. // If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
// This is done when receiving tables in watch events to save bandwidth. // This is done when receiving tables in watch events to save bandwidth.
localOptions.NoHeaders = true
table.ColumnDefinitions = h.lastColumns table.ColumnDefinitions = h.lastColumns
} else { } else if !reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
// If this table has column definitions, remember them for future use. // If this table has column definitions, remember them for future use.
h.lastColumns = table.ColumnDefinitions h.lastColumns = table.ColumnDefinitions
h.printedHeaders = false
}
if len(table.Rows) > 0 {
h.printedHeaders = true
} }
if err := decorateTable(table, localOptions); err != nil { if err := decorateTable(table, localOptions); err != nil {