Skip to content

Commit c082ace

Browse files
authored
Merge pull request kubernetes#76161 from liggitt/kubectl-watch-table
use server-side printing in `kubectl get -w`
2 parents 8f8d9e2 + 53e55d3 commit c082ace

File tree

9 files changed

+391
-106
lines changed

9 files changed

+391
-106
lines changed

pkg/kubectl/cmd/get/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ go_library(
2323
"get_flags.go",
2424
"humanreadable_flags.go",
2525
"sorter.go",
26+
"table_printer.go",
2627
],
2728
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/get",
2829
visibility = ["//visibility:public"],
@@ -40,6 +41,7 @@ go_library(
4041
"//staging/src/k8s.io/api/core/v1:go_default_library",
4142
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
4243
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
44+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
4345
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
4446
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
4547
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",

pkg/kubectl/cmd/get/get.go

Lines changed: 63 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
corev1 "k8s.io/api/core/v1"
3030
kapierrors "k8s.io/apimachinery/pkg/api/errors"
3131
"k8s.io/apimachinery/pkg/api/meta"
32+
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
3233
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3334
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3435
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
@@ -204,11 +205,11 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
204205
o.ExplicitNamespace = false
205206
}
206207

207-
isSorting, err := cmd.Flags().GetString("sort-by")
208+
sortBy, err := cmd.Flags().GetString("sort-by")
208209
if err != nil {
209210
return err
210211
}
211-
o.Sort = len(isSorting) > 0
212+
o.Sort = len(sortBy) > 0
212213

213214
o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers")
214215

@@ -253,12 +254,20 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
253254
return nil, err
254255
}
255256

256-
printer = maybeWrapSortingPrinter(printer, isSorting)
257+
if o.Sort {
258+
printer = &SortingPrinter{Delegate: printer, SortField: sortBy}
259+
}
260+
if o.ServerPrint {
261+
printer = &TablePrinter{Delegate: printer}
262+
}
257263
return printer.PrintObj, nil
258264
}
259265

260266
switch {
261267
case o.Watch || o.WatchOnly:
268+
if o.Sort {
269+
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch or --watch-only requested, --sort-by will be ignored\n")
270+
}
262271
default:
263272
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
264273
fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent))
@@ -271,6 +280,12 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
271280
return cmdutil.UsageErrorf(cmd, usageString)
272281
}
273282
}
283+
284+
// openapi printing is mutually exclusive with server side printing
285+
if o.PrintWithOpenAPICols && o.ServerPrint {
286+
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
287+
}
288+
274289
return nil
275290
}
276291

@@ -398,6 +413,27 @@ func NewRuntimeSorter(objects []runtime.Object, sortBy string) *RuntimeSorter {
398413
}
399414
}
400415

416+
func (o *GetOptions) transformRequests(req *rest.Request) {
417+
// We need full objects if printing with openapi columns
418+
if o.PrintWithOpenAPICols {
419+
return
420+
}
421+
if !o.ServerPrint || !o.IsHumanReadablePrinter {
422+
return
423+
}
424+
425+
group := metav1beta1.GroupName
426+
version := metav1beta1.SchemeGroupVersion.Version
427+
428+
tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
429+
req.SetHeader("Accept", tableParam)
430+
431+
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
432+
if o.Sort {
433+
req.Param("includeObject", "Object")
434+
}
435+
}
436+
401437
// Run performs the get operation.
402438
// TODO: remove the need to pass these arguments, like other commands.
403439
func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
@@ -408,11 +444,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
408444
return o.watch(f, cmd, args)
409445
}
410446

