From 5a34e4f594df5581ce4cb576689a0cca1264fed8 Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Tue, 1 May 2018 15:48:46 -0400 Subject: [PATCH] remove printer helpers --- pkg/kubectl/cmd/apiresources.go | 6 +- pkg/kubectl/cmd/apply_set_last_applied.go | 3 +- pkg/kubectl/cmd/apply_test.go | 6 +- pkg/kubectl/cmd/auth/BUILD | 2 + pkg/kubectl/cmd/auth/auth.go | 11 +- pkg/kubectl/cmd/auth/cani.go | 14 +- pkg/kubectl/cmd/auth/cani_test.go | 2 +- pkg/kubectl/cmd/auth/reconcile.go | 70 +++---- pkg/kubectl/cmd/certificates.go | 8 +- pkg/kubectl/cmd/cmd.go | 4 +- pkg/kubectl/cmd/config/BUILD | 3 + pkg/kubectl/cmd/config/config.go | 33 +-- pkg/kubectl/cmd/config/config_test.go | 7 +- pkg/kubectl/cmd/config/flags.go | 95 +++++++++ pkg/kubectl/cmd/config/get_contexts.go | 28 ++- pkg/kubectl/cmd/config/get_contexts_test.go | 6 +- pkg/kubectl/cmd/config/view.go | 108 +++++----- pkg/kubectl/cmd/config/view_test.go | 7 +- pkg/kubectl/cmd/delete.go | 7 +- pkg/kubectl/cmd/delete_test.go | 4 +- pkg/kubectl/cmd/replace.go | 10 +- pkg/kubectl/cmd/scale.go | 12 +- pkg/kubectl/cmd/util/printing.go | 210 -------------------- pkg/printers/flags.go | 12 +- 24 files changed, 286 insertions(+), 382 deletions(-) create mode 100644 pkg/kubectl/cmd/config/flags.go diff --git a/pkg/kubectl/cmd/apiresources.go b/pkg/kubectl/cmd/apiresources.go index b2ad4f0df9d..c427c2e438d 100644 --- a/pkg/kubectl/cmd/apiresources.go +++ b/pkg/kubectl/cmd/apiresources.go @@ -90,8 +90,10 @@ func NewCmdApiResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams cmdutil.CheckErr(o.RunApiResources(cmd, f)) }, } - cmdutil.AddOutputFlags(cmd) - cmdutil.AddNoHeadersFlags(cmd) + + cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).") + cmd.Flags().StringP("output", "o", "", "Output format. One of: wide|name.") + cmd.Flags().StringVar(&o.APIGroup, "api-group", "", "Limit to resources in the specified API group.") cmd.Flags().BoolVar(&o.Namespaced, "namespaced", true, "Namespaced indicates if a resource is namespaced or not.") cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.") diff --git a/pkg/kubectl/cmd/apply_set_last_applied.go b/pkg/kubectl/cmd/apply_set_last_applied.go index f2291807f21..feb18fbe499 100644 --- a/pkg/kubectl/cmd/apply_set_last_applied.go +++ b/pkg/kubectl/cmd/apply_set_last_applied.go @@ -102,8 +102,9 @@ func NewCmdApplySetLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IO }, } + o.PrintFlags.AddFlags(cmd) + cmdutil.AddDryRunFlag(cmd) - cmdutil.AddPrinterFlags(cmd) cmd.Flags().BoolVar(&o.CreateAnnotation, "create-annotation", o.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one") kubectl.AddJsonFilenameFlag(cmd, &o.FilenameOptions.Filenames, "Filename, directory, or URL to files that contains the last-applied-configuration annotations") diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index c7ebef5627d..7df056ddba1 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -1121,7 +1121,7 @@ func TestRunApplySetLastApplied(t *testing.T) { name: "set with exist object", filePath: filenameRC, expectedErr: "", - expectedOut: "replicationcontroller/test-rc configured\n", + expectedOut: "replicationcontroller/test-rc\n", output: "name", }, { @@ -1142,14 +1142,14 @@ func TestRunApplySetLastApplied(t *testing.T) { name: "set with exist object output json", filePath: filenameRCJSON, expectedErr: "", - expectedOut: "replicationcontroller/test-rc configured\n", + expectedOut: "replicationcontroller/test-rc\n", output: "name", }, { name: "set test for a directory of files", filePath: dirName, expectedErr: "", - expectedOut: "replicationcontroller/test-rc configured\nreplicationcontroller/test-rc configured\n", + expectedOut: "replicationcontroller/test-rc\nreplicationcontroller/test-rc\n", output: "name", }, } diff --git a/pkg/kubectl/cmd/auth/BUILD b/pkg/kubectl/cmd/auth/BUILD index 987683660a3..5f133df34af 100644 --- a/pkg/kubectl/cmd/auth/BUILD +++ b/pkg/kubectl/cmd/auth/BUILD @@ -24,7 +24,9 @@ go_library( "//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/printers:go_default_library", "//pkg/registry/rbac/reconciliation:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", diff --git a/pkg/kubectl/cmd/auth/auth.go b/pkg/kubectl/cmd/auth/auth.go index eb70d3005df..b48fe56a772 100644 --- a/pkg/kubectl/cmd/auth/auth.go +++ b/pkg/kubectl/cmd/auth/auth.go @@ -17,24 +17,23 @@ limitations under the License. package auth import ( - "io" - "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) -func NewCmdAuth(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { +func NewCmdAuth(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { // Parent command to which all subcommands are added. cmds := &cobra.Command{ Use: "auth", Short: "Inspect authorization", Long: `Inspect authorization`, - Run: cmdutil.DefaultSubCommandRun(errOut), + Run: cmdutil.DefaultSubCommandRun(streams.ErrOut), } - cmds.AddCommand(NewCmdCanI(f, out, errOut)) - cmds.AddCommand(NewCmdReconcile(f, out, errOut)) + cmds.AddCommand(NewCmdCanI(f, streams)) + cmds.AddCommand(NewCmdReconcile(f, streams)) return cmds } diff --git a/pkg/kubectl/cmd/auth/cani.go b/pkg/kubectl/cmd/auth/cani.go index d0269f2f8a9..88a93d8d229 100644 --- a/pkg/kubectl/cmd/auth/cani.go +++ b/pkg/kubectl/cmd/auth/cani.go @@ -19,12 +19,12 @@ package auth import ( "errors" "fmt" - "io" "io/ioutil" "os" "strings" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" @@ -48,8 +48,7 @@ type CanIOptions struct { Subresource string ResourceName string - Out io.Writer - Err io.Writer + genericclioptions.IOStreams } var ( @@ -81,10 +80,9 @@ var ( kubectl auth can-i get /logs/`) ) -func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command { +func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { o := &CanIOptions{ - Out: out, - Err: err, + IOStreams: streams, } cmd := &cobra.Command{ @@ -232,9 +230,9 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc gvr, err = mapper.ResourceFor(groupResource.WithVersion("")) if err != nil { if len(groupResource.Group) == 0 { - fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource) + fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource) } else { - fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group) + fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group) } return schema.GroupVersionResource{Resource: resourceArg} } diff --git a/pkg/kubectl/cmd/auth/cani_test.go b/pkg/kubectl/cmd/auth/cani_test.go index eb3cf9c5d84..84dc9826868 100644 --- a/pkg/kubectl/cmd/auth/cani_test.go +++ b/pkg/kubectl/cmd/auth/cani_test.go @@ -119,7 +119,7 @@ func TestRunAccessCheck(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { test.o.Out = ioutil.Discard - test.o.Err = ioutil.Discard + test.o.ErrOut = ioutil.Discard tf := cmdtesting.NewTestFactory() defer tf.Cleanup() diff --git a/pkg/kubectl/cmd/auth/reconcile.go b/pkg/kubectl/cmd/auth/reconcile.go index 26dd8069c4d..007c33b09a7 100644 --- a/pkg/kubectl/cmd/auth/reconcile.go +++ b/pkg/kubectl/cmd/auth/reconcile.go @@ -18,7 +18,6 @@ package auth import ( "errors" - "io" "github.com/golang/glog" "github.com/spf13/cobra" @@ -29,21 +28,25 @@ import ( internalrbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/registry/rbac/reconciliation" ) // ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of // referencing the cmd.Flags() type ReconcileOptions struct { + PrintFlags *printers.PrintFlags + FilenameOptions *resource.FilenameOptions + Visitor resource.Visitor RBACClient internalrbacclient.RbacInterface NamespaceClient internalcoreclient.NamespaceInterface - Print func(*resource.Info) error + PrintObject printers.ResourcePrinterFunc - Out io.Writer - Err io.Writer + genericclioptions.IOStreams } var ( @@ -57,12 +60,16 @@ var ( kubectl auth reconcile -f my-rbac-rules.yaml`) ) -func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command { - fileOptions := &resource.FilenameOptions{} - o := &ReconcileOptions{ - Out: out, - Err: err, +func NewReconcileOptions(ioStreams genericclioptions.IOStreams) *ReconcileOptions { + return &ReconcileOptions{ + FilenameOptions: &resource.FilenameOptions{}, + PrintFlags: printers.NewPrintFlags("reconciled"), + IOStreams: ioStreams, } +} + +func NewCmdReconcile(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := NewReconcileOptions(streams) cmd := &cobra.Command{ Use: "reconcile -f FILENAME", @@ -71,21 +78,21 @@ func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command { Long: reconcileLong, Example: reconcileExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Complete(cmd, f, args, fileOptions)) + cmdutil.CheckErr(o.Complete(cmd, f, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunReconcile()) }, } - cmdutil.AddPrinterFlags(cmd) - usage := "identifying the resource to reconcile." - cmdutil.AddFilenameOptionFlags(cmd, fileOptions, usage) + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to reconcile.") cmd.MarkFlagRequired("filename") return cmd } -func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string, options *resource.FilenameOptions) error { +func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error { if len(args) > 0 { return errors.New("no arguments are allowed") } @@ -99,7 +106,7 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options). + FilenameParam(enforceNamespace, o.FilenameOptions). Flatten(). Do() @@ -115,17 +122,12 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args o.RBACClient = client.Rbac() o.NamespaceClient = client.Core().Namespaces() - dryRun := false - output := cmdutil.GetFlagString(cmd, "output") - shortOutput := output == "name" - o.Print = func(info *resource.Info) error { - if len(output) > 0 && !shortOutput { - return cmdutil.PrintObject(cmd, info.Object, o.Out) - } - cmdutil.PrintSuccess(shortOutput, o.Out, info.Object, dryRun, "reconciled") - return nil + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err } + o.PrintObject = printer.PrintObj return nil } @@ -139,13 +141,13 @@ func (o *ReconcileOptions) Validate() error { if o.NamespaceClient == nil { return errors.New("ReconcileOptions.NamespaceClient must be set") } - if o.Print == nil { + if o.PrintObject == nil { return errors.New("ReconcileOptions.Print must be set") } if o.Out == nil { return errors.New("ReconcileOptions.Out must be set") } - if o.Err == nil { + if o.ErrOut == nil { return errors.New("ReconcileOptions.Err must be set") } return nil @@ -157,10 +159,6 @@ func (o *ReconcileOptions) RunReconcile() error { return err } - // shallowInfoCopy this is used to later twiddle the Object for printing - // we really need more straightforward printing options - shallowInfoCopy := *info - switch t := info.Object.(type) { case *rbac.Role: reconcileOptions := reconciliation.ReconcileRoleOptions{ @@ -176,8 +174,7 @@ func (o *ReconcileOptions) RunReconcile() error { if err != nil { return err } - shallowInfoCopy.Object = result.Role.GetObject() - o.Print(&shallowInfoCopy) + o.PrintObject(result.Role.GetObject(), o.Out) case *rbac.ClusterRole: reconcileOptions := reconciliation.ReconcileRoleOptions{ @@ -192,8 +189,7 @@ func (o *ReconcileOptions) RunReconcile() error { if err != nil { return err } - shallowInfoCopy.Object = result.Role.GetObject() - o.Print(&shallowInfoCopy) + o.PrintObject(result.Role.GetObject(), o.Out) case *rbac.RoleBinding: reconcileOptions := reconciliation.ReconcileRoleBindingOptions{ @@ -209,8 +205,7 @@ func (o *ReconcileOptions) RunReconcile() error { if err != nil { return err } - shallowInfoCopy.Object = result.RoleBinding.GetObject() - o.Print(&shallowInfoCopy) + o.PrintObject(result.RoleBinding.GetObject(), o.Out) case *rbac.ClusterRoleBinding: reconcileOptions := reconciliation.ReconcileRoleBindingOptions{ @@ -225,8 +220,7 @@ func (o *ReconcileOptions) RunReconcile() error { if err != nil { return err } - shallowInfoCopy.Object = result.RoleBinding.GetObject() - o.Print(&shallowInfoCopy) + o.PrintObject(result.RoleBinding.GetObject(), o.Out) default: glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind()) diff --git a/pkg/kubectl/cmd/certificates.go b/pkg/kubectl/cmd/certificates.go index 169381a48a6..880c3daeb87 100644 --- a/pkg/kubectl/cmd/certificates.go +++ b/pkg/kubectl/cmd/certificates.go @@ -123,8 +123,10 @@ func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOS cmdutil.CheckErr(options.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force"))) }, } + + options.PrintFlags.AddFlags(cmd) + cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.") - cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update") return cmd @@ -173,8 +175,10 @@ func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStre cmdutil.CheckErr(options.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force"))) }, } + + options.PrintFlags.AddFlags(cmd) + cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.") - cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update") return cmd diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 2302fe08409..8f1bf016974 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -309,7 +309,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { NewCmdPortForward(f, out, err), NewCmdProxy(f, out), NewCmdCp(f, ioStreams), - auth.NewCmdAuth(f, out, err), + auth.NewCmdAuth(f, ioStreams), }, }, { @@ -355,7 +355,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { } cmds.AddCommand(alpha) - cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), out, err)) + cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams)) cmds.AddCommand(NewCmdPlugin(f, in, out, err)) cmds.AddCommand(NewCmdVersion(f, ioStreams)) cmds.AddCommand(NewCmdApiVersions(f, ioStreams)) diff --git a/pkg/kubectl/cmd/config/BUILD b/pkg/kubectl/cmd/config/BUILD index ba56eab4b34..b575d12d4a6 100644 --- a/pkg/kubectl/cmd/config/BUILD +++ b/pkg/kubectl/cmd/config/BUILD @@ -14,6 +14,7 @@ go_library( "current_context.go", "delete_cluster.go", "delete_context.go", + "flags.go", "get_clusters.go", "get_contexts.go", "navigation_step_parser.go", @@ -30,6 +31,7 @@ go_library( deps = [ "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", @@ -64,6 +66,7 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 7984eb5e15f..12f3991fe6d 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -18,7 +18,6 @@ package config import ( "fmt" - "io" "path" "strconv" @@ -27,11 +26,12 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) // NewCmdConfig creates a command object for the "config" action, and adds all child commands to it. -func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *cobra.Command { +func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, streams genericclioptions.IOStreams) *cobra.Command { if len(pathOptions.ExplicitFileFlag) == 0 { pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag } @@ -48,25 +48,26 @@ func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, out, er 1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. 2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. 3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.`), - Run: cmdutil.DefaultSubCommandRun(errOut), + Run: cmdutil.DefaultSubCommandRun(streams.Out), } // file paths are common to all sub commands cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") - cmd.AddCommand(NewCmdConfigView(f, out, errOut, pathOptions)) - cmd.AddCommand(NewCmdConfigSetCluster(out, pathOptions)) - cmd.AddCommand(NewCmdConfigSetAuthInfo(out, pathOptions)) - cmd.AddCommand(NewCmdConfigSetContext(out, pathOptions)) - cmd.AddCommand(NewCmdConfigSet(out, pathOptions)) - cmd.AddCommand(NewCmdConfigUnset(out, pathOptions)) - cmd.AddCommand(NewCmdConfigCurrentContext(out, pathOptions)) - cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions)) - cmd.AddCommand(NewCmdConfigGetContexts(out, pathOptions)) - cmd.AddCommand(NewCmdConfigGetClusters(out, pathOptions)) - cmd.AddCommand(NewCmdConfigDeleteCluster(out, pathOptions)) - cmd.AddCommand(NewCmdConfigDeleteContext(out, errOut, pathOptions)) - cmd.AddCommand(NewCmdConfigRenameContext(out, pathOptions)) + // TODO(juanvallejo): update all subcommands to work with genericclioptions.IOStreams + cmd.AddCommand(NewCmdConfigView(f, streams, pathOptions)) + cmd.AddCommand(NewCmdConfigSetCluster(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigSetAuthInfo(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigSetContext(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigSet(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigUnset(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigCurrentContext(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigUseContext(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigGetContexts(streams, pathOptions)) + cmd.AddCommand(NewCmdConfigGetClusters(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigDeleteCluster(streams.Out, pathOptions)) + cmd.AddCommand(NewCmdConfigDeleteContext(streams.Out, streams.ErrOut, pathOptions)) + cmd.AddCommand(NewCmdConfigRenameContext(streams.Out, pathOptions)) return cmd } diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index 3c74ff151b5..ca8d26c222c 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -17,7 +17,6 @@ limitations under the License. package config import ( - "bytes" "fmt" "io/ioutil" "os" @@ -31,6 +30,7 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) func newRedFederalCowHammerConfig() clientcmdapi.Config { @@ -863,9 +863,8 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name()) argsToUse = append(argsToUse, args...) - buf := bytes.NewBuffer([]byte{}) - - cmd := NewCmdConfig(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), clientcmd.NewDefaultPathOptions(), buf, buf) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdConfig(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), clientcmd.NewDefaultPathOptions(), streams) cmd.SetArgs(argsToUse) cmd.Execute() diff --git a/pkg/kubectl/cmd/config/flags.go b/pkg/kubectl/cmd/config/flags.go new file mode 100644 index 00000000000..ebba3b7f22f --- /dev/null +++ b/pkg/kubectl/cmd/config/flags.go @@ -0,0 +1,95 @@ +/* +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 config + +import ( + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/printers" +) + +// PrintFlags composes common printer flag structs +// used across all config commands, and provides a method +// of retrieving a known printer based on flag values provided. +type PrintFlags struct { + JSONYamlPrintFlags *printers.JSONYamlPrintFlags + NamePrintFlags *printers.NamePrintFlags + TemplateFlags *printers.KubeTemplatePrintFlags + + OutputFormat *string +} + +func (f *PrintFlags) Complete(successTemplate string) error { + return f.NamePrintFlags.Complete(successTemplate) +} + +func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) { + outputFormat := "" + if f.OutputFormat != nil { + outputFormat = *f.OutputFormat + } + + if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + if p, err := f.TemplateFlags.ToPrinter(outputFormat); !printers.IsNoCompatiblePrinterError(err) { + return p, err + } + + return nil, printers.NoCompatiblePrinterError{Options: f} +} + +func (f *PrintFlags) AddFlags(cmd *cobra.Command) { + f.JSONYamlPrintFlags.AddFlags(cmd) + f.NamePrintFlags.AddFlags(cmd) + f.TemplateFlags.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].") + } +} + +// WithDefaultOutput sets a default output format if one is not provided through a flag value +func (f *PrintFlags) WithDefaultOutput(output string) *PrintFlags { + existingFormat := "" + if f.OutputFormat != nil { + existingFormat = *f.OutputFormat + } + if len(existingFormat) == 0 { + existingFormat = output + } + f.OutputFormat = &existingFormat + + return f +} + +func NewPrintFlags(operation string) *PrintFlags { + outputFormat := "" + + return &PrintFlags{ + OutputFormat: &outputFormat, + + JSONYamlPrintFlags: printers.NewJSONYamlPrintFlags(), + NamePrintFlags: printers.NewNamePrintFlags(operation), + TemplateFlags: printers.NewKubeTemplatePrintFlags(), + } +} diff --git a/pkg/kubectl/cmd/config/get_contexts.go b/pkg/kubectl/cmd/config/get_contexts.go index b392bdb3961..791db8030c3 100644 --- a/pkg/kubectl/cmd/config/get_contexts.go +++ b/pkg/kubectl/cmd/config/get_contexts.go @@ -31,6 +31,7 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" ) @@ -41,7 +42,8 @@ type GetContextsOptions struct { nameOnly bool showHeaders bool contextNames []string - out io.Writer + + genericclioptions.IOStreams } var ( @@ -57,8 +59,12 @@ var ( // NewCmdConfigGetContexts creates a command object for the "get-contexts" action, which // retrieves one or more contexts from a kubeconfig. -func NewCmdConfigGetContexts(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { - options := &GetContextsOptions{configAccess: configAccess} +func NewCmdConfigGetContexts(streams genericclioptions.IOStreams, configAccess clientcmd.ConfigAccess) *cobra.Command { + options := &GetContextsOptions{ + configAccess: configAccess, + + IOStreams: streams, + } cmd := &cobra.Command{ Use: "get-contexts [(-o|--output=)name)]", @@ -74,22 +80,22 @@ func NewCmdConfigGetContexts(out io.Writer, configAccess clientcmd.ConfigAccess) cmdutil.CheckErr(fmt.Errorf("output must be one of '' or 'name': %v", outputFormat)) } if !supportedOutputTypes.Has(outputFormat) { - fmt.Fprintf(out, "--output %v is not available in kubectl config get-contexts; resetting to default output format\n", outputFormat) + fmt.Fprintf(options.Out, "--output %v is not available in kubectl config get-contexts; resetting to default output format\n", outputFormat) cmd.Flags().Set("output", "") } - cmdutil.CheckErr(options.Complete(cmd, args, out)) + cmdutil.CheckErr(options.Complete(cmd, args)) cmdutil.CheckErr(options.RunGetContexts()) }, } - cmdutil.AddOutputFlags(cmd) - cmdutil.AddNoHeadersFlags(cmd) + + cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).") + cmd.Flags().StringP("output", "o", "", "Output format. One of: name") return cmd } // Complete assigns GetContextsOptions from the args. -func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error { +func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error { o.contextNames = args - o.out = out o.nameOnly = false if cmdutil.GetFlagString(cmd, "output") == "name" { o.nameOnly = true @@ -109,9 +115,9 @@ func (o GetContextsOptions) RunGetContexts() error { return err } - out, found := o.out.(*tabwriter.Writer) + out, found := o.Out.(*tabwriter.Writer) if !found { - out = printers.GetNewTabWriter(o.out) + out = printers.GetNewTabWriter(o.Out) defer out.Flush() } diff --git a/pkg/kubectl/cmd/config/get_contexts_test.go b/pkg/kubectl/cmd/config/get_contexts_test.go index d21c4470a43..fe16f076647 100644 --- a/pkg/kubectl/cmd/config/get_contexts_test.go +++ b/pkg/kubectl/cmd/config/get_contexts_test.go @@ -17,13 +17,13 @@ limitations under the License. package config import ( - "bytes" "io/ioutil" "os" "testing" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) type getContextsTest struct { @@ -157,11 +157,11 @@ func (test getContextsTest) run(t *testing.T) { pathOptions := clientcmd.NewDefaultPathOptions() pathOptions.GlobalFile = fakeKubeFile.Name() pathOptions.EnvVar = "" - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() options := GetContextsOptions{ configAccess: pathOptions, } - cmd := NewCmdConfigGetContexts(buf, options.configAccess) + cmd := NewCmdConfigGetContexts(streams, options.configAccess) if test.nameOnly { cmd.Flags().Set("output", "name") } diff --git a/pkg/kubectl/cmd/config/view.go b/pkg/kubectl/cmd/config/view.go index 92f46510980..76036cf1c2a 100644 --- a/pkg/kubectl/cmd/config/view.go +++ b/pkg/kubectl/cmd/config/view.go @@ -18,8 +18,6 @@ package config import ( "errors" - "fmt" - "io" "github.com/spf13/cobra" @@ -29,16 +27,24 @@ import ( "k8s.io/client-go/tools/clientcmd/api/latest" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" ) type ViewOptions struct { + PrintFlags *PrintFlags + PrintObject printers.ResourcePrinterFunc + ConfigAccess clientcmd.ConfigAccess Merge flag.Tristate Flatten bool Minify bool RawByteData bool + + OutputFormat string + + genericclioptions.IOStreams } var ( @@ -56,12 +62,17 @@ var ( # Get the password for the e2e user kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`) + + defaultOutputFormat = "yaml" ) -func NewCmdConfigView(f cmdutil.Factory, out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command { - options := &ViewOptions{ConfigAccess: ConfigAccess} - // Default to yaml - defaultOutputFormat := "yaml" +func NewCmdConfigView(f cmdutil.Factory, streams genericclioptions.IOStreams, ConfigAccess clientcmd.ConfigAccess) *cobra.Command { + o := &ViewOptions{ + PrintFlags: NewPrintFlags("").WithDefaultOutput("yaml"), + ConfigAccess: ConfigAccess, + + IOStreams: streams, + } cmd := &cobra.Command{ Use: "view", @@ -69,40 +80,48 @@ func NewCmdConfigView(f cmdutil.Factory, out, errOut io.Writer, ConfigAccess cli Long: view_long, Example: view_example, Run: func(cmd *cobra.Command, args []string) { - options.Complete() - outputFormat := cmdutil.GetFlagString(cmd, "output") - if outputFormat == "wide" { - fmt.Fprintf(errOut, "--output wide is not available in kubectl config view; reset to default output format (%s)\n\n", defaultOutputFormat) - // TODO: once printing is abstracted, this should be handled at flag declaration time - cmd.Flags().Set("output", defaultOutputFormat) - } - if outputFormat == "" { - fmt.Fprintf(errOut, "Reset to default output format (%s) as --output is empty\n", defaultOutputFormat) - // TODO: once printing is abstracted, this should be handled at flag declaration time - cmd.Flags().Set("output", defaultOutputFormat) - } - - printOpts := cmdutil.ExtractCmdPrintOptions(cmd, false) - printer, err := cmdutil.PrinterForOptions(printOpts) - cmdutil.CheckErr(err) - - cmdutil.CheckErr(options.Run(out, printer)) + cmdutil.CheckErr(o.Complete(cmd)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) }, } - cmdutil.AddPrinterFlags(cmd) - cmd.Flags().Set("output", defaultOutputFormat) + o.PrintFlags.AddFlags(cmd) - options.Merge.Default(true) - mergeFlag := cmd.Flags().VarPF(&options.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files") + o.Merge.Default(true) + mergeFlag := cmd.Flags().VarPF(&o.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files") mergeFlag.NoOptDefVal = "true" - cmd.Flags().BoolVar(&options.RawByteData, "raw", options.RawByteData, "Display raw byte data") - cmd.Flags().BoolVar(&options.Flatten, "flatten", options.Flatten, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)") - cmd.Flags().BoolVar(&options.Minify, "minify", options.Minify, "Remove all information not used by current-context from the output") + cmd.Flags().BoolVar(&o.RawByteData, "raw", o.RawByteData, "Display raw byte data") + cmd.Flags().BoolVar(&o.Flatten, "flatten", o.Flatten, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)") + cmd.Flags().BoolVar(&o.Minify, "minify", o.Minify, "Remove all information not used by current-context from the output") return cmd } -func (o ViewOptions) Run(out io.Writer, printer printers.ResourcePrinter) error { +func (o *ViewOptions) Complete(cmd *cobra.Command) error { + if o.ConfigAccess.IsExplicitFile() { + if !o.Merge.Provided() { + o.Merge.Set("false") + } + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObject = printer.PrintObj + + return nil +} + +func (o ViewOptions) Validate() error { + if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() { + return errors.New("if merge==false a precise file must to specified") + } + + return nil +} + +func (o ViewOptions) Run() error { config, err := o.loadConfig() if err != nil { return err @@ -127,22 +146,7 @@ func (o ViewOptions) Run(out io.Writer, printer printers.ResourcePrinter) error return err } - err = printer.PrintObj(convertedObj, out) - if err != nil { - return err - } - - return nil -} - -func (o *ViewOptions) Complete() bool { - if o.ConfigAccess.IsExplicitFile() { - if !o.Merge.Provided() { - o.Merge.Set("false") - } - } - - return true + return o.PrintObject(convertedObj, o.Out) } func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) { @@ -155,14 +159,6 @@ func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) { return config, err } -func (o ViewOptions) Validate() error { - if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() { - return errors.New("if merge==false a precise file must to specified") - } - - return nil -} - // getStartingConfig returns the Config object built from the sources specified by the options, the filename read (only if it was a single file), and an error if something goes wrong func (o *ViewOptions) getStartingConfig() (*clientcmdapi.Config, error) { switch { diff --git a/pkg/kubectl/cmd/config/view_test.go b/pkg/kubectl/cmd/config/view_test.go index b8fb9e0df01..b3f05d48bf4 100644 --- a/pkg/kubectl/cmd/config/view_test.go +++ b/pkg/kubectl/cmd/config/view_test.go @@ -17,7 +17,6 @@ limitations under the License. package config import ( - "bytes" "io/ioutil" "os" "testing" @@ -25,6 +24,7 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) type viewClusterTest struct { @@ -141,9 +141,8 @@ func (test viewClusterTest) run(t *testing.T) { pathOptions := clientcmd.NewDefaultPathOptions() pathOptions.GlobalFile = fakeKubeFile.Name() pathOptions.EnvVar = "" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConfigView(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), buf, errBuf, pathOptions) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdConfigView(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), streams, pathOptions) cmd.Flags().Parse(test.flags) if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error executing command: %v,kubectl config view flags: %v", err, test.flags) diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 39a45f4f9de..c24c27adc7e 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -124,8 +124,6 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Example: delete_example, Run: func(cmd *cobra.Command, args []string) { options := deleteFlags.ToOptions(out, errOut) - cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) - if err := options.Complete(f, out, errOut, args, cmd); err != nil { cmdutil.CheckErr(err) } @@ -185,6 +183,11 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args } func (o *DeleteOptions) Validate(cmd *cobra.Command) error { + outputMode := cmdutil.GetFlagString(cmd, "output") + if outputMode != "" && outputMode != "name" { + return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", outputMode) + } + if o.DeleteAll && len(o.LabelSelector) > 0 { return fmt.Errorf("cannot set --all and --selector at the same time") } diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index ca3a1846edb..faf8d7f8fbb 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -48,9 +48,7 @@ func fakecmd() *cobra.Command { cmd := &cobra.Command{ Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) - }, + Run: func(cmd *cobra.Command, args []string) {}, } return cmd } diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index 8e4e4aa53f9..1f6f3510476 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -91,8 +91,15 @@ type ReplaceOptions struct { } func NewReplaceOptions(out, errOut io.Writer) *ReplaceOptions { + outputFormat := "" + return &ReplaceOptions{ - PrintFlags: printers.NewPrintFlags("replaced"), + // TODO(juanvallejo): figure out why we only support the "name" outputFormat in this command + // we only support "-o name" for this command, so only register the name printer + PrintFlags: &printers.PrintFlags{ + OutputFormat: &outputFormat, + NamePrintFlags: printers.NewNamePrintFlags("replaced"), + }, DeleteFlags: NewDeleteFlags("to use to replace the resource."), Out: out, @@ -110,7 +117,6 @@ func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Long: replaceLong, Example: replaceExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate(cmd)) cmdutil.CheckErr(o.Run()) diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index b1e710cd240..8bb1d7809e3 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -93,9 +93,16 @@ type ScaleOptions struct { } func NewScaleOptions(ioStreams genericclioptions.IOStreams) *ScaleOptions { + outputFormat := "" + return &ScaleOptions{ + // TODO(juanvallejo): figure out why we only support the "name" outputFormat in this command + // we only support "-o name" for this command, so only register the name printer + PrintFlags: &printers.PrintFlags{ + OutputFormat: &outputFormat, + NamePrintFlags: printers.NewNamePrintFlags("scaled"), + }, RecordFlags: genericclioptions.NewRecordFlags(), - PrintFlags: printers.NewPrintFlags("scaled"), CurrentReplicas: -1, Recorder: genericclioptions.NoopRecorder{}, IOStreams: ioStreams, @@ -173,9 +180,6 @@ func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st } func (o *ScaleOptions) Validate(cmd *cobra.Command) error { - if err := cmdutil.ValidateOutputArgs(cmd); err != nil { - return err - } if o.Replicas < 0 { return fmt.Errorf("The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0") } diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index ef3a0eda50e..1603a3c8eb1 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -18,222 +18,12 @@ package util import ( "fmt" - "io" - "strings" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - kubectlscheme "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" - - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/kubernetes/pkg/api/legacyscheme" ) -// AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path) -func AddPrinterFlags(cmd *cobra.Command) { - AddNonDeprecatedPrinterFlags(cmd) - - cmd.Flags().String("output-version", "", "DEPRECATED: To use a specific API version, fully-qualify the resource, version, and group (for example: 'jobs.v1.batch/myjob').") - cmd.Flags().MarkDeprecated("output-version", "The resource is used exactly as fetched from the API. To get a specific API version, fully-qualify the resource, version, and group (for example: 'jobs.v1.batch/myjob').") - cmd.Flags().MarkHidden("output-version") -} - -// AddNonDeprecatedPrinterFlags supports the conversion case which must logically have output-version. Once output-version -// is completely removed, this function can go away. -func AddNonDeprecatedPrinterFlags(cmd *cobra.Command) { - AddOutputFlags(cmd) - AddNoHeadersFlags(cmd) - cmd.Flags().Bool("show-labels", false, "When printing, show all labels as the last column (default hide labels column)") - cmd.Flags().String("template", "", "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].") - cmd.MarkFlagFilename("template") - cmd.Flags().String("sort-by", "", "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.") - 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") -} - -// AddOutputFlagsForMutation adds output related flags to a command. Used by mutations only. -func AddOutputFlagsForMutation(cmd *cobra.Command) { - cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).") -} - -// AddOutputVarFlagsForMutation adds output related flags to a command. Used by mutations only. -func AddOutputVarFlagsForMutation(cmd *cobra.Command, output *string) { - cmd.Flags().StringVarP(output, "output", "o", *output, "Output mode. Use \"-o name\" for shorter output (resource/name).") -} - -// AddOutputFlags adds output related flags to a command. -func AddOutputFlags(cmd *cobra.Command) { - cmd.Flags().StringP("output", "o", "", "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].") - cmd.Flags().Bool("allow-missing-template-keys", true, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.") -} - -// AddNoHeadersFlags adds no-headers flags to a command. -func AddNoHeadersFlags(cmd *cobra.Command) { - cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).") -} - -// ValidateOutputArgs validates -o flag args for mutations -func ValidateOutputArgs(cmd *cobra.Command) error { - outputMode := GetFlagString(cmd, "output") - if outputMode != "" && outputMode != "name" { - return UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", outputMode) - } - return nil -} - -// PrintSuccess prints a success message and can do a "-o name" as "shortOutput" -// TODO this should really just be a printer. It's got just about the exact same signature. -func PrintSuccess(shortOutput bool, out io.Writer, obj runtime.Object, dryRun bool, operation string) { - dryRunMsg := "" - if dryRun { - dryRunMsg = " (dry run)" - } - - // match name printer format - name := "" - if acc, err := meta.Accessor(obj); err == nil { - if n := acc.GetName(); len(n) > 0 { - name = n - } - } - - // legacy scheme to be sure we work ok with internal types. - // TODO internal types aren't supposed to exist here - groupKind := printers.GetObjectGroupKind(obj, legacyscheme.Scheme) - kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group) - if len(groupKind.Group) == 0 { - kindString = strings.ToLower(groupKind.Kind) - } - - if shortOutput { - // -o name: prints resource/name - fmt.Fprintf(out, "%s/%s\n", kindString, name) - return - } - - // understandable output by default - fmt.Fprintf(out, "%s \"%s\" %s%s\n", kindString, name, operation, dryRunMsg) -} - -// PrintObject prints a single object based on the default command options -// TODO this should go away once commands can embed the PrintOptions instead -func PrintObject(cmd *cobra.Command, obj runtime.Object, out io.Writer) error { - printer, err := PrinterForOptions(ExtractCmdPrintOptions(cmd, false)) - if err != nil { - return err - } - return printer.PrintObj(obj, out) -} - -// PrinterForOptions returns the printer for the outputOptions (if given) or -// returns the default printer for the command. -// TODO this should become a function on the PrintOptions struct -func PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) { - // TODO: used by the custom column implementation and the name implementation, break this dependency - decoders := []runtime.Decoder{kubectlscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme} - encoder := kubectlscheme.Codecs.LegacyCodec(kubectlscheme.Registry.RegisteredGroupVersions()...) - - printer, err := printers.GetStandardPrinter(kubectlscheme.Scheme, encoder, decoders, *options) - if err != nil { - return nil, err - } - - // we try to convert to HumanReadablePrinter, if return ok, it must be no generic - // we execute AddHandlers() here before maybeWrapSortingPrinter so that we don't - // need to convert to delegatePrinter again then invoke AddHandlers() - // TODO this looks highly questionable. human readable printers are baked into code. This can just live in the definition of the handler itself - // TODO or be registered there - if humanReadablePrinter, ok := printer.(printers.PrintHandler); ok { - printersinternal.AddHandlers(humanReadablePrinter) - } - - printer = maybeWrapSortingPrinter(printer, *options) - - // wrap the printer in a versioning printer that understands when to convert and when not to convert - printer = printers.NewVersionedPrinter(printer, legacyscheme.Scheme, legacyscheme.Scheme, kubectlscheme.Versions...) - - return printer, nil -} - -// ExtractCmdPrintOptions parses printer specific commandline args and -// returns a PrintOptions object. -// Requires that printer flags have been added to cmd (see AddPrinterFlags) -func ExtractCmdPrintOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions { - flags := cmd.Flags() - - columnLabel, err := flags.GetStringSlice("label-columns") - if err != nil { - columnLabel = []string{} - } - - options := &printers.PrintOptions{ - NoHeaders: GetFlagBool(cmd, "no-headers"), - Wide: GetWideFlag(cmd), - ShowAll: GetFlagBool(cmd, "show-all"), - ShowLabels: GetFlagBool(cmd, "show-labels"), - AbsoluteTimestamps: isWatch(cmd), - ColumnLabels: columnLabel, - WithNamespace: withNamespace, - } - - var outputFormat string - if flags.Lookup("output") != nil { - outputFormat = GetFlagString(cmd, "output") - } - - if flags.Lookup("sort-by") != nil { - options.SortBy = GetFlagString(cmd, "sort-by") - } - - // templates are logically optional for specifying a format. - // TODO once https://github.com/kubernetes/kubernetes/issues/12668 is fixed, this should fall back to GetFlagString - var templateFile string - if flag := flags.Lookup("template"); flag != nil { - if flag.Value.Type() == "string" { - templateFile = GetFlagString(cmd, "template") - } - } - if len(outputFormat) == 0 && len(templateFile) != 0 { - outputFormat = "template" - } - - templateFormats := []string{ - "go-template=", "go-template-file=", "jsonpath=", "jsonpath-file=", "custom-columns=", "custom-columns-file=", - } - for _, format := range templateFormats { - if strings.HasPrefix(outputFormat, format) { - templateFile = outputFormat[len(format):] - outputFormat = format[:len(format)-1] - } - } - - // this function may be invoked by a command that did not call AddPrinterFlags first, so we need - // to be safe about how we access the allow-missing-template-keys flag - if flags.Lookup("allow-missing-template-keys") != nil { - options.AllowMissingKeys = GetFlagBool(cmd, "allow-missing-template-keys") - } - - options.OutputFormatType = outputFormat - options.OutputFormatArgument = templateFile - - return options -} - -func maybeWrapSortingPrinter(printer printers.ResourcePrinter, printOpts printers.PrintOptions) printers.ResourcePrinter { - if len(printOpts.SortBy) != 0 { - return &kubectl.SortingPrinter{ - Delegate: printer, - SortField: fmt.Sprintf("{%s}", printOpts.SortBy), - } - } - return printer -} - // SuggestApiResources returns a suggestion to use the "api-resources" command // to retrieve a supported list of resources func SuggestApiResources(parent string) string { diff --git a/pkg/printers/flags.go b/pkg/printers/flags.go index 0ab5df8be8e..405f01261cc 100644 --- a/pkg/printers/flags.go +++ b/pkg/printers/flags.go @@ -65,12 +65,16 @@ func (f *PrintFlags) ToPrinter() (ResourcePrinter, error) { outputFormat = *f.OutputFormat } - if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { - return p, err + if f.JSONYamlPrintFlags != nil { + if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { + return p, err + } } - if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { - return p, err + if f.NamePrintFlags != nil { + if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { + return p, err + } } return nil, NoCompatiblePrinterError{Options: f, OutputFormat: f.OutputFormat}