Skip to content

Commit

Permalink
handle Table response in client
Browse files Browse the repository at this point in the history
This patch adds support for the "server-side GET operation"
introduced by pull/40848 and proposed by kubernetes/community#363.
  • Loading branch information
juanvallejo committed Feb 23, 2018
1 parent b4fb4c7 commit 9946374
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 9 deletions.
2 changes: 2 additions & 0 deletions pkg/kubectl/cmd/resource/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)

Expand Down
95 changes: 87 additions & 8 deletions pkg/kubectl/cmd/resource/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
Expand Down Expand Up @@ -64,6 +66,9 @@ type GetOptions struct {
Namespace string
ExplicitNamespace bool

ServerPrint bool

Sort bool
IgnoreNotFound bool
ShowKind bool
LabelColumns []string
Expand Down Expand Up @@ -120,6 +125,7 @@ var (

const (
useOpenAPIPrintColumnFlagLabel = "use-openapi-print-columns"
useServerPrintColumns = "experimental-server-print"
)

// NewCmdGet creates a command object for the generic "get" action, which
Expand Down Expand Up @@ -158,6 +164,7 @@ func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comman
cmdutil.AddIncludeUninitializedFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
addOpenAPIPrintColumnFlags(cmd)
addServerPrintColumnFlags(cmd)
cmd.Flags().BoolVar(&options.ShowKind, "show-kind", options.ShowKind, "If present, list the resource type for the requested object(s).")
cmd.Flags().StringSliceVarP(&options.LabelColumns, "label-columns", "L", options.LabelColumns, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...")
cmd.Flags().BoolVar(&options.Export, "export", options.Export, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.")
Expand All @@ -175,6 +182,8 @@ func (options *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return nil
}

options.ServerPrint = cmdutil.GetFlagBool(cmd, useServerPrintColumns)

var err error
options.Namespace, options.ExplicitNamespace, err = f.DefaultNamespace()
if err != nil {
Expand All @@ -184,6 +193,12 @@ func (options *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
options.ExplicitNamespace = false
}

isSorting, err := cmd.Flags().GetString("sort-by")
if err != nil {
return err
}
options.Sort = len(isSorting) > 0

options.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)

switch {
Expand Down Expand Up @@ -238,6 +253,12 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
return options.watch(f, cmd, args)
}

printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces)
printer, err := cmdutil.PrinterForOptions(printOpts)
if err != nil {
return err
}

r := f.NewBuilder().
Unstructured().
NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces).
Expand All @@ -251,6 +272,15 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
ContinueOnError().
Latest().
Flatten().
TransformRequests(func(req *rest.Request) {
if options.ServerPrint && !printer.IsGeneric() && !options.Sort {
group := metav1beta1.GroupName
version := metav1beta1.SchemeGroupVersion.Version

tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
req.SetHeader("Accept", tableParam)
}
}).
Do()

if options.IgnoreNotFound {
Expand All @@ -260,12 +290,6 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
return err
}

printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces)
printer, err := cmdutil.PrinterForOptions(printOpts)
if err != nil {
return err
}

filterOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces)
filterFuncs := f.DefaultResourceFilterFunc()
if r.TargetsSingleItems() {
Expand All @@ -285,6 +309,17 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str

objs := make([]runtime.Object, len(infos))
for ix := range infos {
if options.ServerPrint {
table, err := options.decodeIntoTable(cmdutil.InternalVersionJSONEncoder(), infos[ix].Object)
if err == nil {
infos[ix].Object = table
} else {
// if we are unable to decode server response into a v1beta1.Table,
// fallback to client-side printing with whatever info the server returned.
glog.V(2).Infof("Unable to decode server response into a Table. Falling back to hardcoded types: %v", err)
}
}

objs[ix] = infos[ix].Object
}

Expand All @@ -293,7 +328,7 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
return err
}
var sorter *kubectl.RuntimeSort
if len(sorting) > 0 && len(objs) > 1 {
if options.Sort && len(objs) > 1 {
// TODO: questionable
if sorter, err = kubectl.SortObjects(cmdutil.InternalVersionDecoder(), objs, sorting); err != nil {
return err
Expand Down Expand Up @@ -324,6 +359,15 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
mapping = info.Mapping
original = info.Object
}

// if dealing with a table that has no rows, skip remaining steps
// and avoid printing an unnecessary newline
if table, isTable := info.Object.(*metav1beta1.Table); isTable {
if len(table.Rows) == 0 {
continue
}
}

if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) {
if printer != nil {
w.Flush()
Expand Down Expand Up @@ -416,7 +460,19 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
}
}
w.Flush()
cmdutil.PrintFilterCount(options.ErrOut, len(objs), filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound)
nonEmptyObjCount := 0
for _, obj := range objs {
if table, ok := obj.(*metav1beta1.Table); ok {
// exclude any Table objects with empty rows from our total object count
if len(table.Rows) == 0 {
continue
}
}

nonEmptyObjCount++
}

cmdutil.PrintFilterCount(options.ErrOut, nonEmptyObjCount, filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound)
return utilerrors.NewAggregate(allErrs)
}

Expand Down Expand Up @@ -592,6 +648,25 @@ func attemptToConvertToInternal(obj runtime.Object, converter runtime.ObjectConv
return internalObject
}

func (options *GetOptions) decodeIntoTable(encoder runtime.Encoder, obj runtime.Object) (runtime.Object, error) {
if obj.GetObjectKind().GroupVersionKind().Kind != "Table" {
return nil, fmt.Errorf("attempt to decode non-Table object into a v1beta1.Table")
}

b, err := runtime.Encode(encoder, obj)
if err != nil {
return nil, err
}

table := &metav1beta1.Table{}
err = json.Unmarshal(b, table)
if err != nil {
return nil, err
}

return table, nil
}

func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *resource.Result, filterFuncs kubectl.Filters, filterOpts *printers.PrintOptions) error {
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
// 1. if there is more than one item, combine them all into a single list
Expand Down Expand Up @@ -689,6 +764,10 @@ func addOpenAPIPrintColumnFlags(cmd *cobra.Command) {
cmd.Flags().Bool(useOpenAPIPrintColumnFlagLabel, true, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
}

func addServerPrintColumnFlags(cmd *cobra.Command) {
cmd.Flags().Bool(useServerPrintColumns, false, "If true, have the server return the appropriate table output. Supports extension APIs and CRD. Experimental.")
}

func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool {
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/printers/humanreadable.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ func hasCondition(conditions []metav1beta1.TableRowCondition, t metav1beta1.RowC
// DecorateTable if you receive a table from a remote server before calling PrintTable.
func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions) error {
if !options.NoHeaders {
// avoid printing headers if we have no rows to display
if len(table.Rows) == 0 {
return nil
}

first := true
for _, column := range table.ColumnDefinitions {
if !options.Wide && column.Priority != 0 {
Expand Down Expand Up @@ -413,7 +418,6 @@ func DecorateTable(table *metav1beta1.Table, options PrintOptions) error {
for i := range columns {
if columns[i].Format == "name" && columns[i].Type == "string" {
nameColumn = i
fmt.Printf("found name column: %d\n", i)
break
}
}
Expand Down

0 comments on commit 9946374

Please sign in to comment.