411-
// openapi printing is mutually exclusive with server side printing
412-
if o.PrintWithOpenAPICols && o.ServerPrint {
413-
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
414-
}
415-
416447
chunkSize := o.ChunkSize
417448
if o.Sort {
418449
// TODO(juanvallejo): in the future, we could have the client use chunking
@@ -432,26 +463,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
432463
ContinueOnError().
433464
Latest().
434465
Flatten().
435-
TransformRequests(func(req *rest.Request) {
436-
// We need full objects if printing with openapi columns
437-
if o.PrintWithOpenAPICols {
438-
return
439-
}
440-
if !o.ServerPrint || !o.IsHumanReadablePrinter {
441-
return
442-
}
443-
444-
group := metav1beta1.GroupName
445-
version := metav1beta1.SchemeGroupVersion.Version
446-
447-
tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
448-
req.SetHeader("Accept", tableParam)
449-
450-
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
451-
if o.Sort {
452-
req.Param("includeObject", "Object")
453-
}
454-
}).
466+
TransformRequests(o.transformRequests).
455467
Do()
456468

457469
if o.IgnoreNotFound {
@@ -475,17 +487,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
475487

476488
objs := make([]runtime.Object, len(infos))
477489
for ix := range infos {
478-
if o.ServerPrint {
479-
table, err := o.decodeIntoTable(infos[ix].Object)
480-
if err == nil {
481-
infos[ix].Object = table
482-
} else {
483-
// if we are unable to decode server response into a v1beta1.Table,
484-
// fallback to client-side printing with whatever info the server returned.
485-
klog.V(2).Infof("Unable to decode server response into a Table. Falling back to hardcoded types: %v", err)
486-
}
487-
}
488-
489490
objs[ix] = infos[ix].Object
490491
}
491492

@@ -505,8 +506,11 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
505506

506507
var printer printers.ResourcePrinter
507508
var lastMapping *meta.RESTMapping
508-
nonEmptyObjCount := 0
509-
w := utilprinters.GetNewTabWriter(o.Out)
509+
510+
// track if we write any output
511+
trackingWriter := &trackingWriterWrapper{Delegate: o.Out}
512+
513+
w := utilprinters.GetNewTabWriter(trackingWriter)
510514
for ix := range objs {
511515
var mapping *meta.RESTMapping
512516
var info *resource.Info
@@ -518,16 +522,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
518522
mapping = info.Mapping
519523
}
520524

521-
// if dealing with a table that has no rows, skip remaining steps
522-
// and avoid printing an unnecessary newline
523-
if table, isTable := info.Object.(*metav1beta1.Table); isTable {
524-
if len(table.Rows) == 0 {
525-
continue
526-
}
527-
}
528-
529-
nonEmptyObjCount++
530-
531525
printWithNamespace := o.AllNamespaces
532526

533527
if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot {
@@ -574,12 +568,23 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
574568
}
575569
}
576570
w.Flush()
577-
if nonEmptyObjCount == 0 && !o.IgnoreNotFound && len(allErrs) == 0 {
571+
if trackingWriter.Written == 0 && !o.IgnoreNotFound && len(allErrs) == 0 {
572+
// if we wrote no output, and had no errors, and are not ignoring NotFound, be sure we output something
578573
fmt.Fprintln(o.ErrOut, "No resources found.")
579574
}
580575
return utilerrors.NewAggregate(allErrs)
581576
}
582577

578+
type trackingWriterWrapper struct {
579+
Delegate io.Writer
580+
Written int
581+
}
582+
583+
func (t *trackingWriterWrapper) Write(p []byte) (n int, err error) {
584+
t.Written += len(p)
585+
return t.Delegate.Write(p)
586+
}
587+
583588
// raw makes a simple HTTP request to the provided path on the server using the default
584589
// credentials.
585590
func (o *GetOptions) raw(f cmdutil.Factory) error {
@@ -615,6 +620,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
615620
ResourceTypeOrNameArgs(true, args...).
616621
SingleResourceType().
617622
Latest().
623+
TransformRequests(o.transformRequests).
618624
Do()
619625
if err := r.Err(); err != nil {
620626
return err
@@ -655,6 +661,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
655661

656662
writer := utilprinters.GetNewTabWriter(o.Out)
657663

664+
tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind()
665+
658666
// print the current object
659667
if !o.WatchOnly {
660668
var objsToPrint []runtime.Object
@@ -665,8 +673,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
665673
objsToPrint = append(objsToPrint, obj)
666674
}
667675
for _, objToPrint := range objsToPrint {
668-
if o.IsHumanReadablePrinter {
669-
// printing always takes the internal version, but the watch event uses externals
676+
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
677+
// printing anything other than tables always takes the internal version, but the watch event uses externals
670678
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
671679
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
672680
}
@@ -698,7 +706,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
698706
// printing always takes the internal version, but the watch event uses externals
699707
// TODO fix printing to use server-side or be version agnostic
700708
objToPrint := e.Object
701-
if o.IsHumanReadablePrinter {
709+
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
702710
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
703711
objToPrint = attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV)
704712
}
@@ -723,35 +731,6 @@ func attemptToConvertToInternal(obj runtime.Object, converter runtime.ObjectConv
723731
return internalObject
724732
}
725733

726-
func (o *GetOptions) decodeIntoTable(obj runtime.Object) (runtime.Object, error) {
727-
if obj.GetObjectKind().GroupVersionKind().Kind != "Table" {
728-
return nil, fmt.Errorf("attempt to decode non-Table object into a v1beta1.Table")
729-
}
730-
731-
unstr, ok := obj.(*unstructured.Unstructured)
732-
if !ok {
733-
return nil, fmt.Errorf("attempt to decode non-Unstructured object")
734-
}
735-
table := &metav1beta1.Table{}
736-
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, table); err != nil {
737-
return nil, err
738-
}
739-
740-
for i := range table.Rows {
741-
row := &table.Rows[i]
742-
if row.Object.Raw == nil || row.Object.Object != nil {
743-
continue
744-
}
745-
converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw)
746-
if err != nil {
747-
return nil, err
748-
}
749-
row.Object.Object = converted
750-
}
751-
752-
return table, nil
753-
}
754-
755734
func (o *GetOptions) printGeneric(r *resource.Result) error {
756735
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
757736
// 1. if there is more than one item, combine them all into a single list
@@ -863,16 +842,6 @@ func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool {
863842
return cmdutil.GetFlagString(cmd, "output") != ""
864843
}
865844

866-
func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) printers.ResourcePrinter {
867-
if len(sortBy) != 0 {
868-
return &SortingPrinter{
869-
Delegate: printer,
870-
SortField: fmt.Sprintf("%s", sortBy),
871-
}
872-
}
873-
return printer
874-
}
875-
876845
func multipleGVKsRequested(infos []*resource.Info) bool {
877846
if len(infos) < 2 {
878847
return false

0 commit comments

Comments
 (0)