From 27bd4ded048a66d8dcbc5c759836366943621cd1 Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Wed, 18 Apr 2018 20:02:37 -0400 Subject: [PATCH] wire printflags through additional cmds --- hack/make-rules/test-cmd-util.sh | 6 +- pkg/kubectl/cmd/annotate.go | 31 ++- pkg/kubectl/cmd/autoscale.go | 196 ++++++++++++------ pkg/kubectl/cmd/certificates.go | 94 ++++++--- pkg/kubectl/cmd/clusterinfo.go | 53 +++-- pkg/kubectl/cmd/clusterinfo_dump.go | 56 +++-- pkg/kubectl/cmd/clusterinfo_dump_test.go | 5 +- pkg/kubectl/cmd/cmd.go | 18 +- pkg/kubectl/cmd/convert.go | 54 +++-- pkg/kubectl/cmd/convert_test.go | 30 ++- pkg/kubectl/cmd/cp.go | 105 ++++++---- pkg/kubectl/cmd/cp_test.go | 8 +- pkg/kubectl/cmd/drain.go | 116 +++++++---- pkg/kubectl/cmd/drain_test.go | 19 +- pkg/kubectl/cmd/edit.go | 2 +- pkg/kubectl/cmd/expose.go | 106 ++++++---- pkg/kubectl/cmd/expose_test.go | 16 +- pkg/kubectl/cmd/label.go | 48 +++-- pkg/kubectl/cmd/label_test.go | 17 +- pkg/kubectl/cmd/logs.go | 1 + pkg/kubectl/cmd/patch.go | 38 +++- pkg/kubectl/cmd/patch_test.go | 2 +- .../test.yaml | 4 +- .../test.yaml | 4 +- .../test.yaml | 2 +- .../test.yaml | 2 +- .../edit/testcase-create-list-error/test.yaml | 2 +- .../edit/testcase-create-list/test.yaml | 4 +- .../edit/testcase-edit-error-reedit/test.yaml | 2 +- .../edit/testcase-edit-output-patch/test.yaml | 2 +- .../edit/testcase-list-errors/test.yaml | 4 +- .../edit/testcase-list-record/test.yaml | 4 +- .../cmd/testdata/edit/testcase-list/test.yaml | 4 +- .../testcase-not-update-annotation/test.yaml | 2 +- .../edit/testcase-schemaless-list/test.yaml | 6 +- .../edit/testcase-single-service/test.yaml | 2 +- .../edit/testcase-syntax-error/test.yaml | 2 +- .../test.yaml | 2 +- .../test.yaml | 2 +- .../edit/testcase-update-annotation/test.yaml | 2 +- pkg/kubectl/cmd/util/editor/editoptions.go | 47 ++++- pkg/printers/flags.go | 14 ++ 42 files changed, 746 insertions(+), 388 deletions(-) diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index e88f1ea912..74527f4fae 100755 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -2843,7 +2843,7 @@ run_rc_tests() { # Pre-condition: default run without --name flag; should succeed by truncating the inherited name output_message=$(kubectl expose -f hack/testdata/pod-with-large-name.yaml --port=8081 2>&1 "${kube_flags[@]}") # Post-condition: inherited name from pod has been truncated - kube::test::if_has_string "${output_message}" '\"kubernetes-serve-hostname-testing-sixty-three-characters-in-len\" exposed' + kube::test::if_has_string "${output_message}" 'kubernetes-serve-hostname-testing-sixty-three-characters-in-len exposed' # Clean-up kubectl delete svc kubernetes-serve-hostname-testing-sixty-three-characters-in-len "${kube_flags[@]}" @@ -2851,7 +2851,7 @@ run_rc_tests() { # Pre-condition: don't use --port flag output_message=$(kubectl expose -f test/fixtures/doc-yaml/admin/high-availability/etcd.yaml --selector=test=etcd 2>&1 "${kube_flags[@]}") # Post-condition: expose succeeded - kube::test::if_has_string "${output_message}" '\"etcd-server\" exposed' + kube::test::if_has_string "${output_message}" 'etcd-server\" exposed' # Post-condition: generated service has both ports from the exposed pod kube::test::get_object_assert 'service etcd-server' "{{$port_name}} {{$port_field}}" 'port-1 2380' kube::test::get_object_assert 'service etcd-server' "{{$second_port_name}} {{$second_port_field}}" 'port-2 2379' @@ -4647,7 +4647,7 @@ __EOF__ kube::test::if_has_string "${response}" 'must provide one or more resources' # test=label matches our node response=$(kubectl cordon --selector test=label) - kube::test::if_has_string "${response}" 'node "127.0.0.1" cordoned' + kube::test::if_has_string "${response}" 'node/127.0.0.1 cordoned' # invalid=label does not match any nodes response=$(kubectl cordon --selector invalid=label) kube::test::if_has_not_string "${response}" 'cordoned' diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index 8e50c0c918..e850a1fa51 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -19,6 +19,7 @@ package cmd import ( "bytes" "fmt" + "io" jsonpatch "github.com/evanphx/json-patch" "github.com/golang/glog" @@ -29,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" + "k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -40,6 +42,9 @@ import ( // AnnotateOptions have the data required to perform the annotate operation type AnnotateOptions struct { + PrintFlags *printers.PrintFlags + PrintObj printers.ResourcePrinterFunc + // Filename options resource.FilenameOptions RecordFlags *genericclioptions.RecordFlags @@ -99,10 +104,11 @@ var ( func NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions { return &AnnotateOptions{ - RecordFlags: genericclioptions.NewRecordFlags(), + PrintFlags: printers.NewPrintFlags("annotated"), - Recorder: genericclioptions.NoopRecorder{}, - IOStreams: ioStreams, + RecordFlags: genericclioptions.NewRecordFlags(), + Recorder: genericclioptions.NoopRecorder{}, + IOStreams: ioStreams, } } @@ -131,8 +137,8 @@ func NewCmdAnnotate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *c // bind flag structs o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) - cmdutil.AddPrinterFlags(cmd) cmdutil.AddIncludeUninitializedFlag(cmd) cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.") cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, annotation will NOT contact api-server but run locally.") @@ -159,6 +165,17 @@ func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.dryrun = cmdutil.GetDryRunFlag(cmd) + if o.dryrun { + o.PrintFlags.Complete("%s (dry run)") + } + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object, out io.Writer) error { + return printer.PrintObj(obj, out) + } + // retrieves resource and annotation args from args // also checks args to verify that all resources are specified before annotations resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation") @@ -280,11 +297,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro } } - if len(o.outputFormat) > 0 { - return cmdutil.PrintObject(cmd, outputObj, o.Out) - } - cmdutil.PrintSuccess(false, o.Out, info.Object, o.dryrun, "annotated") - return nil + return o.PrintObj(outputObj, o.Out) }) } diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index be54eecc9e..27ca4dc02e 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -18,8 +18,13 @@ package cmd import ( "fmt" - "io" + "github.com/golang/glog" + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -27,9 +32,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" - - "github.com/golang/glog" - "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/printers" ) var ( @@ -48,22 +51,46 @@ var ( ) type AutoscaleOptions struct { - FilenameOptions resource.FilenameOptions - RecordFlags *genericclioptions.RecordFlags + FilenameOptions *resource.FilenameOptions - Recorder genericclioptions.Recorder + RecordFlags *genericclioptions.RecordFlags + Recorder genericclioptions.Recorder + + PrintFlags *printers.PrintFlags + ToPrinter func(string) (printers.ResourcePrinterFunc, error) + + Builder *resource.Builder + CanBeAutoscaled func(kind schema.GroupKind) error + + CreateAnnotation bool + DryRun bool + EnforceNamespace bool + + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) + + GeneratorFunc func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error) + + Namespace string + BuilderArgs []string + + genericclioptions.IOStreams } -func NewAutoscaleOptions() *AutoscaleOptions { +func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions { return &AutoscaleOptions{ - FilenameOptions: resource.FilenameOptions{}, + PrintFlags: printers.NewPrintFlags("autoscaled"), + FilenameOptions: &resource.FilenameOptions{}, RecordFlags: genericclioptions.NewRecordFlags(), Recorder: genericclioptions.NoopRecorder{}, + + IOStreams: ioStreams, } } -func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { - o := NewAutoscaleOptions() +func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewAutoscaleOptions(ioStreams) validArgs := []string{"deployment", "replicaset", "replicationcontroller"} argAliases := kubectl.ResourceAliases(validArgs) @@ -75,8 +102,9 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: autoscaleLong, Example: autoscaleExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Complete(f, cmd)) - cmdutil.CheckErr(o.RunAutoscale(f, out, cmd, args)) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd)) + cmdutil.CheckErr(o.Run()) }, ValidArgs: validArgs, ArgAliases: argAliases, @@ -84,8 +112,8 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { // bind flag structs o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) - cmdutil.AddPrinterFlags(cmd) cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator.")) cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.") cmd.Flags().Int32("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.") @@ -94,73 +122,105 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used.")) cmdutil.AddDryRunFlag(cmd) usage := "identifying the resource to autoscale." - cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage) cmdutil.AddApplyAnnotationFlags(cmd) return cmd } -func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { - var err error - +func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + o.Builder = f.NewBuilder() + o.CanBeAutoscaled = f.CanBeAutoscaled + o.Mapper, o.Typer = f.Object() + o.ClientForMapping = f.ClientForMapping + o.BuilderArgs = args o.RecordFlags.Complete(f.Command(cmd, false)) + + var err error o.Recorder, err = o.RecordFlags.ToRecorder() if err != nil { return err } + // get the generator + o.GeneratorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) { + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.HorizontalPodAutoscalerV1GeneratorName: + generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{ + Name: name, + MinReplicas: cmdutil.GetFlagInt32(cmd, "min"), + MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"), + CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"), + ScaleRefName: name, + ScaleRefKind: mapping.GroupVersionKind.Kind, + ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(), + } + default: + return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName) + } + + return generator, nil + } + + o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace() + if err != nil { + return err + } + + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + if o.DryRun { + o.PrintFlags.Complete("%s (dry run)") + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + + return printer.PrintObj, nil + } + + return nil +} + +func (o *AutoscaleOptions) Validate(cmd *cobra.Command) error { + if err := validateFlags(cmd); err != nil { + return err + } + return nil } -func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { - namespace, enforceNamespace, err := f.DefaultNamespace() - if err != nil { - return err - } - - // validate flags - if err := validateFlags(cmd); err != nil { - return err - } - - r := f.NewBuilder(). +func (o *AutoscaleOptions) Run() error { + r := o.Builder. Internal(). ContinueOnError(). - NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, &o.FilenameOptions). - ResourceTypeOrNameArgs(false, args...). + NamespaceParam(o.Namespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, o.FilenameOptions). + ResourceTypeOrNameArgs(false, o.BuilderArgs...). Flatten(). Do() - err = r.Err() - if err != nil { + if err := r.Err(); err != nil { return err } count := 0 - err = r.Visit(func(info *resource.Info, err error) error { + err := r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } mapping := info.ResourceMapping() - if err := f.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil { + if err := o.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil { return err } - // get the generator - var generator kubectl.StructuredGenerator - switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { - case cmdutil.HorizontalPodAutoscalerV1GeneratorName: - generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{ - Name: info.Name, - MinReplicas: cmdutil.GetFlagInt32(cmd, "min"), - MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"), - CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"), - ScaleRefName: info.Name, - ScaleRefKind: mapping.GroupVersionKind.Kind, - ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(), - } - default: - return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName) + generator, err := o.GeneratorFunc(info.Name, mapping) + if err != nil { + return err } // Generate new object @@ -169,11 +229,10 @@ func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *c return err } - mapper, typer := f.Object() resourceMapper := &resource.Mapper{ - ObjectTyper: typer, - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + ObjectTyper: o.Typer, + RESTMapper: o.Mapper, + ClientMapper: resource.ClientMapperFunc(o.ClientForMapping), Decoder: cmdutil.InternalVersionDecoder(), } hpa, err := resourceMapper.InfoForObject(object, nil) @@ -183,26 +242,33 @@ func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *c if err := o.Recorder.Record(hpa.Object); err != nil { glog.V(4).Infof("error recording current command: %v", err) } - if cmdutil.GetDryRunFlag(cmd) { - return cmdutil.PrintObject(cmd, object, out) + object = hpa.Object + + if o.DryRun { + count++ + + printer, err := o.ToPrinter("created") + if err != nil { + return err + } + return printer.PrintObj(hpa.AsVersioned(), o.Out) } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(o.CreateAnnotation, hpa.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { return err } - object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object) + _, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(o.Namespace, false, object) if err != nil { return err } count++ - if len(cmdutil.GetFlagString(cmd, "output")) > 0 { - return cmdutil.PrintObject(cmd, object, out) + printer, err := o.ToPrinter("autoscaled") + if err != nil { + return err } - - cmdutil.PrintSuccess(false, out, info.Object, cmdutil.GetDryRunFlag(cmd), "autoscaled") - return nil + return printer.PrintObj(info.AsVersioned(), o.Out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/certificates.go b/pkg/kubectl/cmd/certificates.go index cecef58e50..2a0861b5da 100644 --- a/pkg/kubectl/cmd/certificates.go +++ b/pkg/kubectl/cmd/certificates.go @@ -21,16 +21,20 @@ import ( "io" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/apis/certificates" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "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/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" "github.com/spf13/cobra" ) -func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command { +func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "certificate SUBCOMMAND", DisableFlagsInUseLine: true, @@ -41,33 +45,58 @@ func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command { }, } - cmd.AddCommand(NewCmdCertificateApprove(f, out)) - cmd.AddCommand(NewCmdCertificateDeny(f, out)) + cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams)) + cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams)) return cmd } type CertificateOptions struct { resource.FilenameOptions + + PrintFlags *printers.PrintFlags + PrintObj printers.ResourcePrinterFunc + csrNames []string outputStyle string + + clientSetFunc func() (internalclientset.Interface, error) + builderFunc func() *resource.Builder + + genericclioptions.IOStreams } -func (options *CertificateOptions) Complete(cmd *cobra.Command, args []string) error { - options.csrNames = args - options.outputStyle = cmdutil.GetFlagString(cmd, "output") +func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.csrNames = args + o.outputStyle = cmdutil.GetFlagString(cmd, "output") + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object, out io.Writer) error { + return printer.PrintObj(obj, out) + } + + o.builderFunc = f.NewBuilder + o.clientSetFunc = f.ClientSet + return nil } -func (options *CertificateOptions) Validate() error { - if len(options.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { +func (o *CertificateOptions) Validate() error { + if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { return fmt.Errorf("one or more CSRs must be specified as or -f ") } return nil } -func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := CertificateOptions{} +func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := CertificateOptions{ + PrintFlags: printers.NewPrintFlags("approved"), + IOStreams: ioStreams, + } cmd := &cobra.Command{ Use: "approve (-f FILENAME | NAME)", DisableFlagsInUseLine: true, @@ -85,9 +114,9 @@ func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command { signed certificate can do. `), Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(cmd, args)) + cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.Validate()) - cmdutil.CheckErr(options.RunCertificateApprove(f, out, cmdutil.GetFlagBool(cmd, "force"))) + cmdutil.CheckErr(options.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force"))) }, } cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.") @@ -97,8 +126,8 @@ func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command { return cmd } -func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out io.Writer, force bool) error { - return options.modifyCertificateCondition(f, out, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string) { +func (o *CertificateOptions) RunCertificateApprove(force bool) error { + return o.modifyCertificateCondition(o.builderFunc, o.clientSetFunc, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) { var alreadyApproved bool for _, c := range csr.Status.Conditions { if c.Type == certificates.CertificateApproved { @@ -106,7 +135,7 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out } } if alreadyApproved { - return csr, true, "approved" + return csr, true } csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{ Type: certificates.CertificateApproved, @@ -114,12 +143,15 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out Message: "This CSR was approved by kubectl certificate approve.", LastUpdateTime: metav1.Now(), }) - return csr, false, "approved" + return csr, false }) } -func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := CertificateOptions{} +func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := CertificateOptions{ + PrintFlags: printers.NewPrintFlags("denied"), + IOStreams: ioStreams, + } cmd := &cobra.Command{ Use: "deny (-f FILENAME | NAME)", DisableFlagsInUseLine: true, @@ -132,9 +164,9 @@ func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command { not to issue a certificate to the requestor. `), Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(cmd, args)) + cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.Validate()) - cmdutil.CheckErr(options.RunCertificateDeny(f, out, cmdutil.GetFlagBool(cmd, "force"))) + cmdutil.CheckErr(options.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force"))) }, } cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.") @@ -144,8 +176,8 @@ func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command { return cmd } -func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.Writer, force bool) error { - return options.modifyCertificateCondition(f, out, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string) { +func (o *CertificateOptions) RunCertificateDeny(force bool) error { + return o.modifyCertificateCondition(o.builderFunc, o.clientSetFunc, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) { var alreadyDenied bool for _, c := range csr.Status.Conditions { if c.Type == certificates.CertificateDenied { @@ -153,7 +185,7 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io. } } if alreadyDenied { - return csr, true, "denied" + return csr, true } csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{ Type: certificates.CertificateDenied, @@ -161,17 +193,17 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io. Message: "This CSR was approved by kubectl certificate deny.", LastUpdateTime: metav1.Now(), }) - return csr, false, "denied" + return csr, false }) } -func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, out io.Writer, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string)) error { +func (options *CertificateOptions) modifyCertificateCondition(builderFunc func() *resource.Builder, clientSetFunc func() (internalclientset.Interface, error), force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error { var found int - c, err := f.ClientSet() + c, err := clientSetFunc() if err != nil { return err } - r := f.NewBuilder(). + r := builderFunc(). Internal(). ContinueOnError(). FilenameParam(false, &options.FilenameOptions). @@ -185,7 +217,7 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, return err } csr := info.Object.(*certificates.CertificateSigningRequest) - csr, hasCondition, verb := modify(csr) + csr, hasCondition := modify(csr) if !hasCondition || force { csr, err = c.Certificates(). CertificateSigningRequests(). @@ -195,11 +227,11 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, } } found++ - cmdutil.PrintSuccess(options.outputStyle == "name", out, info.Object, false, verb) - return nil + + return options.PrintObj(info.AsVersioned(), options.Out) }) if found == 0 { - fmt.Fprintf(out, "No resources found\n") + fmt.Fprintf(options.Out, "No resources found\n") } return err } diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index 0c2da5dc45..41019c004b 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -23,9 +23,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnet "k8s.io/apimachinery/pkg/util/net" + restclient "k8s.io/client-go/rest" api "k8s.io/kubernetes/pkg/apis/core" "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/kubectl/util/i18n" @@ -43,41 +45,62 @@ var ( kubectl cluster-info`)) ) -func NewCmdClusterInfo(f cmdutil.Factory, out io.Writer) *cobra.Command { +type ClusterInfoOptions struct { + genericclioptions.IOStreams + + Namespace string + + Builder *resource.Builder + Client *restclient.Config +} + +func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := &ClusterInfoOptions{ + IOStreams: ioStreams, + } + cmd := &cobra.Command{ Use: "cluster-info", Short: i18n.T("Display cluster info"), Long: longDescr, Example: clusterinfoExample, Run: func(cmd *cobra.Command, args []string) { - err := RunClusterInfo(f, out, cmd) - cmdutil.CheckErr(err) + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.Run()) }, } - cmd.AddCommand(NewCmdClusterInfoDump(f, out)) + cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams)) return cmd } -func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { - client, err := f.ClientConfig() +func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + var err error + o.Client, err = f.ClientConfig() if err != nil { return err } - printService(out, "Kubernetes master", client.Host) cmdNamespace := cmdutil.GetFlagString(cmd, "namespace") if cmdNamespace == "" { cmdNamespace = metav1.NamespaceSystem } + o.Namespace = cmdNamespace + + o.Builder = f.NewBuilder() + return nil +} + +func (o *ClusterInfoOptions) Run() error { + printService(o.Out, "Kubernetes master", o.Client.Host) // TODO use generalized labels once they are implemented (#341) - b := f.NewBuilder(). + b := o.Builder. Internal(). - NamespaceParam(cmdNamespace).DefaultNamespace(). + NamespaceParam(o.Namespace).DefaultNamespace(). LabelSelectorParam("kubernetes.io/cluster-service=true"). ResourceTypeOrNameArgs(false, []string{"services"}...). Latest() - err = b.Do().Visit(func(r *resource.Info, err error) error { + err := b.Do().Visit(func(r *resource.Info, err error) error { if err != nil { return err } @@ -109,10 +132,10 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name) } - if len(client.GroupVersion.Group) == 0 { - link = client.Host + "/api/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" + if len(o.Client.GroupVersion.Group) == 0 { + link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" } else { - link = client.Host + "/api/" + client.GroupVersion.Group + "/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" + link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" } } @@ -120,11 +143,11 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error if len(name) == 0 { name = service.ObjectMeta.Name } - printService(out, name, link) + printService(o.Out, name, link) } return nil }) - out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n")) + o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n")) return err // TODO consider printing more information about cluster diff --git a/pkg/kubectl/cmd/clusterinfo_dump.go b/pkg/kubectl/cmd/clusterinfo_dump.go index 511bba5d93..b2ac7dcfed 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump.go +++ b/pkg/kubectl/cmd/clusterinfo_dump.go @@ -28,19 +28,34 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" "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 ClusterInfoDumpOptions struct { + PrintFlags *printers.PrintFlags + PrintObj printers.ResourcePrinterFunc + + genericclioptions.IOStreams +} + // NewCmdCreateSecret groups subcommands to create various types of secrets -func NewCmdClusterInfoDump(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { +func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := &ClusterInfoDumpOptions{ + PrintFlags: printers.NewPrintFlags(""), + + IOStreams: ioStreams, + } + cmd := &cobra.Command{ Use: "dump", Short: i18n.T("Dump lots of relevant info for debugging and diagnosis"), Long: dumpLong, Example: dumpExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(dumpClusterInfo(f, cmd, cmdOut)) + cmdutil.CheckErr(o.Complete()) + cmdutil.CheckErr(o.Run(f, cmd)) }, } cmd.Flags().String("output-directory", "", i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory")) @@ -88,7 +103,20 @@ func setupOutputWriter(cmd *cobra.Command, defaultWriter io.Writer, filename str return file } -func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error { +func (o *ClusterInfoDumpOptions) Complete() error { + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + jsonOutputFmt := "json" + o.PrintFlags.OutputFormat = &jsonOutputFmt + o.PrintObj = printer.PrintObj + + return nil +} + +func (o *ClusterInfoDumpOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd) if err != nil { return cmdutil.UsageErrorf(cmd, err.Error()) @@ -99,14 +127,12 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error return err } - printer := &printers.JSONPrinter{} - nodes, err := clientset.Core().Nodes().List(metav1.ListOptions{}) if err != nil { return err } - if err := printer.PrintObj(nodes, setupOutputWriter(cmd, out, "nodes.json")); err != nil { + if err := o.PrintObj(nodes, setupOutputWriter(cmd, o.Out, "nodes.json")); err != nil { return err } @@ -139,7 +165,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(events, setupOutputWriter(cmd, out, path.Join(namespace, "events.json"))); err != nil { + if err := o.PrintObj(events, setupOutputWriter(cmd, o.Out, path.Join(namespace, "events.json"))); err != nil { return err } @@ -147,7 +173,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(rcs, setupOutputWriter(cmd, out, path.Join(namespace, "replication-controllers.json"))); err != nil { + if err := o.PrintObj(rcs, setupOutputWriter(cmd, o.Out, path.Join(namespace, "replication-controllers.json"))); err != nil { return err } @@ -155,7 +181,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(svcs, setupOutputWriter(cmd, out, path.Join(namespace, "services.json"))); err != nil { + if err := o.PrintObj(svcs, setupOutputWriter(cmd, o.Out, path.Join(namespace, "services.json"))); err != nil { return err } @@ -163,7 +189,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(sets, setupOutputWriter(cmd, out, path.Join(namespace, "daemonsets.json"))); err != nil { + if err := o.PrintObj(sets, setupOutputWriter(cmd, o.Out, path.Join(namespace, "daemonsets.json"))); err != nil { return err } @@ -171,7 +197,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(deps, setupOutputWriter(cmd, out, path.Join(namespace, "deployments.json"))); err != nil { + if err := o.PrintObj(deps, setupOutputWriter(cmd, o.Out, path.Join(namespace, "deployments.json"))); err != nil { return err } @@ -179,7 +205,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error if err != nil { return err } - if err := printer.PrintObj(rps, setupOutputWriter(cmd, out, path.Join(namespace, "replicasets.json"))); err != nil { + if err := o.PrintObj(rps, setupOutputWriter(cmd, o.Out, path.Join(namespace, "replicasets.json"))); err != nil { return err } @@ -188,7 +214,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error return err } - if err := printer.PrintObj(pods, setupOutputWriter(cmd, out, path.Join(namespace, "pods.json"))); err != nil { + if err := o.PrintObj(pods, setupOutputWriter(cmd, o.Out, path.Join(namespace, "pods.json"))); err != nil { return err } @@ -215,7 +241,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error for ix := range pods.Items { pod := &pods.Items[ix] containers := pod.Spec.Containers - writer := setupOutputWriter(cmd, out, path.Join(namespace, pod.Name, "logs.txt")) + writer := setupOutputWriter(cmd, o.Out, path.Join(namespace, pod.Name, "logs.txt")) for i := range containers { printContainer(writer, containers[i], pod) @@ -227,7 +253,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error dir = "standard output" } if dir != "-" { - fmt.Fprintf(out, "Cluster info dumped to %s\n", dir) + fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", dir) } return nil } diff --git a/pkg/kubectl/cmd/clusterinfo_dump_test.go b/pkg/kubectl/cmd/clusterinfo_dump_test.go index f4a1a1145f..3e603d93a6 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump_test.go +++ b/pkg/kubectl/cmd/clusterinfo_dump_test.go @@ -24,6 +24,7 @@ import ( "testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) func TestSetupOutputWriterNoOp(t *testing.T) { @@ -33,7 +34,7 @@ func TestSetupOutputWriterNoOp(t *testing.T) { f := cmdtesting.NewTestFactory() defer f.Cleanup() - cmd := NewCmdClusterInfoDump(f, os.Stdout) + cmd := NewCmdClusterInfoDump(f, genericclioptions.IOStreams{Out: os.Stdout, ErrOut: os.Stderr}) cmd.Flag("output-directory").Value.Set(test) writer := setupOutputWriter(cmd, out, "/some/file/that/should/be/ignored") if writer != out { @@ -55,7 +56,7 @@ func TestSetupOutputWriterFile(t *testing.T) { f := cmdtesting.NewTestFactory() defer f.Cleanup() - cmd := NewCmdClusterInfoDump(f, os.Stdout) + cmd := NewCmdClusterInfoDump(f, genericclioptions.IOStreams{Out: os.Stdout, ErrOut: os.Stderr}) cmd.Flag("output-directory").Value.Set(dir) writer := setupOutputWriter(cmd, out, file) if writer == out { diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 62094582f4..924981eb5c 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -276,18 +276,18 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob rollout.NewCmdRollout(f, out, err), NewCmdRollingUpdate(f, out), NewCmdScale(f, out, err), - NewCmdAutoscale(f, out), + NewCmdAutoscale(f, ioStreams), }, }, { Message: "Cluster Management Commands:", Commands: []*cobra.Command{ - NewCmdCertificate(f, out), - NewCmdClusterInfo(f, out), + NewCmdCertificate(f, ioStreams), + NewCmdClusterInfo(f, ioStreams), NewCmdTop(f, out, err), - NewCmdCordon(f, out), - NewCmdUncordon(f, out), - NewCmdDrain(f, out, err), + NewCmdCordon(f, ioStreams), + NewCmdUncordon(f, ioStreams), + NewCmdDrain(f, ioStreams), NewCmdTaint(f, out), }, }, @@ -300,7 +300,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob NewCmdExec(f, in, out, err), NewCmdPortForward(f, out, err), NewCmdProxy(f, out), - NewCmdCp(f, out, err), + NewCmdCp(f, ioStreams), auth.NewCmdAuth(f, out, err), }, }, @@ -310,13 +310,13 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob NewCmdApply("kubectl", f, ioStreams), NewCmdPatch(f, out), NewCmdReplace(f, out, err), - NewCmdConvert(f, out), + NewCmdConvert(f, ioStreams), }, }, { Message: "Settings Commands:", Commands: []*cobra.Command{ - NewCmdLabel(f, out, err), + NewCmdLabel(f, ioStreams), NewCmdAnnotate(f, ioStreams), NewCmdCompletion(out, ""), }, diff --git a/pkg/kubectl/cmd/convert.go b/pkg/kubectl/cmd/convert.go index 715c915370..cfcbf920d8 100644 --- a/pkg/kubectl/cmd/convert.go +++ b/pkg/kubectl/cmd/convert.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" @@ -27,6 +26,7 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" "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/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" @@ -61,8 +61,8 @@ var ( // NewCmdConvert creates a command object for the generic "convert" action, which // translates the config file into a given version. -func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := NewConvertOptions() +func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := NewConvertOptions(ioStreams) cmd := &cobra.Command{ Use: "convert -f FILENAME", @@ -71,18 +71,17 @@ func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: convert_long, Example: convert_example, Run: func(cmd *cobra.Command, args []string) { - err := options.Complete(f, out, cmd) - cmdutil.CheckErr(err) - err = options.RunConvert() - cmdutil.CheckErr(err) + cmdutil.CheckErr(options.Complete(f, cmd)) + cmdutil.CheckErr(options.RunConvert()) }, } + options.PrintFlags.AddFlags(cmd) + usage := "to need to get converted." cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) - cmdutil.AddNonDeprecatedPrinterFlags(cmd) cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.") cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)")) return cmd @@ -90,19 +89,24 @@ func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command { // ConvertOptions have the data required to perform the convert operation type ConvertOptions struct { + PrintFlags *printers.PrintFlags + PrintObj printers.ResourcePrinterFunc + resource.FilenameOptions builder *resource.Builder local bool - out io.Writer - printer printers.ResourcePrinter - + genericclioptions.IOStreams specifiedOutputVersion schema.GroupVersion } -func NewConvertOptions() *ConvertOptions { - return &ConvertOptions{local: true} +func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions { + return &ConvertOptions{ + PrintFlags: printers.NewPrintFlags("converted").WithDefaultOutput("yaml"), + local: true, + IOStreams: ioStreams, + } } // outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) @@ -117,7 +121,7 @@ func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) { } // Complete collects information required to run Convert command from command line. -func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) (err error) { +func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) { o.specifiedOutputVersion, err = outputVersion(cmd) if err != nil { return err @@ -145,20 +149,12 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C Flatten() // build the printer - o.out = out - outputFormat := cmdutil.GetFlagString(cmd, "output") - templateFile := cmdutil.GetFlagString(cmd, "template") - if len(outputFormat) == 0 { - if len(templateFile) == 0 { - outputFormat = "yaml" - } else { - outputFormat = "template" - } - // TODO: once printing is abstracted, this should be handled at flag declaration time - cmd.Flags().Set("output", outputFormat) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err } - o.printer, err = cmdutil.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false)) - return err + o.PrintObj = printer.PrintObj + return nil } // RunConvert implements the generic Convert command @@ -189,10 +185,10 @@ func (o *ConvertOptions) RunConvert() error { if err != nil { return err } - return o.printer.PrintObj(obj, o.out) + return o.PrintObj(obj, o.Out) } - return o.printer.PrintObj(objects, o.out) + return o.PrintObj(objects, o.Out) } // objectListToVersionedObject receives a list of api objects and a group version diff --git a/pkg/kubectl/cmd/convert_test.go b/pkg/kubectl/cmd/convert_test.go index 5b7474ef63..72ff0755ee 100644 --- a/pkg/kubectl/cmd/convert_test.go +++ b/pkg/kubectl/cmd/convert_test.go @@ -20,10 +20,12 @@ import ( "bytes" "fmt" "net/http" + "strings" "testing" "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) type testcase struct { @@ -34,7 +36,6 @@ type testcase struct { } type checkField struct { - template string expected string } @@ -46,8 +47,7 @@ func TestConvertObject(t *testing.T) { outputVersion: "extensions/v1beta1", fields: []checkField{ { - template: "{{.apiVersion}}", - expected: "extensions/v1beta1", + expected: "apiVersion: extensions/v1beta1", }, }, }, @@ -57,8 +57,7 @@ func TestConvertObject(t *testing.T) { outputVersion: "apps/v1beta2", fields: []checkField{ { - template: "{{.apiVersion}}", - expected: "apps/v1beta2", + expected: "apiVersion: apps/v1beta2", }, }, }, @@ -68,16 +67,13 @@ func TestConvertObject(t *testing.T) { outputVersion: "autoscaling/v2beta1", fields: []checkField{ { - template: "{{.apiVersion}}", - expected: "autoscaling/v2beta1", + expected: "apiVersion: autoscaling/v2beta1", }, { - template: "{{(index .spec.metrics 0).resource.name}}", - expected: "cpu", + expected: "name: cpu", }, { - template: "{{(index .spec.metrics 0).resource.targetAverageUtilization}}", - expected: "50", + expected: "targetAverageUtilization: 50", }, }, }, @@ -87,12 +83,10 @@ func TestConvertObject(t *testing.T) { outputVersion: "autoscaling/v1", fields: []checkField{ { - template: "{{.apiVersion}}", - expected: "autoscaling/v1", + expected: "apiVersion: autoscaling/v1", }, { - template: "{{.spec.targetCPUUtilizationPercentage}}", - expected: "50", + expected: "targetCPUUtilizationPercentage: 50", }, }, }, @@ -113,13 +107,13 @@ func TestConvertObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConvert(tf, buf) + cmd := NewCmdConvert(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) cmd.Flags().Set("filename", tc.file) cmd.Flags().Set("output-version", tc.outputVersion) cmd.Flags().Set("local", "true") - cmd.Flags().Set("output", "go-template="+field.template) + cmd.Flags().Set("output", "yaml") cmd.Run(cmd, []string{}) - if buf.String() != field.expected { + if !strings.Contains(buf.String(), field.expected) { t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String()) } }) diff --git a/pkg/kubectl/cmd/cp.go b/pkg/kubectl/cmd/cp.go index eca86a029d..258b4f50a7 100644 --- a/pkg/kubectl/cmd/cp.go +++ b/pkg/kubectl/cmd/cp.go @@ -28,8 +28,11 @@ import ( "path/filepath" "strings" + restclient "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "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" "github.com/renstrom/dedent" @@ -61,8 +64,26 @@ var ( /file/path for a local file`) ) +type CopyOptions struct { + Container string + Namespace string + + ClientConfig *restclient.Config + Clientset internalclientset.Interface + + genericclioptions.IOStreams +} + +func NewCopyOptions(ioStreams genericclioptions.IOStreams) *CopyOptions { + return &CopyOptions{ + IOStreams: ioStreams, + } +} + // NewCmdCp creates a new Copy command. -func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { +func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewCopyOptions(ioStreams) + cmd := &cobra.Command{ Use: "cp ", DisableFlagsInUseLine: true, @@ -70,7 +91,8 @@ func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { Long: "Copy files and directories to and from containers.", Example: cpExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(runCopy(f, cmd, cmdOut, cmdErr, args)) + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.Run(args)) }, } cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen") @@ -119,10 +141,35 @@ func extractFileSpec(arg string) (fileSpec, error) { return fileSpec{}, errFileSpecDoesntMatchFormat } -func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args []string) error { +func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.Container = cmdutil.GetFlagString(cmd, "container") + + var err error + o.Namespace, _, err = f.DefaultNamespace() + if err != nil { + return err + } + + o.Clientset, err = f.ClientSet() + if err != nil { + return err + } + + o.ClientConfig, err = f.ClientConfig() + if err != nil { + return err + } + return nil +} + +func (o *CopyOptions) Validate(cmd *cobra.Command, args []string) error { if len(args) != 2 { return cmdutil.UsageErrorf(cmd, cpUsageStr) } + return nil +} + +func (o *CopyOptions) Run(args []string) error { srcSpec, err := extractFileSpec(args[0]) if err != nil { return err @@ -132,19 +179,19 @@ func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args return err } if len(srcSpec.PodName) != 0 { - return copyFromPod(f, cmd, cmderr, srcSpec, destSpec) + return o.copyFromPod(srcSpec, destSpec) } if len(destSpec.PodName) != 0 { - return copyToPod(f, cmd, out, cmderr, srcSpec, destSpec) + return o.copyToPod(srcSpec, destSpec) } - return cmdutil.UsageErrorf(cmd, "One of src or dest must be a remote file specification") + return fmt.Errorf("One of src or dest must be a remote file specification") } // checkDestinationIsDir receives a destination fileSpec and // determines if the provided destination path exists on the // pod. If the destination path does not exist or is _not_ a // directory, an error is returned with the exit code received. -func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) error { +func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error { options := &ExecOptions{ StreamOptions: StreamOptions{ Out: bytes.NewBuffer([]byte{}), @@ -158,10 +205,10 @@ func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) Executor: &DefaultRemoteExecutor{}, } - return execute(f, cmd, options) + return o.execute(options) } -func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error { +func (o *CopyOptions) copyToPod(src, dest fileSpec) error { if len(src.File) == 0 || len(dest.File) == 0 { return errFileCannotBeEmpty } @@ -172,7 +219,7 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, dest.File = dest.File[:len(dest.File)-1] } - if err := checkDestinationIsDir(dest, f, cmd); err == nil { + if err := o.checkDestinationIsDir(dest); err == nil { // If no error, dest.File was found to be a directory. // Copy specified src into it dest.File = dest.File + "/" + path.Base(src.File) @@ -194,8 +241,8 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, options := &ExecOptions{ StreamOptions: StreamOptions{ In: reader, - Out: stdout, - Err: stderr, + Out: o.Out, + Err: o.ErrOut, Stdin: true, Namespace: dest.PodNamespace, @@ -205,10 +252,10 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, Command: cmdArr, Executor: &DefaultRemoteExecutor{}, } - return execute(f, cmd, options) + return o.execute(options) } -func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, dest fileSpec) error { +func (o *CopyOptions) copyFromPod(src, dest fileSpec) error { if len(src.File) == 0 || len(dest.File) == 0 { return errFileCannotBeEmpty } @@ -218,7 +265,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d StreamOptions: StreamOptions{ In: nil, Out: outStream, - Err: cmderr, + Err: o.Out, Namespace: src.PodNamespace, PodName: src.PodName, @@ -231,7 +278,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d go func() { defer outStream.Close() - execute(f, cmd, options) + o.execute(options) }() prefix := getPrefix(src.File) prefix = path.Clean(prefix) @@ -389,31 +436,17 @@ func getPrefix(file string) string { return strings.TrimLeft(file, "/") } -func execute(f cmdutil.Factory, cmd *cobra.Command, options *ExecOptions) error { +func (o *CopyOptions) execute(options *ExecOptions) error { if len(options.Namespace) == 0 { - namespace, _, err := f.DefaultNamespace() - if err != nil { - return err - } - options.Namespace = namespace + options.Namespace = o.Namespace } - container := cmdutil.GetFlagString(cmd, "container") - if len(container) > 0 { - options.ContainerName = container + if len(o.Container) > 0 { + options.ContainerName = o.Container } - config, err := f.ClientConfig() - if err != nil { - return err - } - options.Config = config - - clientset, err := f.ClientSet() - if err != nil { - return err - } - options.PodClient = clientset.Core() + options.Config = o.ClientConfig + options.PodClient = o.Clientset.Core() if err := options.Validate(); err != nil { return err diff --git a/pkg/kubectl/cmd/cp_test.go b/pkg/kubectl/cmd/cp_test.go index b7c0eb4872..b24b976617 100644 --- a/pkg/kubectl/cmd/cp_test.go +++ b/pkg/kubectl/cmd/cp_test.go @@ -36,6 +36,7 @@ import ( "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api/legacyscheme" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -526,7 +527,8 @@ func TestCopyToPod(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdCp(tf, buf, errBuf) + + cmd := NewCmdCp(tf, genericclioptions.IOStreams{Out: buf, ErrOut: errBuf}) srcFile, err := ioutil.TempDir("", "test") if err != nil { @@ -554,6 +556,7 @@ func TestCopyToPod(t *testing.T) { } for name, test := range tests { + opts := NewCopyOptions(genericclioptions.IOStreams{Out: buf, ErrOut: errBuf}) src := fileSpec{ File: srcFile, } @@ -562,8 +565,9 @@ func TestCopyToPod(t *testing.T) { PodName: "pod-name", File: test.dest, } + opts.Complete(tf, cmd) t.Run(name, func(t *testing.T) { - err = copyToPod(tf, cmd, buf, errBuf, src, dest) + err = opts.copyToPod(src, dest) //If error is NotFound error , it indicates that the //request has been sent correctly. //Treat this as no error. diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index cbd09e7797..cc4372a800 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -19,7 +19,6 @@ package cmd import ( "errors" "fmt" - "io" "math" "strings" "time" @@ -42,18 +41,23 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" ) type DrainOptions struct { + PrintFlags *printers.PrintFlags + ToPrinter func(string) (printers.ResourcePrinterFunc, error) + + Namespace string client kubernetes.Interface restClient *restclient.RESTClient - Factory cmdutil.Factory Force bool DryRun bool GracePeriodSeconds int @@ -65,9 +69,9 @@ type DrainOptions struct { PodSelector string mapper meta.RESTMapper nodeInfos []*resource.Info - Out io.Writer - ErrOut io.Writer typer runtime.ObjectTyper + + genericclioptions.IOStreams } // Takes a pod and returns a bool indicating whether or not to operate on the @@ -101,8 +105,12 @@ var ( kubectl cordon foo`)) ) -func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := &DrainOptions{Factory: f, Out: out} +func NewCmdCordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := &DrainOptions{ + PrintFlags: printers.NewPrintFlags("cordoned"), + + IOStreams: ioStreams, + } cmd := &cobra.Command{ Use: "cordon NODE", @@ -111,7 +119,7 @@ func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: cordon_long, Example: cordon_example, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.SetupDrain(cmd, args)) + cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.RunCordonOrUncordon(true)) }, } @@ -129,8 +137,11 @@ var ( $ kubectl uncordon foo`)) ) -func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := &DrainOptions{Factory: f, Out: out} +func NewCmdUncordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := &DrainOptions{ + PrintFlags: printers.NewPrintFlags("uncordoned"), + IOStreams: ioStreams, + } cmd := &cobra.Command{ Use: "uncordon NODE", @@ -139,7 +150,7 @@ func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: uncordon_long, Example: uncordon_example, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.SetupDrain(cmd, args)) + cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.RunCordonOrUncordon(false)) }, } @@ -182,18 +193,18 @@ var ( $ kubectl drain foo --grace-period=900`)) ) -func NewDrainOptions(f cmdutil.Factory, out, errOut io.Writer) *DrainOptions { +func NewDrainOptions(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *DrainOptions { return &DrainOptions{ - Factory: f, - Out: out, - ErrOut: errOut, + PrintFlags: printers.NewPrintFlags("drained"), + + IOStreams: ioStreams, backOff: clockwork.NewRealClock(), GracePeriodSeconds: -1, } } -func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - options := NewDrainOptions(f, out, errOut) +func NewCmdDrain(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + options := NewDrainOptions(f, ioStreams) cmd := &cobra.Command{ Use: "drain NODE", @@ -202,7 +213,7 @@ func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Long: drain_long, Example: drain_example, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.SetupDrain(cmd, args)) + cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.RunDrain()) }, } @@ -218,9 +229,9 @@ func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { return cmd } -// SetupDrain populates some fields from the factory, grabs command line +// Complete populates some fields from the factory, grabs command line // arguments and looks up the node using Builder -func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { +func (o *DrainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { var err error if len(args) == 0 && !cmd.Flags().Changed("selector") { @@ -235,7 +246,7 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { o.DryRun = cmdutil.GetDryRunFlag(cmd) - if o.client, err = o.Factory.KubernetesClientSet(); err != nil { + if o.client, err = f.KubernetesClientSet(); err != nil { return err } @@ -245,21 +256,34 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { } } - o.restClient, err = o.Factory.RESTClient() + o.restClient, err = f.RESTClient() if err != nil { return err } o.nodeInfos = []*resource.Info{} - cmdNamespace, _, err := o.Factory.DefaultNamespace() + o.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } - builder := o.Factory.NewBuilder(). + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + if o.DryRun { + o.PrintFlags.Complete("%s (dry run)") + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } + + builder := f.NewBuilder(). Internal(). - NamespaceParam(cmdNamespace).DefaultNamespace(). + NamespaceParam(o.Namespace).DefaultNamespace(). ResourceNames("nodes", args...). SingleResourceType(). Flatten() @@ -294,6 +318,11 @@ func (o *DrainOptions) RunDrain() error { return err } + printer, err := o.ToPrinter("drained") + if err != nil { + return err + } + drainedNodes := sets.NewString() var fatal error @@ -304,7 +333,7 @@ func (o *DrainOptions) RunDrain() error { } if err == nil || o.DryRun { drainedNodes.Insert(info.Name) - cmdutil.PrintSuccess(false, o.Out, info.Object, o.DryRun, "drained") + printer.PrintObj(info.Object, o.Out) } else { fmt.Fprintf(o.ErrOut, "error: unable to drain node %q, aborting command...\n\n", info.Name) remainingNodes := []string{} @@ -620,12 +649,17 @@ func (o *DrainOptions) waitForDelete(pods []corev1.Pod, interval, timeout time.D } else { verbStr = "deleted" } - err := wait.PollImmediate(interval, timeout, func() (bool, error) { + printer, err := o.ToPrinter(verbStr) + if err != nil { + return pods, err + } + + err = wait.PollImmediate(interval, timeout, func() (bool, error) { pendingPods := []corev1.Pod{} for i, pod := range pods { p, err := getPodFn(pod.Namespace, pod.Name) if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) { - cmdutil.PrintSuccess(false, o.Out, &pod, false, verbStr) + printer.PrintObj(&pod, o.Out) continue } else if err != nil { return false, err @@ -677,11 +711,6 @@ func SupportEviction(clientset kubernetes.Interface) (string, error) { // RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for // "Unschedulable" is passed as the first arg. func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { - cmdNamespace, _, err := o.Factory.DefaultNamespace() - if err != nil { - return err - } - cordonOrUncordon := "cordon" if !desired { cordonOrUncordon = "un" + cordonOrUncordon @@ -706,7 +735,12 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { } unsched := node.Spec.Unschedulable if unsched == desired { - cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, already(desired)) + printer, err := o.ToPrinter(already(desired)) + if err != nil { + fmt.Printf("error: %v", err) + continue + } + printer.PrintObj(nodeInfo.AsVersioned(), o.Out) } else { if !o.DryRun { helper := resource.NewHelper(o.restClient, nodeInfo.Mapping) @@ -721,16 +755,26 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) continue } - _, err = helper.Patch(cmdNamespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes) + _, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes) if err != nil { fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) continue } } - cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, changed(desired)) + printer, err := o.ToPrinter(changed(desired)) + if err != nil { + fmt.Fprintf(o.ErrOut, "%v", err) + continue + } + printer.PrintObj(nodeInfo.AsVersioned(), o.Out) } } else { - cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, "skipped") + printer, err := o.ToPrinter("skipped") + if err != nil { + fmt.Fprintf(o.ErrOut, "%v", err) + continue + } + printer.PrintObj(nodeInfo.AsVersioned(), o.Out) } } diff --git a/pkg/kubectl/cmd/drain_test.go b/pkg/kubectl/cmd/drain_test.go index f4cd5984a0..3869b5a35c 100644 --- a/pkg/kubectl/cmd/drain_test.go +++ b/pkg/kubectl/cmd/drain_test.go @@ -32,6 +32,7 @@ import ( "time" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -51,6 +52,7 @@ import ( cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/scheme" + "k8s.io/kubernetes/pkg/printers" ) const ( @@ -84,7 +86,7 @@ func TestCordon(t *testing.T) { description string node *corev1.Node expected *corev1.Node - cmd func(cmdutil.Factory, io.Writer) *cobra.Command + cmd func(cmdutil.Factory, genericclioptions.IOStreams) *cobra.Command arg string expectFatal bool }{ @@ -197,7 +199,7 @@ func TestCordon(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) - cmd := test.cmd(tf, buf) + cmd := test.cmd(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) saw_fatal := false func() { @@ -708,7 +710,7 @@ func TestDrain(t *testing.T) { buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdDrain(tf, buf, errBuf) + cmd := NewCmdDrain(tf, genericclioptions.IOStreams{Out: buf, ErrOut: errBuf}) saw_fatal := false fatal_msg := "" @@ -833,9 +835,18 @@ func TestDeletePods(t *testing.T) { tf := cmdtesting.NewTestFactory() defer tf.Cleanup() - o := DrainOptions{Factory: tf} + o := DrainOptions{ + PrintFlags: printers.NewPrintFlags("drained"), + } o.mapper, _ = tf.Object() o.Out = os.Stdout + + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + return func(obj runtime.Object, out io.Writer) error { + return nil + }, nil + } + _, pods := createPods(false) pendingPods, err := o.waitForDelete(pods, test.interval, test.timeout, false, test.getPodFn) diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index f9ad0fdea5..acb3c73300 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -94,11 +94,11 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra // bind flag structs o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) usage := "to use to edit the resource" cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) cmdutil.AddValidateOptionFlags(cmd, &o.ValidateOptions) - cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.") cmd.Flags().BoolVarP(&o.OutputPatch, "output-patch", "", o.OutputPatch, "Output the patch if the resource is edited.") cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, "Defaults to the line ending native to your platform.") diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index 0cdeab70f3..c39316374c 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -20,10 +20,12 @@ import ( "regexp" "strings" + "github.com/golang/glog" "github.com/spf13/cobra" - "github.com/golang/glog" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -31,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" ) var ( @@ -76,17 +79,37 @@ var ( type ExposeServiceOptions struct { FilenameOptions resource.FilenameOptions RecordFlags *genericclioptions.RecordFlags + PrintFlags *printers.PrintFlags + PrintObj printers.ResourcePrinterFunc + + DryRun bool + EnforceNamespace bool + + Generators func(string) map[string]kubectl.Generator + CanBeExposed func(kind schema.GroupKind) error + ClientForMapping func(*meta.RESTMapping) (resource.RESTClient, error) + MapBasedSelectorForObject func(runtime.Object) (string, error) + PortsForObject func(runtime.Object) ([]string, error) + ProtocolsForObject func(runtime.Object) (map[string]string, error) + LabelsForObject func(runtime.Object) (map[string]string, error) + + Namespace string + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + + Builder *resource.Builder Recorder genericclioptions.Recorder genericclioptions.IOStreams } -func NewExposeServiceOptions(streams genericclioptions.IOStreams) *ExposeServiceOptions { +func NewExposeServiceOptions(ioStreams genericclioptions.IOStreams) *ExposeServiceOptions { return &ExposeServiceOptions{ RecordFlags: genericclioptions.NewRecordFlags(), + PrintFlags: printers.NewPrintFlags("exposed"), Recorder: genericclioptions.NoopRecorder{}, - IOStreams: streams, + IOStreams: ioStreams, } } @@ -107,15 +130,15 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) Example: exposeExample, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd)) - cmdutil.CheckErr(o.RunExpose(f, cmd, args)) + cmdutil.CheckErr(o.RunExpose(cmd, args)) }, ValidArgs: validArgs, ArgAliases: kubectl.ResourceAliases(validArgs), } o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) - cmdutil.AddPrinterFlags(cmd) cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.")) cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'.")) cmd.Flags().String("port", "", i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified")) @@ -140,7 +163,16 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) } func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { - var err error + o.DryRun = cmdutil.GetDryRunFlag(cmd) + + if o.DryRun { + o.PrintFlags.Complete("%s (dry run)") + } + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = printer.PrintObj o.RecordFlags.Complete(f.Command(cmd, false)) o.Recorder, err = o.RecordFlags.ToRecorder() @@ -148,32 +180,41 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e return err } - return err -} + o.Generators = f.Generators + o.Builder = f.NewBuilder() + o.CanBeExposed = f.CanBeExposed + o.ClientForMapping = f.ClientForMapping + o.MapBasedSelectorForObject = f.MapBasedSelectorForObject + o.PortsForObject = f.PortsForObject + o.ProtocolsForObject = f.ProtocolsForObject + o.Mapper, o.Typer = f.Object() + o.LabelsForObject = f.LabelsForObject -func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - namespace, enforceNamespace, err := f.DefaultNamespace() + o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace() if err != nil { return err } - mapper, typer := f.Object() - r := f.NewBuilder(). + return err +} + +func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error { + r := o.Builder. Internal(). ContinueOnError(). - NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, &o.FilenameOptions). + NamespaceParam(o.Namespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() - err = r.Err() + err := r.Err() if err != nil { return cmdutil.UsageErrorf(cmd, err.Error()) } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") - generators := f.Generators("expose") + generators := o.Generators("expose") generator, found := generators[generatorName] if !found { return cmdutil.UsageErrorf(cmd, "generator %q not found.", generatorName) @@ -186,7 +227,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, } mapping := info.ResourceMapping() - if err := f.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil { + if err := o.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil { return err } @@ -200,7 +241,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, // For objects that need a pod selector, derive it from the exposed object in case a user // didn't explicitly specify one via --selector if s, found := params["selector"]; found && kubectl.IsZero(s) { - s, err := f.MapBasedSelectorForObject(info.Object) + s, err := o.MapBasedSelectorForObject(info.Object) if err != nil { return cmdutil.UsageErrorf(cmd, "couldn't retrieve selectors via --selector flag or introspection: %v", err) } @@ -212,7 +253,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, // For objects that need a port, derive it from the exposed object in case a user // didn't explicitly specify one via --port if port, found := params["port"]; found && kubectl.IsZero(port) { - ports, err := f.PortsForObject(info.Object) + ports, err := o.PortsForObject(info.Object) if err != nil { return cmdutil.UsageErrorf(cmd, "couldn't find port via --port flag or introspection: %v", err) } @@ -231,7 +272,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, // Always try to derive protocols from the exposed object, may use // different protocols for different ports. if _, found := params["protocol"]; found { - protocolsMap, err := f.ProtocolsForObject(info.Object) + protocolsMap, err := o.ProtocolsForObject(info.Object) if err != nil { return cmdutil.UsageErrorf(cmd, "couldn't find protocol via introspection: %v", err) } @@ -241,7 +282,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, } if kubectl.IsZero(params["labels"]) { - labels, err := f.LabelsForObject(info.Object) + labels, err := o.LabelsForObject(info.Object) if err != nil { return err } @@ -270,9 +311,9 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, } resourceMapper := &resource.Mapper{ - ObjectTyper: typer, - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + ObjectTyper: o.Typer, + RESTMapper: o.Mapper, + ClientMapper: resource.ClientMapperFunc(o.ClientForMapping), Decoder: cmdutil.InternalVersionDecoder(), } info, err = resourceMapper.InfoForObject(object, nil) @@ -283,29 +324,20 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, glog.V(4).Infof("error recording current command: %v", err) } info.Refresh(object, true) - if cmdutil.GetDryRunFlag(cmd) { - if len(cmdutil.GetFlagString(cmd, "output")) > 0 { - return cmdutil.PrintObject(cmd, object, o.Out) - } - cmdutil.PrintSuccess(false, o.Out, info.Object, true, "exposed") - return nil + if o.DryRun { + return o.PrintObj(object, o.Out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { return err } // Serialize the object with the annotation applied. - object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, object) + object, err = resource.NewHelper(info.Client, info.Mapping).Create(o.Namespace, false, object) if err != nil { return err } - if len(cmdutil.GetFlagString(cmd, "output")) > 0 { - return cmdutil.PrintObject(cmd, object, o.Out) - } - - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "exposed") - return nil + return o.PrintObj(info.AsVersioned(), o.Out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/expose_test.go b/pkg/kubectl/cmd/expose_test.go index 212efb7aac..c021fe77a6 100644 --- a/pkg/kubectl/cmd/expose_test.go +++ b/pkg/kubectl/cmd/expose_test.go @@ -79,7 +79,7 @@ func TestRunExposeService(t *testing.T) { Selector: map[string]string{"app": "go"}, }, }, - expected: "service \"foo\" exposed", + expected: "service/foo exposed", status: 200, }, { @@ -110,7 +110,7 @@ func TestRunExposeService(t *testing.T) { Selector: map[string]string{"func": "stream"}, }, }, - expected: "service \"foo\" exposed", + expected: "service/foo exposed", status: 200, }, { @@ -142,7 +142,7 @@ func TestRunExposeService(t *testing.T) { Selector: map[string]string{"run": "this"}, }, }, - expected: "service \"mayor\" exposed", + expected: "service/mayor exposed", status: 200, }, { @@ -237,7 +237,7 @@ func TestRunExposeService(t *testing.T) { ClusterIP: "10.10.10.10", }, }, - expected: "service \"foo\" exposed", + expected: "service /foo exposed", status: 200, }, { @@ -269,7 +269,7 @@ func TestRunExposeService(t *testing.T) { ClusterIP: api.ClusterIPNone, }, }, - expected: "service \"foo\" exposed", + expected: "service/foo exposed", status: 200, }, { @@ -295,7 +295,7 @@ func TestRunExposeService(t *testing.T) { ClusterIP: api.ClusterIPNone, }, }, - expected: "service \"foo\" exposed", + expected: "service/foo exposed", status: 200, }, { @@ -353,7 +353,7 @@ func TestRunExposeService(t *testing.T) { Selector: map[string]string{"svc": "frompod"}, }, }, - expected: "service \"a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand\" exposed", + expected: "service/a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand exposed", status: 200, }, { @@ -500,7 +500,7 @@ func TestRunExposeService(t *testing.T) { out := buf.String() if _, ok := test.flags["dry-run"]; ok { - test.expected = fmt.Sprintf("service %q exposed (dry run)", test.flags["name"]) + test.expected = fmt.Sprintf("service/%s exposed (dry run)", test.flags["name"]) } if !strings.Contains(out, test.expected) { diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 14d3f25e77..4592b9e689 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "reflect" "strings" @@ -40,6 +39,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" ) // LabelOptions have the data required to perform the label operation @@ -48,6 +48,9 @@ type LabelOptions struct { resource.FilenameOptions RecordFlags *genericclioptions.RecordFlags + PrintFlags *printers.PrintFlags + ToPrinter func(string) (printers.ResourcePrinterFunc, error) + // Common user flags overwrite bool list bool @@ -66,8 +69,7 @@ type LabelOptions struct { Recorder genericclioptions.Recorder // Common shared fields - out io.Writer - errout io.Writer + genericclioptions.IOStreams } var ( @@ -100,19 +102,19 @@ var ( kubectl label pods foo bar-`)) ) -func NewLabelOptions(out, errOut io.Writer) *LabelOptions { +func NewLabelOptions(ioStreams genericclioptions.IOStreams) *LabelOptions { return &LabelOptions{ RecordFlags: genericclioptions.NewRecordFlags(), + Recorder: genericclioptions.NoopRecorder{}, - Recorder: genericclioptions.NoopRecorder{}, + PrintFlags: printers.NewPrintFlags("labeled"), - out: out, - errout: errOut, + IOStreams: ioStreams, } } -func NewCmdLabel(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - o := NewLabelOptions(out, errOut) +func NewCmdLabel(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewLabelOptions(ioStreams) validArgs := cmdutil.ValidArgList(f) @@ -136,8 +138,8 @@ func NewCmdLabel(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { } o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) - cmdutil.AddPrinterFlags(cmd) cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.") cmd.Flags().BoolVar(&o.list, "list", o.list, "If true, display the labels for a given resource.") cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, label will NOT contact api-server but run locally.") @@ -165,6 +167,19 @@ func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.dryrun = cmdutil.GetDryRunFlag(cmd) + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + if o.dryrun { + o.PrintFlags.Complete("%s (dry run)") + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } + resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label") if err != nil { return err @@ -255,7 +270,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { } for _, label := range o.removeLabels { if _, ok := accessor.GetLabels()[label]; !ok { - fmt.Fprintf(o.out, "label %q not found.\n", label) + fmt.Fprintf(o.Out, "label %q not found.\n", label) } } @@ -304,19 +319,20 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { indent := "" if !one { indent = " " - fmt.Fprintf(o.errout, "Listing labels for %s.%s/%s:\n", info.Mapping.GroupVersionKind.Kind, info.Mapping.GroupVersionKind.Group, info.Name) + fmt.Fprintf(o.ErrOut, "Listing labels for %s.%s/%s:\n", info.Mapping.GroupVersionKind.Kind, info.Mapping.GroupVersionKind.Group, info.Name) } for k, v := range accessor.GetLabels() { - fmt.Fprintf(o.out, "%s%s=%s\n", indent, k, v) + fmt.Fprintf(o.Out, "%s%s=%s\n", indent, k, v) } return nil } - if len(o.outputFormat) > 0 { - return cmdutil.PrintObject(cmd, outputObj, o.out) + printer, err := o.ToPrinter(dataChangeMsg) + if err != nil { + return err } - cmdutil.PrintSuccess(false, o.out, info.Object, o.dryrun, dataChangeMsg) + printer.PrintObj(info.AsVersioned(), o.Out) return nil }) } diff --git a/pkg/kubectl/cmd/label_test.go b/pkg/kubectl/cmd/label_test.go index 4237b901be..fdf2630e79 100644 --- a/pkg/kubectl/cmd/label_test.go +++ b/pkg/kubectl/cmd/label_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api/legacyscheme" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -328,13 +329,13 @@ func TestLabelErrors(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdLabel(tf, buf, buf) + cmd := NewCmdLabel(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) cmd.SetOutput(buf) for k, v := range testCase.flags { cmd.Flags().Set(k, v) } - opts := NewLabelOptions(buf, buf) + opts := NewLabelOptions(genericclioptions.IOStreams{Out: buf, ErrOut: buf}) err := opts.Complete(tf, cmd, testCase.args) if err == nil { err = opts.Validate() @@ -390,8 +391,8 @@ func TestLabelForResourceFromFile(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdLabel(tf, buf, buf) - opts := NewLabelOptions(buf, buf) + cmd := NewCmdLabel(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) + opts := NewLabelOptions(genericclioptions.IOStreams{Out: buf, ErrOut: buf}) opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} err := opts.Complete(tf, cmd, []string{"a=b"}) if err == nil { @@ -423,8 +424,8 @@ func TestLabelLocal(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdLabel(tf, buf, buf) - opts := NewLabelOptions(buf, buf) + cmd := NewCmdLabel(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) + opts := NewLabelOptions(genericclioptions.IOStreams{Out: buf, ErrOut: buf}) opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} opts.local = true err := opts.Complete(tf, cmd, []string{"a=b"}) @@ -481,9 +482,9 @@ func TestLabelMultipleObjects(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) - opts := NewLabelOptions(buf, buf) + opts := NewLabelOptions(genericclioptions.IOStreams{Out: buf, ErrOut: buf}) opts.all = true - cmd := NewCmdLabel(tf, buf, buf) + cmd := NewCmdLabel(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf}) err := opts.Complete(tf, cmd, []string{"pods", "a=b"}) if err == nil { err = opts.Validate() diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index e65f3a1442..26e0ea034c 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -25,6 +25,7 @@ import ( "time" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index 96627603c7..9e959cdf06 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" ) var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge": types.MergePatchType, "strategic": types.StrategicMergePatchType} @@ -49,6 +50,8 @@ var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge" type PatchOptions struct { resource.FilenameOptions RecordFlags *genericclioptions.RecordFlags + PrintFlags *printers.PrintFlags + ToPrinter func(string) (printers.ResourcePrinterFunc, error) Local bool DryRun bool @@ -86,8 +89,8 @@ var ( func NewPatchOptions() *PatchOptions { return &PatchOptions{ RecordFlags: genericclioptions.NewRecordFlags(), - - Recorder: genericclioptions.NoopRecorder{}, + Recorder: genericclioptions.NoopRecorder{}, + PrintFlags: printers.NewPrintFlags("patched"), } } @@ -110,11 +113,11 @@ func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command { } o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.MarkFlagRequired("patch") cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List())) - cmdutil.AddPrinterFlags(cmd) cmdutil.AddDryRunFlag(cmd) usage := "identifying the resource to update" @@ -137,6 +140,19 @@ func (o *PatchOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { o.OutputFormat = cmdutil.GetFlagString(cmd, "output") o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + if o.DryRun { + o.PrintFlags.Complete("%s (dry run)") + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } + return err } @@ -222,10 +238,11 @@ func (o *PatchOptions) RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Com return err } - if len(o.OutputFormat) > 0 && o.OutputFormat != "name" { - return cmdutil.PrintObject(cmd, info.Object, out) + printer, err := o.ToPrinter(patchOperation(didPatch)) + if err != nil { + return err } - cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, false, patchOperation(didPatch)) + printer.PrintObj(info.AsVersioned(), out) // if object was not successfully patched, exit with error code 1 if !didPatch { @@ -264,12 +281,11 @@ func (o *PatchOptions) RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Com } } - if len(o.OutputFormat) > 0 && o.OutputFormat != "name" { - return cmdutil.PrintObject(cmd, info.Object, out) + printer, err := o.ToPrinter(patchOperation(didPatch)) + if err != nil { + return err } - - cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, o.DryRun, patchOperation(didPatch)) - return nil + return printer.PrintObj(info.AsVersioned(), out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go index e4737f6353..2bd24fd549 100644 --- a/pkg/kubectl/cmd/patch_test.go +++ b/pkg/kubectl/cmd/patch_test.go @@ -144,7 +144,7 @@ func TestPatchNoop(t *testing.T) { cmd.Flags().Set("namespace", "test") cmd.Flags().Set("patch", `{"metadata":{"annotations":{"foo":"bar"}}}`) cmd.Run(cmd, []string{"services", "frontend"}) - if buf.String() != "service \"baz\" patched\n" { + if buf.String() != "service/baz patched\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml index 56b93abd66..e1e80d5d18 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml @@ -5,8 +5,8 @@ args: - service/svc1 namespace: "myproject" expectedStdout: -- configmap "cm1" edited -- service "svc1" edited +- configmap/cm1 edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml index ef9f5588de..9236dc984f 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml @@ -5,8 +5,8 @@ args: - service/svc1 namespace: "myproject" expectedStdout: -- configmap "cm1" edited -- service "svc1" edited +- configmap/cm1 edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml index f500dc65ed..df1a5db832 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml @@ -4,7 +4,7 @@ args: - service/svc1 namespace: myproject expectedStdout: -- "service \"svc1\" edited" +- "service/svc1 edited" expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml index a284da093f..73d8fd98b2 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml @@ -6,7 +6,7 @@ args: outputFormat: yaml namespace: myproject expectedStdout: -- service "svc1" edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/test.yaml index 5663b94d91..e0bfe91f62 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/test.yaml @@ -3,7 +3,7 @@ mode: create filename: "svc.yaml" namespace: "edit-test" expectedStdout: -- "service \"svc1\" created" +- "service/svc1 created" expectedStderr: - "\"svc2\" is invalid" expectedExitCode: 1 diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-create-list/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-create-list/test.yaml index 0b75c7ffe6..a3198e7919 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-create-list/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-create-list/test.yaml @@ -3,8 +3,8 @@ mode: create filename: "svc.yaml" namespace: "edit-test" expectedStdout: -- service "svc1" created -- service "svc2" created +- service/svc1 created +- service/svc2 created expectedExitCode: 0 steps: - type: edit diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/test.yaml index 8ead267075..0cc3a37051 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-error-reedit/test.yaml @@ -5,7 +5,7 @@ args: - svc1 namespace: edit-test expectedStdout: -- service "svc1" edited +- service/svc1 edited expectedStderr: - "error: services \"svc1\" is invalid" expectedExitCode: 0 diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml index 0b0f92f975..1a6e13c501 100644 --- a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml @@ -11,7 +11,7 @@ outputPatch: "true" namespace: edit-test expectedStdout: - 'Patch: {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"},"labels":{"new-label":"new-value"}}}' -- service "svc1" edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/test.yaml index 03f6ca9041..a55cff618d 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/test.yaml @@ -4,8 +4,8 @@ args: - configmaps,services namespace: "edit-test" expectedStdout: -- configmap "cm1" edited -- service "svc1" edited +- configmap/cm1 edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/test.yaml index 1ee9e35faf..c4fd1ae171 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/test.yaml @@ -5,8 +5,8 @@ args: - service/svc1 namespace: "edit-test" expectedStdout: -- configmap "cm1" edited -- service "svc1" edited +- configmap/cm1 edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-list/test.yaml index 1ee9e35faf..c4fd1ae171 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list/test.yaml @@ -5,8 +5,8 @@ args: - service/svc1 namespace: "edit-test" expectedStdout: -- configmap "cm1" edited -- service "svc1" edited +- configmap/cm1 edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/test.yaml index 01315f66c5..a78dc296a5 100644 --- a/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-not-update-annotation/test.yaml @@ -9,7 +9,7 @@ args: saveConfig: "false" namespace: edit-test expectedStdout: -- service "svc1" edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-schemaless-list/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-schemaless-list/test.yaml index 3aa57d07f0..726e747352 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-schemaless-list/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-schemaless-list/test.yaml @@ -6,9 +6,9 @@ args: - bars/test2 namespace: default expectedStdout: -- "service \"kubernetes\" edited" -- "bar.company.com \"test\" edited" -- "bar.company.com \"test2\" edited" +- "service/kubernetes edited" +- "bar.company.com/test edited" +- "bar.company.com/test2 edited" expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-single-service/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-single-service/test.yaml index 5b48baa965..f1f66de235 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-single-service/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-single-service/test.yaml @@ -8,7 +8,7 @@ args: - svc1 namespace: edit-test expectedStdout: -- service "svc1" edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-syntax-error/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-syntax-error/test.yaml index a37c25139c..6365571399 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-syntax-error/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-syntax-error/test.yaml @@ -4,7 +4,7 @@ args: - service/kubernetes namespace: default expectedStdout: -- "service \"kubernetes\" edited" +- "service/kubernetes edited" expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-unknown-field-known-group-kind/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-unknown-field-known-group-kind/test.yaml index fc099fcae7..5b119e6116 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-unknown-field-known-group-kind/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-unknown-field-known-group-kind/test.yaml @@ -4,7 +4,7 @@ args: - storageclasses.v1beta1.storage.k8s.io/foo namespace: default expectedStdout: -- "storageclass.storage.k8s.io \"foo\" edited" +- "storageclass.storage.k8s.io/foo edited" expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-unknown-version-known-group-kind/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-unknown-version-known-group-kind/test.yaml index cfc8dccf33..b0c78e05c5 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-unknown-version-known-group-kind/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-unknown-version-known-group-kind/test.yaml @@ -4,7 +4,7 @@ args: - storageclasses.v0.storage.k8s.io/foo namespace: default expectedStdout: -- "storageclass.storage.k8s.io \"foo\" edited" +- "storageclass.storage.k8s.io/foo edited" expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/test.yaml index 928c10642d..1e01c83698 100644 --- a/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/test.yaml +++ b/pkg/kubectl/cmd/testdata/edit/testcase-update-annotation/test.yaml @@ -9,7 +9,7 @@ args: saveConfig: "true" namespace: edit-test expectedStdout: -- service "svc1" edited +- service/svc1 edited expectedExitCode: 0 steps: - type: request diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index 227c79fe5e..dd16245521 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -57,6 +57,9 @@ type EditOptions struct { resource.FilenameOptions RecordFlags *genericclioptions.RecordFlags + PrintFlags *printers.PrintFlags + ToPrinter func(string) (printers.ResourcePrinterFunc, error) + Output string OutputPatch bool WindowsLineEndings bool @@ -86,12 +89,14 @@ func NewEditOptions(editMode EditMode, ioStreams genericclioptions.IOStreams) *E EditMode: editMode, - Output: "yaml", + PrintFlags: printers.NewPrintFlags("edited"), + WindowsLineEndings: goruntime.GOOS == "windows", Recorder: genericclioptions.NoopRecorder{}, IOStreams: ioStreams, + Output: "yaml", } } @@ -159,6 +164,15 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm Do() } + o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) { + o.PrintFlags.NamePrintFlags.Operation = operation + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } + o.CmdNamespace = cmdNamespace o.f = f @@ -423,14 +437,23 @@ func (o *EditOptions) visitToApplyEditPatch(originalInfos []*resource.Info, patc } if reflect.DeepEqual(originalJS, editedJS) { - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "skipped") + printer, err := o.ToPrinter("skipped") + if err != nil { + return err + } + printer.PrintObj(info.AsVersioned(), o.Out) return nil } else { err := o.annotationPatch(info) if err != nil { return err } - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "edited") + + printer, err := o.ToPrinter("edited") + if err != nil { + return err + } + printer.PrintObj(info.AsVersioned(), o.Out) return nil } }) @@ -549,7 +572,11 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor if reflect.DeepEqual(originalJS, editedJS) { // no edit, so just skip it. - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "skipped") + printer, err := o.ToPrinter("skipped") + if err != nil { + return err + } + printer.PrintObj(info.AsVersioned(), o.Out) return nil } @@ -603,7 +630,11 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor return nil } info.Refresh(patched, true) - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "edited") + printer, err := o.ToPrinter("edited") + if err != nil { + return err + } + printer.PrintObj(info.AsVersioned(), o.Out) return nil }) return err @@ -614,7 +645,11 @@ func (o *EditOptions) visitToCreate(createVisitor resource.Visitor) error { if err := resource.CreateAndRefresh(info); err != nil { return err } - cmdutil.PrintSuccess(false, o.Out, info.Object, false, "created") + printer, err := o.ToPrinter("created") + if err != nil { + return err + } + printer.PrintObj(info.AsVersioned(), o.Out) return nil }) return err diff --git a/pkg/printers/flags.go b/pkg/printers/flags.go index 6fcea1cbf0..95e45f8298 100644 --- a/pkg/printers/flags.go +++ b/pkg/printers/flags.go @@ -79,6 +79,20 @@ func (f *PrintFlags) AddFlags(cmd *cobra.Command) { } } +// 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 := ""