diff --git a/pkg/kubectl/cmd/alpha.go b/pkg/kubectl/cmd/alpha.go index afbe7347dc0..bd7f34e66ac 100644 --- a/pkg/kubectl/cmd/alpha.go +++ b/pkg/kubectl/cmd/alpha.go @@ -17,17 +17,16 @@ limitations under the License. package cmd import ( - "io" - "github.com/spf13/cobra" "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" ) // NewCmdAlpha creates a command that acts as an alternate root command for features in alpha -func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command { +func NewCmdAlpha(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "alpha", Short: i18n.T("Commands for features in alpha"), @@ -37,7 +36,7 @@ func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Com // Alpha commands should be added here. As features graduate from alpha they should move // from here to the CommandGroups defined by NewKubeletCommand() in cmd.go. //cmd.AddCommand(NewCmdDebug(f, in, out, err)) - cmd.AddCommand(NewCmdDiff(f, out, err)) + cmd.AddCommand(NewCmdDiff(f, streams)) // NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding // the help function ensures a reasonable message if someone types the hidden command anyway. diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 752b919d86b..fb2005f9704 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -197,7 +197,7 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return err } - o.DeleteOptions = o.DeleteFlags.ToOptions(o.Out, o.ErrOut) + o.DeleteOptions = o.DeleteFlags.ToOptions(o.IOStreams) return nil } diff --git a/pkg/kubectl/cmd/attach.go b/pkg/kubectl/cmd/attach.go index 569cfec9245..1171c307203 100644 --- a/pkg/kubectl/cmd/attach.go +++ b/pkg/kubectl/cmd/attach.go @@ -35,6 +35,7 @@ import ( coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -60,12 +61,10 @@ const ( defaultPodLogsTimeout = 20 * time.Second ) -func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { - options := &AttachOptions{ +func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := &AttachOptions{ StreamOptions: StreamOptions{ - In: cmdIn, - Out: cmdOut, - Err: cmdErr, + IOStreams: streams, }, Attach: &DefaultRemoteAttach{}, @@ -77,15 +76,15 @@ func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) Long: "Attach to a process that is already running inside an existing container.", Example: attachExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(f, cmd, args)) - cmdutil.CheckErr(options.Validate()) - cmdutil.CheckErr(options.Run()) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) }, } cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout) - cmd.Flags().StringVarP(&options.ContainerName, "container", "c", options.ContainerName, "Container name. If omitted, the first container in the pod will be chosen") - cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", options.Stdin, "Pass stdin to the container") - cmd.Flags().BoolVarP(&options.TTY, "tty", "t", options.TTY, "Stdin is a TTY") + cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen") + cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container") + cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY") return cmd } @@ -203,7 +202,7 @@ func (p *AttachOptions) Validate() error { if len(p.PodName) == 0 { allErrs = append(allErrs, errors.New("pod name must be specified")) } - if p.Out == nil || p.Err == nil { + if p.Out == nil || p.ErrOut == nil { allErrs = append(allErrs, errors.New("both output and error output must be provided")) } if p.Attach == nil || p.PodClient == nil || p.Config == nil { @@ -236,8 +235,8 @@ func (p *AttachOptions) Run() error { } if p.TTY && !containerToAttach.TTY { p.TTY = false - if p.Err != nil { - fmt.Fprintf(p.Err, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) + if p.ErrOut != nil { + fmt.Fprintf(p.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) } } else if !p.TTY && containerToAttach.TTY { // the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get @@ -249,7 +248,7 @@ func (p *AttachOptions) Run() error { t := p.setupTTY() // save p.Err so we can print the command prompt message below - stderr := p.Err + stderr := p.ErrOut var sizeQueue remotecommand.TerminalSizeQueue if t.Raw { @@ -266,7 +265,7 @@ func (p *AttachOptions) Run() error { // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is // true - p.Err = nil + p.ErrOut = nil } fn := func() error { @@ -284,11 +283,11 @@ func (p *AttachOptions) Run() error { Container: containerToAttach.Name, Stdin: p.Stdin, Stdout: p.Out != nil, - Stderr: p.Err != nil, + Stderr: p.ErrOut != nil, TTY: t.Raw, }, legacyscheme.ParameterCodec) - return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue) + return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue) } if !p.Quiet && stderr != nil { @@ -322,8 +321,8 @@ func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error } if len(p.SuggestedCmdUsage) > 0 { - fmt.Fprintf(p.Err, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name) - fmt.Fprintf(p.Err, "%s\n", p.SuggestedCmdUsage) + fmt.Fprintf(p.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name) + fmt.Fprintf(p.ErrOut, "%s\n", p.SuggestedCmdUsage) } glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) diff --git a/pkg/kubectl/cmd/attach_test.go b/pkg/kubectl/cmd/attach_test.go index 603487def6e..29a452c4a62 100644 --- a/pkg/kubectl/cmd/attach_test.go +++ b/pkg/kubectl/cmd/attach_test.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "bytes" "fmt" "io" "net/http" @@ -38,6 +37,7 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -249,9 +249,6 @@ func TestAttach(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} - bufOut := bytes.NewBuffer([]byte{}) - bufErr := bytes.NewBuffer([]byte{}) - bufIn := bytes.NewBuffer([]byte{}) remoteAttach := &fakeRemoteAttach{} if test.remoteAttachErr { remoteAttach.err = fmt.Errorf("attach error") @@ -259,9 +256,7 @@ func TestAttach(t *testing.T) { params := &AttachOptions{ StreamOptions: StreamOptions{ ContainerName: test.container, - In: bufIn, - Out: bufOut, - Err: bufErr, + IOStreams: genericclioptions.NewTestIOStreamsDiscard(), }, Attach: remoteAttach, GetPodTimeout: 1000, @@ -342,16 +337,12 @@ func TestAttachWarnings(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} - bufOut := bytes.NewBuffer([]byte{}) - bufErr := bytes.NewBuffer([]byte{}) - bufIn := bytes.NewBuffer([]byte{}) + streams, _, _, bufErr := genericclioptions.NewTestIOStreams() ex := &fakeRemoteAttach{} params := &AttachOptions{ StreamOptions: StreamOptions{ ContainerName: test.container, - In: bufIn, - Out: bufOut, - Err: bufErr, + IOStreams: streams, Stdin: test.stdin, TTY: test.tty, }, diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 8f1bf016974..3a8cb5b2ca9 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -275,7 +275,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { NewCmdExplain("kubectl", f, ioStreams), get.NewCmdGet("kubectl", f, ioStreams), NewCmdEdit(f, ioStreams), - NewCmdDelete(f, out, err), + NewCmdDelete(f, ioStreams), }, }, { @@ -292,7 +292,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { Commands: []*cobra.Command{ NewCmdCertificate(f, ioStreams), NewCmdClusterInfo(f, ioStreams), - NewCmdTop(f, out, err), + NewCmdTop(f, ioStreams), NewCmdCordon(f, ioStreams), NewCmdUncordon(f, ioStreams), NewCmdDrain(f, ioStreams), @@ -303,11 +303,11 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { Message: "Troubleshooting and Debugging Commands:", Commands: []*cobra.Command{ NewCmdDescribe("kubectl", f, ioStreams), - NewCmdLogs(f, out, err), - NewCmdAttach(f, in, out, err), - NewCmdExec(f, in, out, err), - NewCmdPortForward(f, out, err), - NewCmdProxy(f, out), + NewCmdLogs(f, ioStreams), + NewCmdAttach(f, ioStreams), + NewCmdExec(f, ioStreams), + NewCmdPortForward(f, ioStreams), + NewCmdProxy(f, ioStreams), NewCmdCp(f, ioStreams), auth.NewCmdAuth(f, ioStreams), }, @@ -317,7 +317,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { Commands: []*cobra.Command{ NewCmdApply("kubectl", f, ioStreams), NewCmdPatch(f, ioStreams), - NewCmdReplace(f, out, err), + NewCmdReplace(f, ioStreams), NewCmdConvert(f, ioStreams), }, }, @@ -326,7 +326,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { Commands: []*cobra.Command{ NewCmdLabel(f, ioStreams), NewCmdAnnotate("kubectl", f, ioStreams), - NewCmdCompletion(out, ""), + NewCmdCompletion(ioStreams.Out, ""), }, }, } @@ -335,7 +335,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { filters := []string{"options"} // Hide the "alpha" subcommand if there are no alpha commands in this build. - alpha := NewCmdAlpha(f, in, out, err) + alpha := NewCmdAlpha(f, ioStreams) if !alpha.HasSubCommands() { filters = append(filters, alpha.Name()) } @@ -356,11 +356,11 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { cmds.AddCommand(alpha) cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams)) - cmds.AddCommand(NewCmdPlugin(f, in, out, err)) + cmds.AddCommand(NewCmdPlugin(f, ioStreams)) cmds.AddCommand(NewCmdVersion(f, ioStreams)) cmds.AddCommand(NewCmdApiVersions(f, ioStreams)) cmds.AddCommand(NewCmdApiResources(f, ioStreams)) - cmds.AddCommand(NewCmdOptions(out)) + cmds.AddCommand(NewCmdOptions(ioStreams.Out)) return cmds } diff --git a/pkg/kubectl/cmd/cp.go b/pkg/kubectl/cmd/cp.go index 258b4f50a78..5a919abfbbb 100644 --- a/pkg/kubectl/cmd/cp.go +++ b/pkg/kubectl/cmd/cp.go @@ -18,7 +18,6 @@ package cmd import ( "archive/tar" - "bytes" "errors" "fmt" "io" @@ -35,6 +34,8 @@ import ( "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "bytes" + "github.com/renstrom/dedent" "github.com/spf13/cobra" ) @@ -194,8 +195,10 @@ func (o *CopyOptions) Run(args []string) error { func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error { options := &ExecOptions{ StreamOptions: StreamOptions{ - Out: bytes.NewBuffer([]byte{}), - Err: bytes.NewBuffer([]byte{}), + IOStreams: genericclioptions.IOStreams{ + Out: bytes.NewBuffer([]byte{}), + ErrOut: bytes.NewBuffer([]byte{}), + }, Namespace: dest.PodNamespace, PodName: dest.PodName, @@ -240,9 +243,11 @@ func (o *CopyOptions) copyToPod(src, dest fileSpec) error { options := &ExecOptions{ StreamOptions: StreamOptions{ - In: reader, - Out: o.Out, - Err: o.ErrOut, + IOStreams: genericclioptions.IOStreams{ + In: reader, + Out: o.Out, + ErrOut: o.ErrOut, + }, Stdin: true, Namespace: dest.PodNamespace, @@ -263,9 +268,11 @@ func (o *CopyOptions) copyFromPod(src, dest fileSpec) error { reader, outStream := io.Pipe() options := &ExecOptions{ StreamOptions: StreamOptions{ - In: nil, - Out: outStream, - Err: o.Out, + IOStreams: genericclioptions.IOStreams{ + In: nil, + Out: outStream, + ErrOut: o.Out, + }, Namespace: src.PodNamespace, PodName: src.PodName, diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index b9471f0d295..74b165abc4a 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "strings" "time" @@ -31,6 +30,7 @@ import ( "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/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -109,11 +109,10 @@ type DeleteOptions struct { Mapper meta.RESTMapper Result *resource.Result - Out io.Writer - ErrOut io.Writer + genericclioptions.IOStreams } -func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { +func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { deleteFlags := NewDeleteCommandFlags("containing the resource to delete.") cmd := &cobra.Command{ @@ -123,14 +122,14 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Long: delete_long, Example: delete_example, Run: func(cmd *cobra.Command, args []string) { - options := deleteFlags.ToOptions(out, errOut) - if err := options.Complete(f, out, errOut, args, cmd); err != nil { + o := deleteFlags.ToOptions(streams) + if err := o.Complete(f, args, cmd); err != nil { cmdutil.CheckErr(err) } - if err := options.Validate(cmd); err != nil { + if err := o.Validate(cmd); err != nil { cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error())) } - if err := options.RunDelete(); err != nil { + if err := o.RunDelete(); err != nil { cmdutil.CheckErr(err) } }, @@ -143,7 +142,7 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { return cmd } -func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string, cmd *cobra.Command) error { +func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -195,10 +194,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args return err } - // Set up writer - o.Out = out - o.ErrOut = errOut - return nil } diff --git a/pkg/kubectl/cmd/delete_flags.go b/pkg/kubectl/cmd/delete_flags.go index 10cc3d85ea8..b1330db58eb 100644 --- a/pkg/kubectl/cmd/delete_flags.go +++ b/pkg/kubectl/cmd/delete_flags.go @@ -17,12 +17,12 @@ limitations under the License. package cmd import ( - "io" "time" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -72,10 +72,9 @@ type DeleteFlags struct { Output *string } -func (f *DeleteFlags) ToOptions(out, errOut io.Writer) *DeleteOptions { +func (f *DeleteFlags) ToOptions(streams genericclioptions.IOStreams) *DeleteOptions { options := &DeleteOptions{ - Out: out, - ErrOut: errOut, + IOStreams: streams, } // add filename options diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index faf8d7f8fbb..9d2ef02a47b 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "bytes" "encoding/json" "io" "io/ioutil" @@ -38,6 +37,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -84,8 +84,8 @@ func TestDeleteObjectByTuple(t *testing.T) { } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd := NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -95,8 +95,8 @@ func TestDeleteObjectByTuple(t *testing.T) { } // Test cascading delete of object without client-side reaper doesn't make GET requests - buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd = NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ = genericclioptions.NewTestIOStreams() + cmd = NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"secrets/mysecret"}) @@ -147,8 +147,8 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) { // DeleteOptions.OrphanDependents should be false, when cascade is true (default). falseVar := false expectedOrphanDependents = &falseVar - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd := NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"secrets/mysecret"}) @@ -159,8 +159,8 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) { // Test that delete options should be set to orphan when cascade is false. trueVar := true expectedOrphanDependents = &trueVar - buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd = NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ = genericclioptions.NewTestIOStreams() + cmd = NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -202,8 +202,8 @@ func TestDeleteNamedObject(t *testing.T) { } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd := NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -213,8 +213,8 @@ func TestDeleteNamedObject(t *testing.T) { } // Test cascading delete of object without client-side reaper doesn't make GET requests - buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd = NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ = genericclioptions.NewTestIOStreams() + cmd = NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -246,9 +246,9 @@ func TestDeleteObject(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmd := NewCmdDelete(tf, buf, errBuf) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -318,11 +318,11 @@ func TestDeleteObjectGraceZero(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) reaper := &fakeReaper{} fake := &fakeReaperFactory{Factory: tf, reaper: reaper} - cmd := NewCmdDelete(fake, buf, errBuf) + streams, _, buf, errBuf := genericclioptions.NewTestIOStreams() + cmd := NewCmdDelete(fake, streams) cmd.Flags().Set("output", "name") cmd.Flags().Set("grace-period", "0") cmd.Run(cmd, []string{"pods/nginx"}) @@ -357,7 +357,6 @@ func TestDeleteObjectNotFound(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) options := &DeleteOptions{ FilenameOptions: resource.FilenameOptions{ @@ -366,8 +365,9 @@ func TestDeleteObjectNotFound(t *testing.T) { GracePeriod: -1, Cascade: false, Output: "name", + IOStreams: genericclioptions.NewTestIOStreamsDiscard(), } - err := options.Complete(tf, buf, errBuf, []string{}, fakecmd()) + err := options.Complete(tf, []string{}, fakecmd()) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -395,9 +395,9 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("ignore-not-found", "true") @@ -438,7 +438,6 @@ func TestDeleteAllNotFound(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) // Make sure we can explicitly choose to fail on NotFound errors, even with --all options := &DeleteOptions{ @@ -448,8 +447,9 @@ func TestDeleteAllNotFound(t *testing.T) { DeleteAll: true, IgnoreNotFound: false, Output: "name", + IOStreams: genericclioptions.NewTestIOStreamsDiscard(), } - err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd()) + err := options.Complete(tf, []string{"services"}, fakecmd()) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -489,9 +489,9 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("all", "true") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -526,9 +526,9 @@ func TestDeleteMultipleObject(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml") cmd.Flags().Set("cascade", "false") @@ -564,7 +564,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() options := &DeleteOptions{ FilenameOptions: resource.FilenameOptions{ @@ -573,8 +573,9 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { GracePeriod: -1, Cascade: false, Output: "name", + IOStreams: streams, } - err := options.Complete(tf, buf, errBuf, []string{}, fakecmd()) + err := options.Complete(tf, []string{}, fakecmd()) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -616,9 +617,9 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -650,9 +651,9 @@ func TestDeleteDirectory(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -697,9 +698,9 @@ func TestDeleteMultipleSelector(t *testing.T) { }), } tf.Namespace = "test" - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdDelete(tf, buf, errBuf) + cmd := NewCmdDelete(tf, streams) cmd.Flags().Set("selector", "a=b") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -742,15 +743,15 @@ func TestResourceErrors(t *testing.T) { tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - + streams, _, buf, _ := genericclioptions.NewTestIOStreams() options := &DeleteOptions{ FilenameOptions: resource.FilenameOptions{}, GracePeriod: -1, Cascade: false, Output: "name", + IOStreams: streams, } - err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd()) + err := options.Complete(tf, testCase.args, fakecmd()) if !testCase.errFn(err) { t.Errorf("%s: unexpected error: %v", k, err) return diff --git a/pkg/kubectl/cmd/diff.go b/pkg/kubectl/cmd/diff.go index 43440f54907..d0cfe99d268 100644 --- a/pkg/kubectl/cmd/diff.go +++ b/pkg/kubectl/cmd/diff.go @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/apply/strategy" "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/utils/exec" @@ -101,12 +102,11 @@ func parseDiffArguments(args []string) (string, string, error) { return from, to, nil } -func NewCmdDiff(f cmdutil.Factory, stdout, stderr io.Writer) *cobra.Command { +func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { var options DiffOptions diff := DiffProgram{ - Exec: exec.New(), - Stdout: stdout, - Stderr: stderr, + Exec: exec.New(), + IOStreams: streams, } cmd := &cobra.Command{ Use: "diff -f FILENAME", @@ -132,9 +132,8 @@ func NewCmdDiff(f cmdutil.Factory, stdout, stderr io.Writer) *cobra.Command { // KUBERNETES_EXTERNAL_DIFF environment variable will be used a diff // program. By default, `diff(1)` will be used. type DiffProgram struct { - Exec exec.Interface - Stdout io.Writer - Stderr io.Writer + Exec exec.Interface + genericclioptions.IOStreams } func (d *DiffProgram) getCommand(args ...string) exec.Cmd { @@ -147,8 +146,8 @@ func (d *DiffProgram) getCommand(args ...string) exec.Cmd { } cmd := d.Exec.Command(diff, args...) - cmd.SetStdout(d.Stdout) - cmd.SetStderr(d.Stderr) + cmd.SetStdout(d.Out) + cmd.SetStderr(d.ErrOut) return cmd } diff --git a/pkg/kubectl/cmd/diff_test.go b/pkg/kubectl/cmd/diff_test.go index 4400023ff3e..302e69e5a99 100644 --- a/pkg/kubectl/cmd/diff_test.go +++ b/pkg/kubectl/cmd/diff_test.go @@ -25,6 +25,7 @@ import ( "strings" "testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/utils/exec" ) @@ -137,11 +138,10 @@ func TestArguments(t *testing.T) { func TestDiffProgram(t *testing.T) { os.Setenv("KUBERNETES_EXTERNAL_DIFF", "echo") - stdout := bytes.Buffer{} + streams, _, stdout, _ := genericclioptions.NewTestIOStreams() diff := DiffProgram{ - Stdout: &stdout, - Stderr: &bytes.Buffer{}, - Exec: exec.New(), + IOStreams: streams, + Exec: exec.New(), } err := diff.Run("one", "two") if err != nil { diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index f9ceabd3e3d..b5c04dd83ca 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -32,6 +32,7 @@ import ( coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/term" "k8s.io/kubernetes/pkg/util/interrupt" @@ -62,12 +63,10 @@ const ( execUsageStr = "expected 'exec POD_NAME COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD_NAME and COMMAND are required arguments for the exec command" ) -func NewCmdExec(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { +func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { options := &ExecOptions{ StreamOptions: StreamOptions{ - In: cmdIn, - Out: cmdOut, - Err: cmdErr, + IOStreams: streams, }, Executor: &DefaultRemoteExecutor{}, @@ -125,9 +124,8 @@ type StreamOptions struct { Quiet bool // InterruptParent, if set, is used to handle interrupts while attached InterruptParent *interrupt.Handler - In io.Reader - Out io.Writer - Err io.Writer + + genericclioptions.IOStreams // for testing overrideStreams func() (io.ReadCloser, io.Writer, io.Writer) @@ -155,7 +153,7 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s return cmdutil.UsageErrorf(cmd, execUsageStr) } if len(p.PodName) != 0 { - printDeprecationWarning(p.Err, "exec POD_NAME", "-p POD_NAME") + printDeprecationWarning(p.ErrOut, "exec POD_NAME", "-p POD_NAME") if len(argsIn) < 1 { return cmdutil.UsageErrorf(cmd, execUsageStr) } @@ -205,7 +203,7 @@ func (p *ExecOptions) Validate() error { if len(p.Command) == 0 { return fmt.Errorf("you must specify at least one command for the container") } - if p.Out == nil || p.Err == nil { + if p.Out == nil || p.ErrOut == nil { return fmt.Errorf("both output and error output must be provided") } if p.Executor == nil || p.PodClient == nil || p.Config == nil { @@ -240,8 +238,8 @@ func (o *StreamOptions) setupTTY() term.TTY { if !o.isTerminalIn(t) { o.TTY = false - if o.Err != nil { - fmt.Fprintln(o.Err, "Unable to use a TTY - input is not a terminal or the right kind of file") + if o.ErrOut != nil { + fmt.Fprintln(o.ErrOut, "Unable to use a TTY - input is not a terminal or the right kind of file") } return t @@ -284,7 +282,7 @@ func (p *ExecOptions) Run() error { if len(p.SuggestedCmdUsage) > 0 { usageString = fmt.Sprintf("%s\n%s", usageString, p.SuggestedCmdUsage) } - fmt.Fprintf(p.Err, "%s\n", usageString) + fmt.Fprintf(p.ErrOut, "%s\n", usageString) } containerName = pod.Spec.Containers[0].Name } @@ -299,7 +297,7 @@ func (p *ExecOptions) Run() error { // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is // true - p.Err = nil + p.ErrOut = nil } fn := func() error { @@ -320,11 +318,11 @@ func (p *ExecOptions) Run() error { Command: p.Command, Stdin: p.Stdin, Stdout: p.Out != nil, - Stderr: p.Err != nil, + Stderr: p.ErrOut != nil, TTY: t.Raw, }, legacyscheme.ParameterCodec) - return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue) + return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue) } if err := t.Safe(fn); err != nil { diff --git a/pkg/kubectl/cmd/exec_test.go b/pkg/kubectl/cmd/exec_test.go index 228da38d724..0a4c52b35cc 100644 --- a/pkg/kubectl/cmd/exec_test.go +++ b/pkg/kubectl/cmd/exec_test.go @@ -36,6 +36,7 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/term" ) @@ -145,7 +146,7 @@ func TestPodAndContainer(t *testing.T) { cmd := &cobra.Command{} options := test.p - options.Err = bytes.NewBuffer([]byte{}) + options.ErrOut = bytes.NewBuffer([]byte{}) err := options.Complete(tf, cmd, test.args, test.argsLenAtDash) if test.expectError && err == nil { t.Errorf("%s: unexpected non-error", test.name) @@ -213,9 +214,6 @@ func TestExec(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - bufOut := bytes.NewBuffer([]byte{}) - bufErr := bytes.NewBuffer([]byte{}) - bufIn := bytes.NewBuffer([]byte{}) ex := &fakeRemoteExecutor{} if test.execErr { ex.execErr = fmt.Errorf("exec error") @@ -224,9 +222,7 @@ func TestExec(t *testing.T) { StreamOptions: StreamOptions{ PodName: "foo", ContainerName: "bar", - In: bufIn, - Out: bufOut, - Err: bufErr, + IOStreams: genericclioptions.NewTestIOStreamsDiscard(), }, Executor: ex, } @@ -277,16 +273,14 @@ func execPod() *api.Pod { } func TestSetupTTY(t *testing.T) { - stderr := &bytes.Buffer{} + streams, _, _, stderr := genericclioptions.NewTestIOStreams() // test 1 - don't attach stdin o := &StreamOptions{ // InterruptParent: , - Stdin: false, - In: &bytes.Buffer{}, - Out: &bytes.Buffer{}, - Err: stderr, - TTY: true, + Stdin: false, + IOStreams: streams, + TTY: true, } tty := o.setupTTY() @@ -334,7 +328,7 @@ func TestSetupTTY(t *testing.T) { // test 3 - request a TTY, but stdin is not a terminal o.Stdin = true o.In = &bytes.Buffer{} - o.Err = stderr + o.ErrOut = stderr o.TTY = true tty = o.setupTTY() diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index 7b07806b04f..7faf0cd3ebd 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -35,6 +35,7 @@ import ( "k8s.io/kubernetes/pkg/apis/core/validation" "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" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -89,12 +90,19 @@ type LogsOptions struct { GetPodTimeout time.Duration LogsForObject func(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) - Out io.Writer + genericclioptions.IOStreams +} + +func NewLogsOptions(streams genericclioptions.IOStreams) *LogsOptions { + return &LogsOptions{ + IOStreams: streams, + } } // NewCmdLogs creates a new pod logs command -func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - o := &LogsOptions{} +func NewCmdLogs(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := NewLogsOptions(streams) + cmd := &cobra.Command{ Use: "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]", DisableFlagsInUseLine: true, @@ -103,11 +111,11 @@ func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Example: logsExample, PreRun: func(cmd *cobra.Command, args []string) { if len(os.Args) > 1 && os.Args[1] == "log" { - printDeprecationWarning(errOut, "logs", "log") + printDeprecationWarning(o.ErrOut, "logs", "log") } }, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Complete(f, out, cmd, args)) + cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunLogs()) }, @@ -129,7 +137,7 @@ func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { return cmd } -func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { containerName := cmdutil.GetFlagString(cmd, "container") selector := cmdutil.GetFlagString(cmd, "selector") o.AllContainers = cmdutil.GetFlagBool(cmd, "all-containers") @@ -189,7 +197,6 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm } o.Options = logOptions o.LogsForObject = f.LogsForObject - o.Out = out if len(selector) != 0 { if logOptions.Follow { diff --git a/pkg/kubectl/cmd/logs_test.go b/pkg/kubectl/cmd/logs_test.go index 1a7e5e359e1..2a4c177fa86 100644 --- a/pkg/kubectl/cmd/logs_test.go +++ b/pkg/kubectl/cmd/logs_test.go @@ -20,7 +20,6 @@ import ( "bytes" "io/ioutil" "net/http" - "os" "strings" "testing" @@ -31,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -74,9 +74,9 @@ func TestLog(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdLogs(tf, buf, buf) + cmd := NewCmdLogs(tf, streams) cmd.Flags().Set("namespace", "test") cmd.Run(cmd, []string{"foo"}) @@ -144,17 +144,17 @@ func TestValidateLogFlags(t *testing.T) { }, } for _, test := range tests { - buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdLogs(f, buf, buf) + streams := genericclioptions.NewTestIOStreamsDiscard() + cmd := NewCmdLogs(f, streams) out := "" for flag, value := range test.flags { cmd.Flags().Set(flag, value) } // checkErr breaks tests in case of errors, plus we just // need to check errors returned by the command validation - o := &LogsOptions{} + o := NewLogsOptions(streams) cmd.Run = func(cmd *cobra.Command, args []string) { - o.Complete(f, os.Stdout, cmd, args) + o.Complete(f, cmd, args) out = o.Validate().Error() } cmd.Run(cmd, test.args) @@ -205,8 +205,7 @@ func TestLogComplete(t *testing.T) { }, } for _, test := range tests { - buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdLogs(f, buf, buf) + cmd := NewCmdLogs(f, genericclioptions.NewTestIOStreamsDiscard()) var err error out := "" for flag, value := range test.flags { @@ -214,8 +213,8 @@ func TestLogComplete(t *testing.T) { } // checkErr breaks tests in case of errors, plus we just // need to check errors returned by the command validation - o := &LogsOptions{} - err = o.Complete(f, os.Stdout, cmd, test.args) + o := NewLogsOptions(genericclioptions.NewTestIOStreamsDiscard()) + err = o.Complete(f, cmd, test.args) out = err.Error() if !strings.Contains(out, test.expected) { t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out) diff --git a/pkg/kubectl/cmd/plugin.go b/pkg/kubectl/cmd/plugin.go index d34e78733a4..12d621ff3ef 100644 --- a/pkg/kubectl/cmd/plugin.go +++ b/pkg/kubectl/cmd/plugin.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "os" "os/exec" "syscall" @@ -28,6 +27,7 @@ import ( "github.com/spf13/pflag" "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/plugins" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -42,7 +42,7 @@ var ( ) // NewCmdPlugin creates the command that is the top-level for plugin commands. -func NewCmdPlugin(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command { +func NewCmdPlugin(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { // Loads plugins and create commands for each plugin identified loadedPlugins, loadErr := f.PluginLoader().Load() if loadErr != nil { @@ -58,14 +58,14 @@ func NewCmdPlugin(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Co if len(loadedPlugins) == 0 { cmdutil.CheckErr(fmt.Errorf("no plugins installed.")) } - cmdutil.DefaultSubCommandRun(err)(cmd, args) + cmdutil.DefaultSubCommandRun(streams.ErrOut)(cmd, args) }, } if len(loadedPlugins) > 0 { pluginRunner := f.PluginRunner() for _, p := range loadedPlugins { - cmd.AddCommand(NewCmdForPlugin(f, p, pluginRunner, in, out, err)) + cmd.AddCommand(NewCmdForPlugin(f, p, pluginRunner, streams)) } } @@ -73,7 +73,7 @@ func NewCmdPlugin(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Co } // NewCmdForPlugin creates a command capable of running the provided plugin. -func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.PluginRunner, in io.Reader, out, errout io.Writer) *cobra.Command { +func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.PluginRunner, streams genericclioptions.IOStreams) *cobra.Command { if !plugin.IsValid() { return nil } @@ -85,7 +85,7 @@ func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.P Example: templates.Examples(plugin.Example), Run: func(cmd *cobra.Command, args []string) { if len(plugin.Command) == 0 { - cmdutil.DefaultSubCommandRun(errout)(cmd, args) + cmdutil.DefaultSubCommandRun(streams.ErrOut)(cmd, args) return } @@ -104,9 +104,7 @@ func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.P } runningContext := plugins.RunningContext{ - In: in, - Out: out, - ErrOut: errout, + IOStreams: streams, Args: args, EnvProvider: envProvider, WorkingDir: plugin.Dir, @@ -117,7 +115,7 @@ func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.P // check for (and exit with) the correct exit code // from a failed plugin command execution if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - fmt.Fprintf(errout, "error: %v\n", err) + fmt.Fprintf(streams.ErrOut, "error: %v\n", err) os.Exit(status.ExitStatus()) } } @@ -132,7 +130,7 @@ func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.P } for _, childPlugin := range plugin.Tree { - cmd.AddCommand(NewCmdForPlugin(f, childPlugin, runner, in, out, errout)) + cmd.AddCommand(NewCmdForPlugin(f, childPlugin, runner, streams)) } return cmd diff --git a/pkg/kubectl/cmd/plugin_test.go b/pkg/kubectl/cmd/plugin_test.go index 205b266743f..c47646dd8b4 100644 --- a/pkg/kubectl/cmd/plugin_test.go +++ b/pkg/kubectl/cmd/plugin_test.go @@ -17,12 +17,12 @@ limitations under the License. package cmd import ( - "bytes" "fmt" "testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/plugins" ) @@ -81,9 +81,7 @@ func TestPluginCmd(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - inBuf := bytes.NewBuffer([]byte{}) - outBuf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + streams, _, outBuf, errBuf := genericclioptions.NewTestIOStreams() cmdutil.BehaviorOnFatal(func(str string, code int) { errBuf.Write([]byte(str)) @@ -96,7 +94,7 @@ func TestPluginCmd(t *testing.T) { f := cmdtesting.NewTestFactory() defer f.Cleanup() - cmd := NewCmdForPlugin(f, test.plugin, runner, inBuf, outBuf, errBuf) + cmd := NewCmdForPlugin(f, test.plugin, runner, streams) if cmd == nil { if !test.expectedNilCmd { t.Fatalf("%s: command was unexpectedly not registered", test.name) diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index 5f0043104a4..59f0af076f8 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "net/http" "net/url" "os" @@ -38,6 +37,7 @@ import ( coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -84,11 +84,10 @@ const ( defaultPodPortForwardWaitTimeout = 60 * time.Second ) -func NewCmdPortForward(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { +func NewCmdPortForward(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { opts := &PortForwardOptions{ PortForwarder: &defaultPortForwarder{ - cmdOut: cmdOut, - cmdErr: cmdErr, + IOStreams: streams, }, } cmd := &cobra.Command{ @@ -119,7 +118,7 @@ type portForwarder interface { } type defaultPortForwarder struct { - cmdOut, cmdErr io.Writer + genericclioptions.IOStreams } func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error { @@ -128,7 +127,7 @@ func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts Po return err } dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url) - fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.cmdOut, f.cmdErr) + fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut) if err != nil { return err } diff --git a/pkg/kubectl/cmd/portforward_test.go b/pkg/kubectl/cmd/portforward_test.go index 24071ca253f..406274b79b5 100644 --- a/pkg/kubectl/cmd/portforward_test.go +++ b/pkg/kubectl/cmd/portforward_test.go @@ -20,7 +20,6 @@ import ( "fmt" "net/http" "net/url" - "os" "reflect" "testing" @@ -32,6 +31,7 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -102,7 +102,7 @@ func testPortForward(t *testing.T, flags map[string]string, args []string) { } opts := &PortForwardOptions{} - cmd := NewCmdPortForward(tf, os.Stdout, os.Stderr) + cmd := NewCmdPortForward(tf, genericclioptions.NewTestIOStreamsDiscard()) cmd.Run = func(cmd *cobra.Command, args []string) { if err = opts.Complete(tf, cmd, args); err != nil { return diff --git a/pkg/kubectl/cmd/proxy.go b/pkg/kubectl/cmd/proxy.go index 027dfbf1ee8..440c8c867f4 100644 --- a/pkg/kubectl/cmd/proxy.go +++ b/pkg/kubectl/cmd/proxy.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "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/proxy" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -69,7 +70,7 @@ var ( kubectl proxy --api-prefix=/k8s-api`)) ) -func NewCmdProxy(f cmdutil.Factory, out io.Writer) *cobra.Command { +func NewCmdProxy(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", DisableFlagsInUseLine: true, @@ -77,7 +78,7 @@ func NewCmdProxy(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: proxyLong, Example: proxyExample, Run: func(cmd *cobra.Command, args []string) { - err := RunProxy(f, out, cmd) + err := RunProxy(f, streams.Out, cmd) cmdutil.CheckErr(err) }, } diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index 1f6f3510476..8bade7795b2 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -86,11 +85,10 @@ type ReplaceOptions struct { Recorder genericclioptions.Recorder - Out io.Writer - ErrOut io.Writer + genericclioptions.IOStreams } -func NewReplaceOptions(out, errOut io.Writer) *ReplaceOptions { +func NewReplaceOptions(streams genericclioptions.IOStreams) *ReplaceOptions { outputFormat := "" return &ReplaceOptions{ @@ -102,13 +100,12 @@ func NewReplaceOptions(out, errOut io.Writer) *ReplaceOptions { }, DeleteFlags: NewDeleteFlags("to use to replace the resource."), - Out: out, - ErrOut: errOut, + IOStreams: streams, } } -func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - o := NewReplaceOptions(out, errOut) +func NewCmdReplace(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := NewReplaceOptions(streams) cmd := &cobra.Command{ Use: "replace -f FILENAME", @@ -154,7 +151,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return printer.PrintObj(obj, o.Out) } - deleteOpts := o.DeleteFlags.ToOptions(o.Out, o.ErrOut) + deleteOpts := o.DeleteFlags.ToOptions(o.IOStreams) //Replace will create a resource if it doesn't exist already, so ignore not found error deleteOpts.IgnoreNotFound = true diff --git a/pkg/kubectl/cmd/replace_test.go b/pkg/kubectl/cmd/replace_test.go index 2789967784e..485ea07f106 100644 --- a/pkg/kubectl/cmd/replace_test.go +++ b/pkg/kubectl/cmd/replace_test.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "bytes" "net/http" "strings" "testing" @@ -26,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -63,9 +63,9 @@ func TestReplaceObject(t *testing.T) { }), } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdReplace(tf, buf, buf) + cmd := NewCmdReplace(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -134,9 +134,9 @@ func TestReplaceMultipleObject(t *testing.T) { }), } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdReplace(tf, buf, buf) + cmd := NewCmdReplace(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml") cmd.Flags().Set("output", "name") @@ -192,9 +192,9 @@ func TestReplaceDirectory(t *testing.T) { }), } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdReplace(tf, buf, buf) + cmd := NewCmdReplace(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy") cmd.Flags().Set("namespace", "test") cmd.Flags().Set("output", "name") @@ -239,9 +239,9 @@ func TestForceReplaceObjectNotFound(t *testing.T) { }), } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdReplace(tf, buf, buf) + cmd := NewCmdReplace(tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("force", "true") cmd.Flags().Set("cascade", "false") diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 92eeabe89d5..90c845665ba 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -233,7 +233,7 @@ func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return printer.PrintObj(obj, o.Out) } - deleteOpts := o.DeleteFlags.ToOptions(o.Out, o.ErrOut) + deleteOpts := o.DeleteFlags.ToOptions(o.IOStreams) deleteOpts.IgnoreNotFound = true deleteOpts.WaitForDeletion = false deleteOpts.GracePeriod = -1 @@ -374,12 +374,10 @@ func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e opts := &AttachOptions{ StreamOptions: StreamOptions{ - In: o.In, - Out: o.Out, - Err: o.ErrOut, - Stdin: o.Interactive, - TTY: o.TTY, - Quiet: o.Quiet, + IOStreams: o.IOStreams, + Stdin: o.Interactive, + TTY: o.TTY, + Quiet: o.Quiet, }, GetPodTimeout: timeout, CommandName: cmd.Parent().CommandPath() + " attach", @@ -528,7 +526,7 @@ func handleAttachPod(f cmdutil.Factory, podClient coreclient.PodsGetter, ns, nam opts.Namespace = ns // TODO: opts.Run sets opts.Err to nil, we need to find a better way - stderr := opts.Err + stderr := opts.ErrOut if err := opts.Run(); err != nil { fmt.Fprintf(stderr, "Error attaching, falling back to logs: %v\n", err) return logOpts(f, pod, opts) diff --git a/pkg/kubectl/cmd/run_test.go b/pkg/kubectl/cmd/run_test.go index 6d65d0d5d07..c08def94d9b 100644 --- a/pkg/kubectl/cmd/run_test.go +++ b/pkg/kubectl/cmd/run_test.go @@ -208,7 +208,7 @@ func TestRunArgsFollowDashRules(t *testing.T) { deleteFlags := NewDeleteFlags("to use to replace the resource.") opts := &RunOptions{ PrintFlags: printFlags, - DeleteOptions: deleteFlags.ToOptions(os.Stdout, os.Stderr), + DeleteOptions: deleteFlags.ToOptions(genericclioptions.NewTestIOStreamsDiscard()), IOStreams: genericclioptions.NewTestIOStreamsDiscard(), @@ -377,7 +377,7 @@ func TestGenerateService(t *testing.T) { deleteFlags := NewDeleteFlags("to use to replace the resource.") opts := &RunOptions{ PrintFlags: printFlags, - DeleteOptions: deleteFlags.ToOptions(os.Stdout, os.Stderr), + DeleteOptions: deleteFlags.ToOptions(genericclioptions.NewTestIOStreamsDiscard()), IOStreams: ioStreams, diff --git a/pkg/kubectl/cmd/top.go b/pkg/kubectl/cmd/top.go index 608c0ae7c52..c8abeee226f 100644 --- a/pkg/kubectl/cmd/top.go +++ b/pkg/kubectl/cmd/top.go @@ -17,8 +17,6 @@ limitations under the License. package cmd import ( - "io" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" @@ -26,6 +24,7 @@ import ( "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) var ( @@ -40,17 +39,17 @@ var ( This command requires Heapster to be correctly configured and working on the server. `)) ) -func NewCmdTop(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { +func NewCmdTop(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "top", Short: i18n.T("Display Resource (CPU/Memory/Storage) usage."), Long: topLong, - Run: cmdutil.DefaultSubCommandRun(errOut), + Run: cmdutil.DefaultSubCommandRun(streams.ErrOut), } // create subcommands - cmd.AddCommand(NewCmdTopNode(f, nil, out)) - cmd.AddCommand(NewCmdTopPod(f, nil, out)) + cmd.AddCommand(NewCmdTopNode(f, nil, streams)) + cmd.AddCommand(NewCmdTopPod(f, nil, streams)) return cmd } diff --git a/pkg/kubectl/cmd/top_node.go b/pkg/kubectl/cmd/top_node.go index 624be6633aa..fb2bbb0f95f 100644 --- a/pkg/kubectl/cmd/top_node.go +++ b/pkg/kubectl/cmd/top_node.go @@ -18,7 +18,6 @@ package cmd import ( "errors" - "io" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -29,6 +28,7 @@ import ( corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "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/metricsutil" "k8s.io/kubernetes/pkg/kubectl/util/i18n" metricsapi "k8s.io/metrics/pkg/apis/metrics" @@ -46,6 +46,8 @@ type TopNodeOptions struct { Printer *metricsutil.TopCmdPrinter DiscoveryClient discovery.DiscoveryInterface MetricsClient metricsclientset.Interface + + genericclioptions.IOStreams } type HeapsterTopOptions struct { @@ -89,9 +91,11 @@ var ( kubectl top node NODE_NAME`)) ) -func NewCmdTopNode(f cmdutil.Factory, options *TopNodeOptions, out io.Writer) *cobra.Command { - if options == nil { - options = &TopNodeOptions{} +func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericclioptions.IOStreams) *cobra.Command { + if o == nil { + o = &TopNodeOptions{ + IOStreams: streams, + } } cmd := &cobra.Command{ @@ -101,24 +105,24 @@ func NewCmdTopNode(f cmdutil.Factory, options *TopNodeOptions, out io.Writer) *c Long: topNodeLong, Example: topNodeExample, Run: func(cmd *cobra.Command, args []string) { - if err := options.Complete(f, cmd, args, out); err != nil { + if err := o.Complete(f, cmd, args); err != nil { cmdutil.CheckErr(err) } - if err := options.Validate(); err != nil { + if err := o.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err)) } - if err := options.RunTopNode(); err != nil { + if err := o.RunTopNode(); err != nil { cmdutil.CheckErr(err) } }, Aliases: []string{"nodes", "no"}, } - cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - options.HeapsterOptions.Bind(cmd.Flags()) + cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + o.HeapsterOptions.Bind(cmd.Flags()) return cmd } -func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, out io.Writer) error { +func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { if len(args) == 1 { o.ResourceName = args[0] } else if len(args) > 1 { @@ -144,7 +148,7 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] o.NodeClient = clientset.CoreV1() o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) - o.Printer = metricsutil.NewTopCmdPrinter(out) + o.Printer = metricsutil.NewTopCmdPrinter(o.Out) return nil } diff --git a/pkg/kubectl/cmd/top_node_test.go b/pkg/kubectl/cmd/top_node_test.go index 13c7af7f871..f9a8555344d 100644 --- a/pkg/kubectl/cmd/top_node_test.go +++ b/pkg/kubectl/cmd/top_node_test.go @@ -32,6 +32,7 @@ import ( core "k8s.io/client-go/testing" "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" metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" @@ -79,9 +80,9 @@ func TestTopNodeAllMetrics(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) cmd.Run(cmd, []string{}) // Check the presence of node names in the output. @@ -132,7 +133,7 @@ func TestTopNodeAllMetricsCustomDefaults(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() opts := &TopNodeOptions{ HeapsterOptions: HeapsterTopOptions{ @@ -140,8 +141,9 @@ func TestTopNodeAllMetricsCustomDefaults(t *testing.T) { Scheme: "https", Service: "custom-heapster-service", }, + IOStreams: streams, } - cmd := NewCmdTopNode(tf, opts, buf) + cmd := NewCmdTopNode(tf, opts, streams) cmd.Run(cmd, []string{}) // Check the presence of node names in the output. @@ -195,9 +197,9 @@ func TestTopNodeWithNameMetrics(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) cmd.Run(cmd, []string{expectedMetrics.Name}) // Check the presence of node names in the output. @@ -262,9 +264,9 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) cmd.Flags().Set("selector", label) cmd.Run(cmd, []string{}) @@ -315,14 +317,16 @@ func TestTopNodeAllMetricsFromMetricsServer(t *testing.T) { }) tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) // TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks // TODO then check the particular Run functionality and harvest results from fake clients - cmdOptions := &TopNodeOptions{} - if err := cmdOptions.Complete(tf, cmd, []string{}, buf); err != nil { + cmdOptions := &TopNodeOptions{ + IOStreams: streams, + } + if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil { t.Fatal(err) } cmdOptions.MetricsClient = fakemetricsClientset @@ -381,14 +385,16 @@ func TestTopNodeWithNameMetricsFromMetricsServer(t *testing.T) { }) tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) // TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks // TODO then check the particular Run functionality and harvest results from fake clients - cmdOptions := &TopNodeOptions{} - if err := cmdOptions.Complete(tf, cmd, []string{expectedMetrics.Name}, buf); err != nil { + cmdOptions := &TopNodeOptions{ + IOStreams: streams, + } + if err := cmdOptions.Complete(tf, cmd, []string{expectedMetrics.Name}); err != nil { t.Fatal(err) } cmdOptions.MetricsClient = fakemetricsClientset @@ -458,15 +464,17 @@ func TestTopNodeWithLabelSelectorMetricsFromMetricsServer(t *testing.T) { }) tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopNode(tf, nil, buf) + cmd := NewCmdTopNode(tf, nil, streams) cmd.Flags().Set("selector", label) // TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks // TODO then check the particular Run functionality and harvest results from fake clients - cmdOptions := &TopNodeOptions{} - if err := cmdOptions.Complete(tf, cmd, []string{}, buf); err != nil { + cmdOptions := &TopNodeOptions{ + IOStreams: streams, + } + if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil { t.Fatal(err) } cmdOptions.MetricsClient = fakemetricsClientset diff --git a/pkg/kubectl/cmd/top_pod.go b/pkg/kubectl/cmd/top_pod.go index 71bd8df2cb0..e073099d413 100644 --- a/pkg/kubectl/cmd/top_pod.go +++ b/pkg/kubectl/cmd/top_pod.go @@ -19,7 +19,6 @@ package cmd import ( "errors" "fmt" - "io" "time" "k8s.io/api/core/v1" @@ -37,6 +36,7 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) type TopPodOptions struct { @@ -51,6 +51,8 @@ type TopPodOptions struct { Printer *metricsutil.TopCmdPrinter DiscoveryClient discovery.DiscoveryInterface MetricsClient metricsclientset.Interface + + genericclioptions.IOStreams } const metricsCreationDelay = 2 * time.Minute @@ -78,9 +80,11 @@ var ( kubectl top pod -l name=myLabel`)) ) -func NewCmdTopPod(f cmdutil.Factory, options *TopPodOptions, out io.Writer) *cobra.Command { - if options == nil { - options = &TopPodOptions{} +func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions.IOStreams) *cobra.Command { + if o == nil { + o = &TopPodOptions{ + IOStreams: streams, + } } cmd := &cobra.Command{ @@ -90,26 +94,26 @@ func NewCmdTopPod(f cmdutil.Factory, options *TopPodOptions, out io.Writer) *cob Long: topPodLong, Example: topPodExample, Run: func(cmd *cobra.Command, args []string) { - if err := options.Complete(f, cmd, args, out); err != nil { + if err := o.Complete(f, cmd, args); err != nil { cmdutil.CheckErr(err) } - if err := options.Validate(); err != nil { + if err := o.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err)) } - if err := options.RunTopPod(); err != nil { + if err := o.RunTopPod(); err != nil { cmdutil.CheckErr(err) } }, Aliases: []string{"pods", "po"}, } - cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmd.Flags().BoolVar(&options.PrintContainers, "containers", options.PrintContainers, "If present, print usage of containers within a pod.") - cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") - options.HeapsterOptions.Bind(cmd.Flags()) + cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.") + cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") + o.HeapsterOptions.Bind(cmd.Flags()) return cmd } -func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, out io.Writer) error { +func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { var err error if len(args) == 1 { o.ResourceName = args[0] @@ -139,7 +143,7 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s o.PodClient = clientset.CoreV1() o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) - o.Printer = metricsutil.NewTopCmdPrinter(out) + o.Printer = metricsutil.NewTopCmdPrinter(o.Out) return nil } diff --git a/pkg/kubectl/cmd/top_pod_test.go b/pkg/kubectl/cmd/top_pod_test.go index ac5f78aa3d9..494e08efa6c 100644 --- a/pkg/kubectl/cmd/top_pod_test.go +++ b/pkg/kubectl/cmd/top_pod_test.go @@ -37,6 +37,7 @@ import ( core "k8s.io/client-go/testing" "k8s.io/kubernetes/pkg/api/legacyscheme" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" @@ -191,9 +192,9 @@ func TestTopPod(t *testing.T) { } tf.Namespace = testNS tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopPod(tf, nil, buf) + cmd := NewCmdTopPod(tf, nil, streams) for name, value := range testCase.flags { cmd.Flags().Set(name, value) } @@ -330,19 +331,20 @@ func TestTopPodWithMetricsServer(t *testing.T) { } tf.Namespace = testNS tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdTopPod(tf, nil, buf) + cmd := NewCmdTopPod(tf, nil, streams) var cmdOptions *TopPodOptions if testCase.options != nil { cmdOptions = testCase.options } else { cmdOptions = &TopPodOptions{} } + cmdOptions.IOStreams = streams // TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks // TODO then check the particular Run functionality and harvest results from fake clients. We probably end up skipping the factory altogether. - if err := cmdOptions.Complete(tf, cmd, testCase.args, buf); err != nil { + if err := cmdOptions.Complete(tf, cmd, testCase.args); err != nil { t.Fatal(err) } cmdOptions.MetricsClient = fakemetricsClientset @@ -534,7 +536,7 @@ func TestTopPodCustomDefaults(t *testing.T) { } tf.Namespace = testNS tf.ClientConfigVal = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() opts := &TopPodOptions{ HeapsterOptions: HeapsterTopOptions{ @@ -543,8 +545,9 @@ func TestTopPodCustomDefaults(t *testing.T) { Service: "custom-heapster-service", }, DiscoveryClient: &fakeDiscovery{}, + IOStreams: streams, } - cmd := NewCmdTopPod(tf, opts, buf) + cmd := NewCmdTopPod(tf, opts, streams) for name, value := range testCase.flags { cmd.Flags().Set(name, value) } diff --git a/pkg/kubectl/cmd/top_test.go b/pkg/kubectl/cmd/top_test.go index 0090782fcd8..2ad63d06bdb 100644 --- a/pkg/kubectl/cmd/top_test.go +++ b/pkg/kubectl/cmd/top_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1" metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) @@ -46,9 +47,7 @@ func TestTopSubcommandsExist(t *testing.T) { f := cmdtesting.NewTestFactory() defer f.Cleanup() - buf := bytes.NewBuffer([]byte{}) - - cmd := NewCmdTop(f, buf, buf) + cmd := NewCmdTop(f, genericclioptions.NewTestIOStreamsDiscard()) if !cmd.HasSubCommands() { t.Error("top command should have subcommands") } diff --git a/pkg/kubectl/plugins/BUILD b/pkg/kubectl/plugins/BUILD index e2f32b7d607..ac8b4f73ff6 100644 --- a/pkg/kubectl/plugins/BUILD +++ b/pkg/kubectl/plugins/BUILD @@ -16,6 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubectl/plugins", deps = [ + "//pkg/kubectl/genericclioptions:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", @@ -45,5 +46,8 @@ go_test( "runner_test.go", ], embed = [":go_default_library"], - deps = ["//vendor/github.com/spf13/pflag:go_default_library"], + deps = [ + "//pkg/kubectl/genericclioptions:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + ], ) diff --git a/pkg/kubectl/plugins/runner.go b/pkg/kubectl/plugins/runner.go index b30472193bc..eff195564b6 100644 --- a/pkg/kubectl/plugins/runner.go +++ b/pkg/kubectl/plugins/runner.go @@ -17,12 +17,12 @@ limitations under the License. package plugins import ( - "io" "os" "os/exec" "strings" "github.com/golang/glog" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) // PluginRunner is capable of running a plugin in a given running context. @@ -34,9 +34,7 @@ type PluginRunner interface { // in, out, and err streams, arguments and environment passed to it, and the // working directory. type RunningContext struct { - In io.Reader - Out io.Writer - ErrOut io.Writer + genericclioptions.IOStreams Args []string EnvProvider EnvProvider WorkingDir string diff --git a/pkg/kubectl/plugins/runner_test.go b/pkg/kubectl/plugins/runner_test.go index c91ad2fb13d..ae378148767 100644 --- a/pkg/kubectl/plugins/runner_test.go +++ b/pkg/kubectl/plugins/runner_test.go @@ -17,9 +17,10 @@ limitations under the License. package plugins import ( - "bytes" "os" "testing" + + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) func TestExecRunner(t *testing.T) { @@ -50,7 +51,7 @@ func TestExecRunner(t *testing.T) { defer os.Unsetenv("KUBECTL_PLUGINS_TEST") for _, test := range tests { - outBuf := bytes.NewBuffer([]byte{}) + streams, _, outBuf, _ := genericclioptions.NewTestIOStreams() plugin := &Plugin{ Description: Description{ @@ -61,7 +62,7 @@ func TestExecRunner(t *testing.T) { } ctx := RunningContext{ - Out: outBuf, + IOStreams: streams, WorkingDir: ".", EnvProvider: &EmptyEnvProvider{}, }