From df6a7ea6547175039c655a1b3236815b807f66fe Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Thu, 19 Apr 2018 17:56:34 -0400 Subject: [PATCH 1/2] move "get" cmd pieces to cmd/get --- pkg/kubectl/cmd/{resource => get}/get.go | 0 pkg/kubectl/cmd/{resource => get}/get_test.go | 0 pkg/{printers => kubectl/cmd/get}/humanreadable_flags.go | 0 pkg/{printers => kubectl/cmd/get}/humanreadable_flags_test.go | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename pkg/kubectl/cmd/{resource => get}/get.go (100%) rename pkg/kubectl/cmd/{resource => get}/get_test.go (100%) rename pkg/{printers => kubectl/cmd/get}/humanreadable_flags.go (100%) rename pkg/{printers => kubectl/cmd/get}/humanreadable_flags_test.go (100%) diff --git a/pkg/kubectl/cmd/resource/get.go b/pkg/kubectl/cmd/get/get.go similarity index 100% rename from pkg/kubectl/cmd/resource/get.go rename to pkg/kubectl/cmd/get/get.go diff --git a/pkg/kubectl/cmd/resource/get_test.go b/pkg/kubectl/cmd/get/get_test.go similarity index 100% rename from pkg/kubectl/cmd/resource/get_test.go rename to pkg/kubectl/cmd/get/get_test.go diff --git a/pkg/printers/humanreadable_flags.go b/pkg/kubectl/cmd/get/humanreadable_flags.go similarity index 100% rename from pkg/printers/humanreadable_flags.go rename to pkg/kubectl/cmd/get/humanreadable_flags.go diff --git a/pkg/printers/humanreadable_flags_test.go b/pkg/kubectl/cmd/get/humanreadable_flags_test.go similarity index 100% rename from pkg/printers/humanreadable_flags_test.go rename to pkg/kubectl/cmd/get/humanreadable_flags_test.go From 191a48f4c3cddbba5669b2f4412508c581305c38 Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Thu, 19 Apr 2018 17:57:09 -0400 Subject: [PATCH 2/2] wire PrintFlags through get cmd --- build/visible_to/BUILD | 15 +- hack/.golint_failures | 1 + hack/make-rules/test-cmd-util.sh | 2 +- pkg/kubectl/cmd/BUILD | 6 +- pkg/kubectl/cmd/cmd.go | 4 +- pkg/kubectl/cmd/cmd_test.go | 79 ----- pkg/kubectl/cmd/{resource => get}/BUILD | 44 ++- pkg/kubectl/cmd/get/get.go | 307 +++++++++--------- pkg/kubectl/cmd/get/get_flags.go | 169 ++++++++++ pkg/kubectl/cmd/get/get_test.go | 29 +- pkg/kubectl/cmd/get/humanreadable_flags.go | 44 ++- .../cmd/get/humanreadable_flags_test.go | 27 +- pkg/printers/BUILD | 4 - pkg/printers/customcolumn_flags.go | 21 +- pkg/printers/customcolumn_flags_test.go | 6 +- pkg/printers/internalversion/printers_test.go | 20 -- pkg/printers/printers.go | 26 +- 17 files changed, 425 insertions(+), 379 deletions(-) rename pkg/kubectl/cmd/{resource => get}/BUILD (90%) create mode 100644 pkg/kubectl/cmd/get/get_flags.go diff --git a/build/visible_to/BUILD b/build/visible_to/BUILD index 8bd49242a6..1e02a448b9 100644 --- a/build/visible_to/BUILD +++ b/build/visible_to/BUILD @@ -149,6 +149,13 @@ package_group( ], ) +package_group( + name = "pkg_kubectl_cmd_get_CONSUMERS", + packages = [ + "//pkg/kubectl/cmd", + ], +) + package_group( name = "pkg_kubectl_cmd_rollout_CONSUMERS", packages = [ @@ -177,7 +184,7 @@ package_group( "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", "//pkg/kubectl/cmd/create", - "//pkg/kubectl/cmd/resource", + "//pkg/kubectl/cmd/get", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/templates", @@ -199,7 +206,7 @@ package_group( "//pkg/kubectl/cmd", "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/create", - "//pkg/kubectl/cmd/resource", + "//pkg/kubectl/cmd/get", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/explain", @@ -233,7 +240,7 @@ package_group( "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", "//pkg/kubectl/cmd/create", - "//pkg/kubectl/cmd/resource", + "//pkg/kubectl/cmd/get", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/testing", @@ -303,7 +310,7 @@ package_group( "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", "//pkg/kubectl/cmd/create", - "//pkg/kubectl/cmd/resource", + "//pkg/kubectl/cmd/get", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/testing", diff --git a/hack/.golint_failures b/hack/.golint_failures index 9b5904c4b0..a98da4618c 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -139,6 +139,7 @@ pkg/kubectl/cmd pkg/kubectl/cmd/auth pkg/kubectl/cmd/config pkg/kubectl/cmd/create +pkg/kubectl/cmd/get pkg/kubectl/cmd/rollout pkg/kubectl/cmd/set pkg/kubectl/cmd/templates diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index e88f1ea912..1803757305 100755 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -227,7 +227,7 @@ function wait-for-pods-with-label() { local i for i in $(seq 1 10); do - kubeout=`kubectl get po -l $1 --template '{{range.items}}{{.metadata.name}}{{end}}' --sort-by metadata.name "${kube_flags[@]}"` + kubeout=`kubectl get po -l $1 --output=go-template --template='{{range.items}}{{.metadata.name}}{{end}}' --sort-by metadata.name "${kube_flags[@]}"` if [[ $kubeout = $2 ]]; then return fi diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 6c6037417e..cd03673de6 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -69,7 +69,7 @@ go_library( "//pkg/kubectl/cmd/auth:go_default_library", "//pkg/kubectl/cmd/config:go_default_library", "//pkg/kubectl/cmd/create:go_default_library", - "//pkg/kubectl/cmd/resource:go_default_library", + "//pkg/kubectl/cmd/get:go_default_library", "//pkg/kubectl/cmd/rollout:go_default_library", "//pkg/kubectl/cmd/scalejob:go_default_library", "//pkg/kubectl/cmd/set:go_default_library", @@ -190,7 +190,6 @@ go_test( "//pkg/apis/extensions:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/create:go_default_library", - "//pkg/kubectl/cmd/resource:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", @@ -201,7 +200,6 @@ go_test( "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/term:go_default_library", "//pkg/printers:go_default_library", - "//pkg/util/strings:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", @@ -249,7 +247,7 @@ filegroup( "//pkg/kubectl/cmd/auth:all-srcs", "//pkg/kubectl/cmd/config:all-srcs", "//pkg/kubectl/cmd/create:all-srcs", - "//pkg/kubectl/cmd/resource:all-srcs", + "//pkg/kubectl/cmd/get:all-srcs", "//pkg/kubectl/cmd/rollout:all-srcs", "//pkg/kubectl/cmd/scalejob:all-srcs", "//pkg/kubectl/cmd/set:all-srcs", diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 62094582f4..d99a768975 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -27,7 +27,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/auth" cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" "k8s.io/kubernetes/pkg/kubectl/cmd/create" - "k8s.io/kubernetes/pkg/kubectl/cmd/resource" + "k8s.io/kubernetes/pkg/kubectl/cmd/get" "k8s.io/kubernetes/pkg/kubectl/cmd/rollout" "k8s.io/kubernetes/pkg/kubectl/cmd/set" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -264,8 +264,8 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob { Message: "Basic Commands (Intermediate):", Commands: []*cobra.Command{ - resource.NewCmdGet(f, ioStreams), NewCmdExplain(f, ioStreams), + get.NewCmdGet(f, ioStreams), NewCmdEdit(f, ioStreams), NewCmdDelete(f, out, err), }, diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 74312869c4..1dfe229519 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -19,7 +19,6 @@ package cmd import ( "bytes" "encoding/json" - "fmt" "io" "io/ioutil" "net/http" @@ -35,18 +34,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" - "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/kubectl/cmd/resource" - cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" - "k8s.io/kubernetes/pkg/util/strings" ) // This init should be removed after switching this command and its tests to user external types. @@ -244,78 +237,6 @@ func newAllPhasePodList() *api.PodList { } } -func Example_printServiceWithLabels() { - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() - - ns := legacyscheme.Codecs - - tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, - Client: nil, - } - cmd := resource.NewCmdGet(tf, genericclioptions.NewTestIOStreamsDiscard()) - svc := &api.ServiceList{ - Items: []api.Service{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "svc1", - Namespace: "ns1", - CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, - Labels: map[string]string{ - "l1": "value", - }, - }, - Spec: api.ServiceSpec{ - Ports: []api.ServicePort{ - {Protocol: "UDP", Port: 53}, - {Protocol: "TCP", Port: 53}, - }, - Selector: map[string]string{ - "s": "magic", - }, - ClusterIP: "10.1.1.1", - Type: api.ServiceTypeClusterIP, - }, - Status: api.ServiceStatus{}, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "svc2", - Namespace: "ns2", - CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, - Labels: map[string]string{ - "l1": "dolla-bill-yall", - }, - }, - Spec: api.ServiceSpec{ - Ports: []api.ServicePort{ - {Protocol: "TCP", Port: 80}, - {Protocol: "TCP", Port: 8080}, - }, - Selector: map[string]string{ - "s": "kazam", - }, - ClusterIP: "10.1.1.2", - Type: api.ServiceTypeClusterIP, - }, - Status: api.ServiceStatus{}, - }}, - } - ld := strings.NewLineDelimiter(os.Stdout, "|") - defer ld.Flush() - cmd.Flags().Set("label-columns", "l1") - err := cmdutil.PrintObject(cmd, svc, ld) - if err != nil { - fmt.Printf("Unexpected error: %v", err) - } - // Output: - // |NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE L1| - // |svc1 ClusterIP 10.1.1.1 53/UDP,53/TCP 10y value| - // |svc2 ClusterIP 10.1.1.2 80/TCP,8080/TCP 10y dolla-bill-yall| - // || -} - func TestNormalizationFuncGlobalExistence(t *testing.T) { // This test can be safely deleted when we will not support multiple flag formats root := NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr) diff --git a/pkg/kubectl/cmd/resource/BUILD b/pkg/kubectl/cmd/get/BUILD similarity index 90% rename from pkg/kubectl/cmd/resource/BUILD rename to pkg/kubectl/cmd/get/BUILD index 927fd5f278..c36439140b 100644 --- a/pkg/kubectl/cmd/resource/BUILD +++ b/pkg/kubectl/cmd/get/BUILD @@ -1,9 +1,27 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + go_library( name = "go_default_library", - srcs = ["get.go"], - importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/resource", + srcs = [ + "get.go", + "get_flags.go", + "humanreadable_flags.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/get", visibility = ["//visibility:public"], deps = [ "//pkg/apis/core:go_default_library", @@ -13,8 +31,10 @@ go_library( "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/printers:go_default_library", + "//pkg/printers/internalversion:go_default_library", "//pkg/util/interrupt:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", @@ -34,7 +54,10 @@ go_library( go_test( name = "go_default_test", - srcs = ["get_test.go"], + srcs = [ + "get_test.go", + "humanreadable_flags_test.go", + ], data = [ "//api/openapi-spec:swagger-spec", "//test/e2e/testing-manifests:all-srcs", @@ -53,6 +76,7 @@ go_test( "//pkg/kubectl/cmd/util/openapi/testing:go_default_library", "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/scheme:go_default_library", + "//pkg/printers: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/runtime:go_default_library", @@ -68,17 +92,3 @@ go_test( "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/kubectl/cmd/get/get.go b/pkg/kubectl/cmd/get/get.go index d488e24b7f..d23cccae0c 100644 --- a/pkg/kubectl/cmd/get/get.go +++ b/pkg/kubectl/cmd/get/get.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package get import ( "encoding/json" @@ -52,6 +52,11 @@ import ( // GetOptions contains the input to the get command. type GetOptions struct { + PrintFlags *PrintFlags + ToPrinter func(*meta.RESTMapping, bool) (printers.ResourcePrinterFunc, error) + IsGeneric bool + PrintWithOpenAPICols bool + resource.FilenameOptions Raw string @@ -67,10 +72,9 @@ type GetOptions struct { ServerPrint bool + NoHeaders bool Sort bool IgnoreNotFound bool - ShowKind bool - LabelColumns []string Export bool IncludeUninitialized bool @@ -129,7 +133,8 @@ const ( // NewGetOptions returns a GetOptions with default chunk size 500. func NewGetOptions(streams genericclioptions.IOStreams) *GetOptions { return &GetOptions{ - ChunkSize: 500, + PrintFlags: NewGetPrintFlags(), + ChunkSize: 500, IOStreams: streams, } @@ -138,7 +143,7 @@ func NewGetOptions(streams genericclioptions.IOStreams) *GetOptions { // NewCmdGet creates a command object for the generic "get" action, which // retrieves one or more resources from a server. func NewCmdGet(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { - options := NewGetOptions(streams) + o := NewGetOptions(streams) validArgs := cmdutil.ValidArgList(f) cmd := &cobra.Command{ @@ -148,77 +153,112 @@ func NewCmdGet(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Co Long: getLong + "\n\n" + cmdutil.ValidResourceTypeList(f), Example: getExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(f, cmd, args)) - cmdutil.CheckErr(options.Validate(cmd)) - cmdutil.CheckErr(options.Run(f, cmd, args)) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd)) + cmdutil.CheckErr(o.Run(f, cmd, args)) }, SuggestFor: []string{"list", "ps"}, ValidArgs: validArgs, ArgAliases: kubectl.ResourceAliases(validArgs), } - cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.") - cmd.Flags().BoolVarP(&options.Watch, "watch", "w", options.Watch, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.") - cmd.Flags().BoolVar(&options.WatchOnly, "watch-only", options.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") - cmd.Flags().Int64Var(&options.ChunkSize, "chunk-size", options.ChunkSize, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.") - cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", options.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") - cmd.Flags().StringVarP(&options.LabelSelector, "selector", "l", options.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmd.Flags().StringVar(&options.FieldSelector, "field-selector", options.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") - cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") + o.PrintFlags.AddFlags(cmd) + + cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.") + cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.") + cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") + cmd.Flags().Int64Var(&o.ChunkSize, "chunk-size", o.ChunkSize, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.") + cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") + cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") + cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") 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.") - cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to get from a server.") + cmd.Flags().BoolVar(&o.Export, "export", o.Export, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.") + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.") return cmd } // Complete takes the command arguments and factory and infers any remaining options. -func (options *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - if len(options.Raw) > 0 { +func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(o.Raw) > 0 { if len(args) > 0 { return fmt.Errorf("arguments may not be passed when --raw is specified") } return nil } - options.ServerPrint = cmdutil.GetFlagBool(cmd, useServerPrintColumns) + o.ServerPrint = cmdutil.GetFlagBool(cmd, useServerPrintColumns) var err error - options.Namespace, options.ExplicitNamespace, err = f.DefaultNamespace() + o.Namespace, o.ExplicitNamespace, err = f.DefaultNamespace() if err != nil { return err } - if options.AllNamespaces { - options.ExplicitNamespace = false + if o.AllNamespaces { + o.ExplicitNamespace = false } isSorting, err := cmd.Flags().GetString("sort-by") if err != nil { return err } - options.Sort = len(isSorting) > 0 + o.Sort = len(isSorting) > 0 + + o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers") // TODO (soltysh): currently we don't support sorting and custom columns // with server side print. So in these cases force the old behavior. outputOption := cmd.Flags().Lookup("output").Value.String() - if options.Sort && outputOption == "custom-columns" { - options.ServerPrint = false + if o.Sort && outputOption == "custom-columns" { + o.ServerPrint = false } - options.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false) + // obtain printer here in order to determine if we are + // printing humanreadable or generic output. + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.IsGeneric = printer.IsGeneric() + + o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false) + o.PrintWithOpenAPICols = cmdutil.GetFlagBool(cmd, useOpenAPIPrintColumnFlagLabel) + + o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinterFunc, error) { + // make a new copy of current flags / opts before mutating + printFlags := o.PrintFlags.Copy() + + if mapping != nil { + if !cmdSpecifiesOutputFmt(cmd) && o.PrintWithOpenAPICols { + if apiSchema, err := f.OpenAPISchema(); err == nil { + printFlags.UseOpenAPIColumns(apiSchema, mapping) + } + } + if resource.MultipleTypesRequested(args) { + printFlags.EnsureWithKind(mapping.GroupVersionKind.GroupKind()) + } + } + if withNamespace { + printFlags.EnsureWithNamespace() + } + + printer, err := printFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } switch { - case options.Watch || options.WatchOnly: + case o.Watch || o.WatchOnly: // include uninitialized objects when watching on a single object // unless explicitly set --include-uninitialized=false - options.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2) + o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2) default: - if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { - fmt.Fprintf(options.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.ValidResourceTypeList(f)) + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.ValidResourceTypeList(f)) fullCmdName := cmd.Parent().CommandPath() usageString := "Required resource not specified." if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { @@ -232,15 +272,15 @@ func (options *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args } // Validate checks the set of flags provided by the user. -func (options *GetOptions) Validate(cmd *cobra.Command) error { - if len(options.Raw) > 0 { - if options.Watch || options.WatchOnly || len(options.LabelSelector) > 0 || options.Export { +func (o *GetOptions) Validate(cmd *cobra.Command) error { + if len(o.Raw) > 0 { + if o.Watch || o.WatchOnly || len(o.LabelSelector) > 0 || o.Export { return fmt.Errorf("--raw may not be specified with other flags that filter the server request or alter the output") } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive") } - if _, err := url.ParseRequestURI(options.Raw); err != nil { + if _, err := url.ParseRequestURI(o.Raw); err != nil { return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err) } } @@ -255,35 +295,29 @@ func (options *GetOptions) Validate(cmd *cobra.Command) error { // Run performs the get operation. // TODO: remove the need to pass these arguments, like other commands. -func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - if len(options.Raw) > 0 { - return options.raw(f) +func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(o.Raw) > 0 { + return o.raw(f) } - if options.Watch || options.WatchOnly { - return options.watch(f, cmd, args) - } - - printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) - printer, err := cmdutil.PrinterForOptions(printOpts) - if err != nil { - return err + if o.Watch || o.WatchOnly { + return o.watch(f, cmd, args) } r := f.NewBuilder(). Unstructured(). - NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). - FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). - LabelSelectorParam(options.LabelSelector). - FieldSelectorParam(options.FieldSelector). - ExportParam(options.Export). - RequestChunksOf(options.ChunkSize). - IncludeUninitialized(options.IncludeUninitialized). + NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). + FilenameParam(o.ExplicitNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + FieldSelectorParam(o.FieldSelector). + ExportParam(o.Export). + RequestChunksOf(o.ChunkSize). + IncludeUninitialized(o.IncludeUninitialized). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest(). Flatten(). TransformRequests(func(req *rest.Request) { - if options.ServerPrint && !printer.IsGeneric() && !options.Sort { + if o.ServerPrint && !o.IsGeneric && !o.Sort { group := metav1beta1.GroupName version := metav1beta1.SchemeGroupVersion.Version @@ -293,15 +327,15 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str }). Do() - if options.IgnoreNotFound { + if o.IgnoreNotFound { r.IgnoreErrors(kapierrors.IsNotFound) } if err := r.Err(); err != nil { return err } - if printer.IsGeneric() { - return options.printGeneric(printer, r) + if o.IsGeneric { + return o.printGeneric(r) } allErrs := []error{} @@ -313,8 +347,8 @@ 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 o.ServerPrint { + table, err := o.decodeIntoTable(cmdutil.InternalVersionJSONEncoder(), infos[ix].Object) if err == nil { infos[ix].Object = table } else { @@ -332,35 +366,26 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str return err } var sorter *kubectl.RuntimeSort - if options.Sort && len(objs) > 1 { + if o.Sort && len(objs) > 1 { // TODO: questionable if sorter, err = kubectl.SortObjects(cmdutil.InternalVersionDecoder(), objs, sorting); err != nil { return err } } - // use the default printer for each object - printer = nil + var printer printers.ResourcePrinter var lastMapping *meta.RESTMapping - w := printers.GetNewTabWriter(options.Out) - - useOpenAPIPrintColumns := cmdutil.GetFlagBool(cmd, useOpenAPIPrintColumnFlagLabel) - - showKind := options.ShowKind || resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) - - noHeaders := cmdutil.GetFlagBool(cmd, "no-headers") + nonEmptyObjCount := 0 + w := printers.GetNewTabWriter(o.Out) for ix := range objs { var mapping *meta.RESTMapping - var original runtime.Object var info *resource.Info if sorter != nil { info = infos[sorter.OriginalPosition(ix)] mapping = info.Mapping - original = info.Object } else { info = infos[ix] mapping = info.Mapping - original = info.Object } // if dealing with a table that has no rows, skip remaining steps @@ -371,29 +396,24 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str } } + nonEmptyObjCount++ + + printWithNamespace := o.AllNamespaces + if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot { + printWithNamespace = false + } + if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) { - if printer != nil { - w.Flush() + w.Flush() + + // TODO: this doesn't belong here + // add linebreak between resource groups (if there is more than one) + // skip linebreak above first resource group + if lastMapping != nil && !o.NoHeaders { + fmt.Fprintln(o.ErrOut) } - printWithNamespace := options.AllNamespaces - if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot { - printWithNamespace = false - } - - printOpts := cmdutil.ExtractCmdPrintOptions(cmd, printWithNamespace) - // if cmd does not specify output format and useOpenAPIPrintColumnFlagLabel flag is true, - // then get the default output options for this mapping from OpenAPI schema. - if !cmdSpecifiesOutputFmt(cmd) && useOpenAPIPrintColumns { - updatePrintOptionsForOpenAPI(f, mapping, printOpts) - } - - if showKind && mapping != nil { - printOpts.WithKind = true - printOpts.Kind = mapping.GroupVersionKind.GroupKind() - } - - printer, err = cmdutil.PrinterForOptions(printOpts) + printer, err = o.ToPrinter(mapping, printWithNamespace) if err != nil { if !errs.Has(err.Error()) { errs.Insert(err.Error()) @@ -402,66 +422,33 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str continue } - // TODO: this doesn't belong here - // add linebreak between resource groups (if there is more than one) - // skip linebreak above first resource group - if lastMapping != nil && !noHeaders { - fmt.Fprintf(options.ErrOut, "%s\n", "") - } - lastMapping = mapping } - typedObj := info.AsInternal() - - objToPrint := typedObj - if printer.IsGeneric() { - // use raw object as received from the builder when using generic - // printer instead of decodedObj - objToPrint = original - } - if err := printer.PrintObj(objToPrint, w); err != nil { - if !errs.Has(err.Error()) { - errs.Insert(err.Error()) - allErrs = append(allErrs, err) - } - continue - } + printer.PrintObj(info.AsInternal(), w) } w.Flush() - 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++ - } - - if nonEmptyObjCount == 0 && !options.IgnoreNotFound { - fmt.Fprintln(options.ErrOut, "No resources found.") + if nonEmptyObjCount == 0 && !o.IgnoreNotFound { + fmt.Fprintln(o.ErrOut, "No resources found.") } return utilerrors.NewAggregate(allErrs) } // raw makes a simple HTTP request to the provided path on the server using the default // credentials. -func (options *GetOptions) raw(f cmdutil.Factory) error { +func (o *GetOptions) raw(f cmdutil.Factory) error { restClient, err := f.RESTClient() if err != nil { return err } - stream, err := restClient.Get().RequestURI(options.Raw).Stream() + stream, err := restClient.Get().RequestURI(o.Raw).Stream() if err != nil { return err } defer stream.Close() - _, err = io.Copy(options.Out, stream) + _, err = io.Copy(o.Out, stream) if err != nil && err != io.EOF { return err } @@ -470,16 +457,16 @@ func (options *GetOptions) raw(f cmdutil.Factory) error { // watch starts a client-side watch of one or more resources. // TODO: remove the need for arguments here. -func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string) error { +func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string) error { r := f.NewBuilder(). Unstructured(). - NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). - FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). - LabelSelectorParam(options.LabelSelector). - FieldSelectorParam(options.FieldSelector). - ExportParam(options.Export). - RequestChunksOf(options.ChunkSize). - IncludeUninitialized(options.IncludeUninitialized). + NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). + FilenameParam(o.ExplicitNamespace, &o.FilenameOptions). + LabelSelectorParam(o.LabelSelector). + FieldSelectorParam(o.FieldSelector). + ExportParam(o.Export). + RequestChunksOf(o.ChunkSize). + IncludeUninitialized(o.IncludeUninitialized). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Latest(). @@ -514,8 +501,7 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s info := infos[0] mapping := info.ResourceMapping() - printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) - printer, err := cmdutil.PrinterForOptions(printOpts) + printer, err := o.ToPrinter(mapping, o.AllNamespaces) if err != nil { return err } @@ -540,9 +526,9 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s } // print the current object - if !options.WatchOnly { + if !o.WatchOnly { var objsToPrint []runtime.Object - writer := printers.GetNewTabWriter(options.Out) + writer := printers.GetNewTabWriter(o.Out) if isList { objsToPrint, _ = meta.ExtractList(obj) @@ -550,10 +536,12 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s objsToPrint = append(objsToPrint, obj) } for _, objToPrint := range objsToPrint { - // printing always takes the internal version, but the watch event uses externals - // TODO fix printing to use server-side or be version agnostic - internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion() - if err := printer.PrintObj(attemptToConvertToInternal(objToPrint, mapping, internalGV), writer); err != nil { + if !o.IsGeneric { + // printing always takes the internal version, but the watch event uses externals + internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion() + objToPrint = attemptToConvertToInternal(objToPrint, mapping, internalGV) + } + if err := printer.PrintObj(objToPrint, writer); err != nil { return fmt.Errorf("unable to output the provided object: %v", err) } } @@ -579,7 +567,7 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s // printing always takes the internal version, but the watch event uses externals // TODO fix printing to use server-side or be version agnostic internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion() - if err := printer.PrintObj(attemptToConvertToInternal(e.Object, mapping, internalGV), options.Out); err != nil { + if err := printer.PrintObj(attemptToConvertToInternal(e.Object, mapping, internalGV), o.Out); err != nil { return false, err } return false, nil @@ -599,7 +587,7 @@ func attemptToConvertToInternal(obj runtime.Object, converter runtime.ObjectConv return internalObject } -func (options *GetOptions) decodeIntoTable(encoder runtime.Encoder, obj runtime.Object) (runtime.Object, error) { +func (o *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") } @@ -618,7 +606,7 @@ func (options *GetOptions) decodeIntoTable(encoder runtime.Encoder, obj runtime. return table, nil } -func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *resource.Result) error { +func (o *GetOptions) printGeneric(r *resource.Result) 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 // 2. if there is a single item and that item is a list, leave it as its specific list @@ -633,10 +621,15 @@ func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *res errs = append(errs, err) } - if len(infos) == 0 && options.IgnoreNotFound { + if len(infos) == 0 && o.IgnoreNotFound { return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } + printer, err := o.ToPrinter(nil, false) + if err != nil { + return err + } + var obj runtime.Object if !singleItemImplied || len(infos) > 1 { // we have more than one item, so coerce all items into a list. @@ -694,13 +687,13 @@ func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *res for _, item := range items { list.Items = append(list.Items, *item.(*unstructured.Unstructured)) } - if err := printer.PrintObj(list, options.Out); err != nil { + if err := printer.PrintObj(list, o.Out); err != nil { errs = append(errs, err) } return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } - if printErr := printer.PrintObj(obj, options.Out); printErr != nil { + if printErr := printer.PrintObj(obj, o.Out); printErr != nil { errs = append(errs, printErr) } diff --git a/pkg/kubectl/cmd/get/get_flags.go b/pkg/kubectl/cmd/get/get_flags.go new file mode 100644 index 0000000000..4349e79d06 --- /dev/null +++ b/pkg/kubectl/cmd/get/get_flags.go @@ -0,0 +1,169 @@ +/* +Copyright 2018 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 ( + "strings" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kubernetes/pkg/printers" +) + +// PrintFlags composes common printer flag structs +// used in the Get command. +type PrintFlags struct { + JSONYamlPrintFlags *printers.JSONYamlPrintFlags + NamePrintFlags *printers.NamePrintFlags + TemplateFlags *printers.KubeTemplatePrintFlags + CustomColumnsFlags *printers.CustomColumnsPrintFlags + HumanReadableFlags *HumanPrintFlags + + NoHeaders *bool + OutputFormat *string +} + +// EnsureWithNamespace ensures that humanreadable flags return +// a printer capable of printing with a "namespace" column. +func (f *PrintFlags) EnsureWithNamespace() error { + return f.HumanReadableFlags.EnsureWithNamespace() +} + +// EnsureWithKind ensures that humanreadable flags return +// a printer capable of including resource kinds. +func (f *PrintFlags) EnsureWithKind(kind schema.GroupKind) error { + return f.HumanReadableFlags.EnsureWithKind(kind) +} + +// Copy returns a copy of PrintFlags for mutation +func (f *PrintFlags) Copy() PrintFlags { + printFlags := *f + return printFlags +} + +// UseOpenAPIColumns modifies the output format, as well as the +// "allowMissingKeys" option for template printers, to values +// defined in the OpenAPI schema of a resource. +func (f *PrintFlags) UseOpenAPIColumns(api openapi.Resources, mapping *meta.RESTMapping) error { + // Found openapi metadata for this resource + schema := api.LookupResource(mapping.GroupVersionKind) + if schema == nil { + // Schema not found, return empty columns + return nil + } + + columns, found := openapi.GetPrintColumns(schema.GetExtensions()) + if !found { + // Extension not found, return empty columns + return nil + } + + parts := strings.SplitN(columns, "=", 2) + if len(parts) < 2 { + return nil + } + + allowMissingKeys := true + f.OutputFormat = &parts[0] + f.TemplateFlags.TemplateArgument = &parts[1] + f.TemplateFlags.AllowMissingKeys = &allowMissingKeys + return nil +} + +// ToPrinter attempts to find a composed set of PrintFlags suitable for +// returning a printer based on current flag values. +func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) { + outputFormat := "" + if f.OutputFormat != nil { + outputFormat = *f.OutputFormat + } + + noHeaders := false + if f.NoHeaders != nil { + noHeaders = *f.NoHeaders + } + f.HumanReadableFlags.NoHeaders = noHeaders + f.CustomColumnsFlags.NoHeaders = noHeaders + + if f.TemplateFlags.TemplateArgument != nil { + f.CustomColumnsFlags.TemplateArgument = *f.TemplateFlags.TemplateArgument + } + + if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.HumanReadableFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.TemplateFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.CustomColumnsFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + return nil, printers.NoCompatiblePrinterError{Options: f} +} + +// AddFlags receives a *cobra.Command reference and binds +// flags related to humanreadable and template printing. +func (f *PrintFlags) AddFlags(cmd *cobra.Command) { + f.JSONYamlPrintFlags.AddFlags(cmd) + f.NamePrintFlags.AddFlags(cmd) + f.TemplateFlags.AddFlags(cmd) + f.HumanReadableFlags.AddFlags(cmd) + f.CustomColumnsFlags.AddFlags(cmd) + + if f.OutputFormat != nil { + cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].") + } + if f.NoHeaders != nil { + cmd.Flags().BoolVar(f.NoHeaders, "no-headers", *f.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).") + } + + // TODO(juanvallejo): This is deprecated - remove + cmd.Flags().BoolP("show-all", "a", true, "When printing, show all resources (default show all pods including terminated one.)") + cmd.Flags().MarkDeprecated("show-all", "will be removed in an upcoming release") +} + +// NewGetPrintFlags returns flags associated with humanreadable, +// template, and "name" printing, with default values set. +func NewGetPrintFlags() *PrintFlags { + outputFormat := "" + noHeaders := false + + return &PrintFlags{ + OutputFormat: &outputFormat, + NoHeaders: &noHeaders, + + JSONYamlPrintFlags: printers.NewJSONYamlPrintFlags(), + NamePrintFlags: printers.NewNamePrintFlags(""), + TemplateFlags: printers.NewKubeTemplatePrintFlags(), + HumanReadableFlags: NewHumanPrintFlags(), + CustomColumnsFlags: printers.NewCustomColumnsPrintFlags(), + } +} diff --git a/pkg/kubectl/cmd/get/get_test.go b/pkg/kubectl/cmd/get/get_test.go index 7a4f0fc71a..a7844fcd8a 100644 --- a/pkg/kubectl/cmd/get/get_test.go +++ b/pkg/kubectl/cmd/get/get_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package get import ( "bytes" @@ -513,7 +513,6 @@ func TestGetAllListObjects(t *testing.T) { streams, _, buf, _ := genericclioptions.NewTestIOStreams() cmd := NewCmdGet(tf, streams) cmd.SetOutput(buf) - cmd.Flags().Set("show-all", "true") cmd.Run(cmd, []string{"pods"}) expected := `NAME READY STATUS RESTARTS AGE @@ -889,32 +888,6 @@ node/foo Unknown } } -func TestGetByFormatForcesFlag(t *testing.T) { - pods, _, _ := testData() - - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() - codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...) - - tf.UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: unstructuredSerializer, - Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, - } - tf.Namespace = "test" - - streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) - cmd.SetOutput(buf) - cmd.Flags().Lookup("output").Value.Set("yaml") - cmd.Flags().Set("show-all", "false") - cmd.Run(cmd, []string{"pods"}) - - showAllFlag, _ := cmd.Flags().GetBool("show-all") - if showAllFlag { - t.Error("expected showAll to not be true when getting resource") - } -} - func watchTestData() ([]api.Pod, []watch.Event) { pods := []api.Pod{ { diff --git a/pkg/kubectl/cmd/get/humanreadable_flags.go b/pkg/kubectl/cmd/get/humanreadable_flags.go index 7b657f5008..54c9aae804 100644 --- a/pkg/kubectl/cmd/get/humanreadable_flags.go +++ b/pkg/kubectl/cmd/get/humanreadable_flags.go @@ -14,13 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package printers +package get import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/kubectl/scheme" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" ) // HumanPrintFlags provides default flags necessary for printing. @@ -40,11 +42,28 @@ type HumanPrintFlags struct { WithNamespace bool } +// EnsureWithKind sets the provided GroupKind humanreadable value. +// If the kind received is non-empty, the "showKind" humanreadable +// printer option is set to true. +func (f *HumanPrintFlags) EnsureWithKind(kind schema.GroupKind) error { + showKind := !kind.Empty() + + f.Kind = kind + f.ShowKind = &showKind + return nil +} + +// EnsureWithNamespace sets the "WithNamespace" humanreadable option to true. +func (f *HumanPrintFlags) EnsureWithNamespace() error { + f.WithNamespace = true + return nil +} + // ToPrinter receives an outputFormat and returns a printer capable of // handling human-readable output. -func (f *HumanPrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, bool, error) { +func (f *HumanPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) { if len(outputFormat) > 0 && outputFormat != "wide" { - return nil, false, nil + return nil, printers.NoCompatiblePrinterError{Options: f} } encoder := scheme.Codecs.LegacyCodec(scheme.Registry.RegisteredGroupVersions()...) @@ -65,7 +84,7 @@ func (f *HumanPrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, bool, columnLabels = *f.ColumnLabels } - p := NewHumanReadablePrinter(encoder, decoder, PrintOptions{ + p := printers.NewHumanReadablePrinter(encoder, decoder, printers.PrintOptions{ Kind: f.Kind, WithKind: showKind, NoHeaders: f.NoHeaders, @@ -74,14 +93,11 @@ func (f *HumanPrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, bool, ColumnLabels: columnLabels, ShowLabels: showLabels, }) - - // TODO(juanvallejo): enable this here once we wire commands to instantiate PrintFlags directly. - // PrintHandlers are currently added through cmd/util/printing.go#PrinterForOptions - //printersinternal.AddHandlers(p) + printersinternal.AddHandlers(p) // TODO(juanvallejo): handle sorting here - return p, true, nil + return p, nil } // AddFlags receives a *cobra.Command reference and binds @@ -103,19 +119,19 @@ func (f *HumanPrintFlags) AddFlags(c *cobra.Command) { // NewHumanPrintFlags returns flags associated with // human-readable printing, with default values set. -func NewHumanPrintFlags(kind schema.GroupKind, noHeaders, withNamespace, absoluteTimestamps bool) *HumanPrintFlags { +func NewHumanPrintFlags() *HumanPrintFlags { showLabels := false sortBy := "" showKind := false columnLabels := []string{} return &HumanPrintFlags{ - NoHeaders: noHeaders, - WithNamespace: withNamespace, - AbsoluteTimestamps: absoluteTimestamps, + NoHeaders: false, + WithNamespace: false, + AbsoluteTimestamps: false, ColumnLabels: &columnLabels, - Kind: kind, + Kind: schema.GroupKind{}, ShowLabels: &showLabels, SortBy: &sortBy, ShowKind: &showKind, diff --git a/pkg/kubectl/cmd/get/humanreadable_flags_test.go b/pkg/kubectl/cmd/get/humanreadable_flags_test.go index f584bf26b6..a9d03d7e3a 100644 --- a/pkg/kubectl/cmd/get/humanreadable_flags_test.go +++ b/pkg/kubectl/cmd/get/humanreadable_flags_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package printers_test +package get import ( "bytes" @@ -26,11 +26,15 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" - printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" ) func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) { - testObject := &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + testObject := &api.Pod{ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "l1": "value", + }, + }} testCases := []struct { name string @@ -75,6 +79,11 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) { showKind: true, expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +\n", }, + { + name: "label-columns prints specified label values in new column", + columnLabels: []string{"l1"}, + expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +\\ +value\n", + }, { name: "withNamespace displays an additional NAMESPACE column", withNamespace: true, @@ -94,7 +103,7 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - printFlags := printers.HumanPrintFlags{ + printFlags := HumanPrintFlags{ ShowKind: &tc.showKind, ShowLabels: &tc.showLabels, SortBy: &tc.sortBy, @@ -108,14 +117,14 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) { printFlags.Kind = schema.GroupKind{Kind: "pod"} } - p, matched, err := printFlags.ToPrinter(tc.outputFormat) + p, err := printFlags.ToPrinter(tc.outputFormat) if tc.expectNoMatch { - if matched { + if !printers.IsNoCompatiblePrinterError(err) { t.Fatalf("expected no printer matches for output format %q", tc.outputFormat) } return } - if !matched { + if printers.IsNoCompatiblePrinterError(err) { t.Fatalf("expected to match template printer for output format %q", tc.outputFormat) } @@ -129,10 +138,6 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - // TODO(juanvallejo): remove this once we wire PrintFlags at the command level. - // handlers should be attached to the printer inside of the ToPrinter method. - printersinternal.AddHandlers(p.(*printers.HumanReadablePrinter)) - out := bytes.NewBuffer([]byte{}) err = p.PrintObj(testObject, out) if err != nil { diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 7adeeb0815..578a62d0b1 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -13,7 +13,6 @@ go_library( "customcolumn_flags.go", "flags.go", "humanreadable.go", - "humanreadable_flags.go", "interface.go", "json.go", "json_yaml_flags.go", @@ -53,7 +52,6 @@ go_test( srcs = [ "customcolumn_flags_test.go", "customcolumn_test.go", - "humanreadable_flags_test.go", "json_yaml_flags_test.go", "jsonpath_flags_test.go", "name_flags_test.go", @@ -63,11 +61,9 @@ go_test( ":go_default_library", "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/core:go_default_library", - "//pkg/printers/internalversion:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], ) diff --git a/pkg/printers/customcolumn_flags.go b/pkg/printers/customcolumn_flags.go index e1424f5331..b5cc54f70b 100644 --- a/pkg/printers/customcolumn_flags.go +++ b/pkg/printers/customcolumn_flags.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -37,9 +38,9 @@ type CustomColumnsPrintFlags struct { // handling custom-column printing. // Returns false if the specified templateFormat does not match a supported format. // Supported format types can be found in pkg/printers/printers.go -func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, bool, error) { +func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) { if len(templateFormat) == 0 { - return nil, false, fmt.Errorf("missing output format") + return nil, NoCompatiblePrinterError{} } templateValue := "" @@ -63,11 +64,11 @@ func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (ResourcePrin } if _, supportedFormat := supportedFormats[templateFormat]; !supportedFormat { - return nil, false, nil + return nil, NoCompatiblePrinterError{} } if len(templateValue) == 0 { - return nil, true, fmt.Errorf("custom-columns format specified but no custom columns given") + return nil, fmt.Errorf("custom-columns format specified but no custom columns given") } decoder := scheme.Codecs.UniversalDecoder() @@ -75,15 +76,15 @@ func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (ResourcePrin if templateFormat == "custom-columns-file" { file, err := os.Open(templateValue) if err != nil { - return nil, true, fmt.Errorf("error reading template %s, %v\n", templateValue, err) + return nil, fmt.Errorf("error reading template %s, %v\n", templateValue, err) } defer file.Close() p, err := NewCustomColumnsPrinterFromTemplate(file, decoder) - return p, true, err + return p, err } p, err := NewCustomColumnsPrinterFromSpec(templateValue, decoder, f.NoHeaders) - return p, true, err + return NewVersionedPrinter(p, legacyscheme.Scheme, legacyscheme.Scheme, scheme.Versions...), err } // AddFlags receives a *cobra.Command reference and binds @@ -93,9 +94,9 @@ func (f *CustomColumnsPrintFlags) AddFlags(c *cobra.Command) {} // NewCustomColumnsPrintFlags returns flags associated with // custom-column printing, with default values set. // NoHeaders and TemplateArgument should be set by callers. -func NewCustomColumnsPrintFlags(noHeaders bool, templateValue string) *CustomColumnsPrintFlags { +func NewCustomColumnsPrintFlags() *CustomColumnsPrintFlags { return &CustomColumnsPrintFlags{ - NoHeaders: noHeaders, - TemplateArgument: templateValue, + NoHeaders: false, + TemplateArgument: "", } } diff --git a/pkg/printers/customcolumn_flags_test.go b/pkg/printers/customcolumn_flags_test.go index 8f337ebf02..550d261fe1 100644 --- a/pkg/printers/customcolumn_flags_test.go +++ b/pkg/printers/customcolumn_flags_test.go @@ -98,14 +98,14 @@ func TestPrinterSupportsExpectedCustomColumnFormats(t *testing.T) { TemplateArgument: tc.templateArg, } - p, matched, err := printFlags.ToPrinter(tc.outputFormat) + p, err := printFlags.ToPrinter(tc.outputFormat) if tc.expectNoMatch { - if matched { + if !printers.IsNoCompatiblePrinterError(err) { t.Fatalf("expected no printer matches for output format %q", tc.outputFormat) } return } - if !matched { + if printers.IsNoCompatiblePrinterError(err) { t.Fatalf("expected to match template printer for output format %q", tc.outputFormat) } diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index fe9afba96c..81b0125d6f 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -99,26 +99,6 @@ func TestVersionedPrinter(t *testing.T) { } } -func TestPrintDefault(t *testing.T) { - printerTests := []struct { - Name string - Format string - }{ - {"test wide", "wide"}, - {"test blank format", ""}, - } - - for _, test := range printerTests { - printer, err := printers.GetStandardPrinter(nil, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.RegisteredGroupVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{AllowMissingKeys: false}) - if err != nil { - t.Errorf("in %s, unexpected error: %#v", test.Name, err) - } - if printer.IsGeneric() { - t.Errorf("in %s, printer should not be generic: %#v", test.Name, printer) - } - } -} - func TestPrintUnstructuredObject(t *testing.T) { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ diff --git a/pkg/printers/printers.go b/pkg/printers/printers.go index a11f3a1ad1..9daee38807 100644 --- a/pkg/printers/printers.go +++ b/pkg/printers/printers.go @@ -75,37 +75,13 @@ func GetStandardPrinter(typer runtime.ObjectTyper, encoder runtime.Encoder, deco NoHeaders: options.NoHeaders, TemplateArgument: formatArgument, } - customColumnsPrinter, matched, err := customColumnsFlags.ToPrinter(format) - if !matched { - return nil, fmt.Errorf("unable to match a name printer to handle current print options") - } + customColumnsPrinter, err := customColumnsFlags.ToPrinter(format) if err != nil { return nil, err } printer = customColumnsPrinter - case "wide": - fallthrough - case "": - humanPrintFlags := NewHumanPrintFlags(options.Kind, options.NoHeaders, options.WithNamespace, options.AbsoluteTimestamps) - - // TODO: these should be bound through a call to humanPrintFlags#AddFlags(cmd) once we instantiate PrintFlags at the command level - humanPrintFlags.ShowKind = &options.WithKind - humanPrintFlags.ShowLabels = &options.ShowLabels - humanPrintFlags.ColumnLabels = &options.ColumnLabels - humanPrintFlags.SortBy = &options.SortBy - - humanPrinter, matches, err := humanPrintFlags.ToPrinter(format) - if !matches { - return nil, fmt.Errorf("unable to match a printer to handle current print options") - } - if err != nil { - return nil, err - } - - printer = humanPrinter - default: return nil, fmt.Errorf("output format %q not recognized", format) }