From ae584d811420e494c61825d3473bc37b3c455ad9 Mon Sep 17 00:00:00 2001 From: Dominika Hodovska Date: Thu, 4 Aug 2016 08:31:23 +0200 Subject: [PATCH] kubectl: Convert port-forward cmd to complete/validate/run structure --- pkg/kubectl/cmd/portforward.go | 96 ++++++++++++++++++++--------- pkg/kubectl/cmd/portforward_test.go | 56 +++++++++++++---- 2 files changed, 113 insertions(+), 39 deletions(-) diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index adaa9c95127..2014b62f405 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -17,21 +17,32 @@ limitations under the License. package cmd import ( + "fmt" "io" "net/url" "os" "os/signal" - "github.com/golang/glog" "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/restclient" + client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/portforward" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) +// PortForwardOptions contains all the options for running the port-forward cli command. +type PortForwardOptions struct { + Namespace string + PodName string + Config *restclient.Config + Client *client.Client + Ports []string + PortForwarder portForwarder +} + var ( portforward_example = dedent.Dedent(` # Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod @@ -48,18 +59,27 @@ var ( ) func NewCmdPortForward(f *cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { + opts := &PortForwardOptions{ + PortForwarder: &defaultPortForwarder{ + cmdOut: cmdOut, + cmdErr: cmdErr, + }, + } cmd := &cobra.Command{ Use: "port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]", Short: "Forward one or more local ports to a pod", Long: "Forward one or more local ports to a pod.", Example: portforward_example, Run: func(cmd *cobra.Command, args []string) { - pf := &defaultPortForwarder{ - cmdOut: cmdOut, - cmdErr: cmdErr, + if err := opts.Complete(f, cmd, args, cmdOut, cmdErr); err != nil { + cmdutil.CheckErr(err) + } + if err := opts.Validate(); err != nil { + cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error())) + } + if err := opts.RunPortForward(); err != nil { + cmdutil.CheckErr(err) } - err := RunPortForward(f, cmd, args, pf) - cmdutil.CheckErr(err) }, } cmd.Flags().StringP("pod", "p", "", "Pod name") @@ -87,45 +107,65 @@ func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, config return fw.ForwardPorts() } -func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string, fw portForwarder) error { - podName := cmdutil.GetFlagString(cmd, "pod") - if len(podName) == 0 && len(args) == 0 { +// Complete completes all the required options for port-forward cmd. +func (o *PortForwardOptions) Complete(f *cmdutil.Factory, cmd *cobra.Command, args []string, cmdOut io.Writer, cmdErr io.Writer) error { + var err error + o.PodName = cmdutil.GetFlagString(cmd, "pod") + if len(o.PodName) == 0 && len(args) == 0 { return cmdutil.UsageError(cmd, "POD is required for port-forward") } - if len(podName) != 0 { + if len(o.PodName) != 0 { printDeprecationWarning("port-forward POD", "-p POD") + o.Ports = args } else { - podName = args[0] - args = args[1:] + o.PodName = args[0] + o.Ports = args[1:] } - if len(args) < 1 { - return cmdutil.UsageError(cmd, "at least 1 PORT is required for port-forward") - } - - namespace, _, err := f.DefaultNamespace() + o.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } - client, err := f.Client() + o.Client, err = f.Client() if err != nil { return err } - pod, err := client.Pods(namespace).Get(podName) + o.Config, err = f.ClientConfig() + if err != nil { + return err + } + + return nil +} + +// Validate validates all the required options for port-forward cmd. +func (o PortForwardOptions) Validate() error { + if len(o.PodName) == 0 { + return fmt.Errorf("pod name must be specified") + } + + if len(o.Ports) < 1 { + return fmt.Errorf("at least 1 PORT is required for port-forward") + } + + if o.PortForwarder == nil || o.Client == nil || o.Config == nil { + return fmt.Errorf("client, client config, and portforwarder must be provided") + } + return nil +} + +// RunPortForward implements all the necessary functionality for port-forward cmd. +func (o PortForwardOptions) RunPortForward() error { + pod, err := o.Client.Pods(o.Namespace).Get(o.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { - glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase) - } - - config, err := f.ClientConfig() - if err != nil { - return err + return fmt.Errorf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase) } signals := make(chan os.Signal, 1) @@ -138,11 +178,11 @@ func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string, fw po close(stopCh) }() - req := client.RESTClient.Post(). + req := o.Client.RESTClient.Post(). Resource("pods"). - Namespace(namespace). + Namespace(o.Namespace). Name(pod.Name). SubResource("portforward") - return fw.ForwardPorts("POST", req.URL(), config, args, stopCh) + return o.PortForwarder.ForwardPorts("POST", req.URL(), o.Config, o.Ports, stopCh) } diff --git a/pkg/kubectl/cmd/portforward_test.go b/pkg/kubectl/cmd/portforward_test.go index 7e4195cbbc5..888c32b96c7 100644 --- a/pkg/kubectl/cmd/portforward_test.go +++ b/pkg/kubectl/cmd/portforward_test.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "net/url" + "os" "testing" "github.com/spf13/cobra" @@ -68,6 +69,7 @@ func TestPortForward(t *testing.T) { }, } for _, test := range tests { + var err error f, tf, codec, ns := NewAPIFactory() tf.Client = &fake.RESTClient{ NegotiatedSerializer: ns, @@ -89,9 +91,21 @@ func TestPortForward(t *testing.T) { if test.pfErr { ff.pfErr = fmt.Errorf("pf error") } - cmd := &cobra.Command{} - cmd.Flags().StringP("pod", "p", "", "Pod name") - err := RunPortForward(f, cmd, []string{"foo", ":5000", ":1000"}, ff) + + opts := &PortForwardOptions{} + cmd := NewCmdPortForward(f, os.Stdout, os.Stderr) + cmd.Run = func(cmd *cobra.Command, args []string) { + if err = opts.Complete(f, cmd, args, os.Stdout, os.Stderr); err != nil { + return + } + opts.PortForwarder = ff + if err = opts.Validate(); err != nil { + return + } + err = opts.RunPortForward() + } + + cmd.Run(cmd, []string{"foo", ":5000", ":1000"}) if test.pfErr && err != ff.pfErr { t.Errorf("%s: Unexpected exec error: %v", test.name, err) @@ -109,7 +123,6 @@ func TestPortForward(t *testing.T) { if ff.method != "POST" { t.Errorf("%s: Did not get method for attach request: %s", test.name, ff.method) } - } } @@ -138,6 +151,7 @@ func TestPortForwardWithPFlag(t *testing.T) { }, } for _, test := range tests { + var err error f, tf, codec, ns := NewAPIFactory() tf.Client = &fake.RESTClient{ NegotiatedSerializer: ns, @@ -159,18 +173,38 @@ func TestPortForwardWithPFlag(t *testing.T) { if test.pfErr { ff.pfErr = fmt.Errorf("pf error") } - cmd := &cobra.Command{} - podPtr := cmd.Flags().StringP("pod", "p", "", "Pod name") - *podPtr = "foo" - err := RunPortForward(f, cmd, []string{":5000", ":1000"}, ff) + + opts := &PortForwardOptions{} + cmd := NewCmdPortForward(f, os.Stdout, os.Stderr) + cmd.Run = func(cmd *cobra.Command, args []string) { + if err = opts.Complete(f, cmd, args, os.Stdout, os.Stderr); err != nil { + return + } + opts.PortForwarder = ff + if err = opts.Validate(); err != nil { + return + } + err = opts.RunPortForward() + } + cmd.Flags().Set("pod", "foo") + + cmd.Run(cmd, []string{":5000", ":1000"}) + if test.pfErr && err != ff.pfErr { t.Errorf("%s: Unexpected exec error: %v", test.name, err) } - if !test.pfErr && ff.url.Path != test.pfPath { - t.Errorf("%s: Did not get expected path for portforward request", test.name) - } if !test.pfErr && err != nil { t.Errorf("%s: Unexpected error: %v", test.name, err) } + if test.pfErr { + continue + } + + if ff.url.Path != test.pfPath { + t.Errorf("%s: Did not get expected path for portforward request", test.name) + } + if ff.method != "POST" { + t.Errorf("%s: Did not get method for attach request: %s", test.name, ff.method) + } } }