diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index b16c6931da4..bbe6f4319b1 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "k8s.io/kubernetes/pkg/util/interrupt" "k8s.io/kubernetes/pkg/util/term" @@ -157,6 +158,29 @@ func (p *ExecOptions) Complete(f *cmdutil.Factory, cmd *cobra.Command, argsIn [] return cmdutil.UsageError(cmd, execUsageStr) } } + namespace, _, err := f.DefaultNamespace() + if err != nil { + return err + } + p.Namespace = namespace + + clientMapper := resource.ClientMapperFunc(f.ClientForMapping) + mapper, typer := f.Object() + decoder := f.Decoder(true) + + infos, err := resource.NewBuilder(mapper, typer, clientMapper, decoder). + NamespaceParam(p.Namespace).DefaultNamespace(). + ResourceNames("pods", p.PodName). + SingleResourceType(). + Do().Infos() + if err != nil { + return err + } + if len(infos) != 1 { + return cmdutil.UsageError(cmd, execUsageStr) + } + + p.PodName = infos[0].Name cmdParent := cmd.Parent() if cmdParent != nil { @@ -166,12 +190,6 @@ func (p *ExecOptions) Complete(f *cmdutil.Factory, cmd *cobra.Command, argsIn [] p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s' to see all of the containers in this pod.", p.FullCmdName, p.PodName) } - namespace, _, err := f.DefaultNamespace() - if err != nil { - return err - } - p.Namespace = namespace - config, err := f.ClientConfig() if err != nil { return err diff --git a/pkg/kubectl/cmd/exec_test.go b/pkg/kubectl/cmd/exec_test.go index 3d759bb7287..57b6e496681 100644 --- a/pkg/kubectl/cmd/exec_test.go +++ b/pkg/kubectl/cmd/exec_test.go @@ -55,7 +55,7 @@ func TestPodAndContainer(t *testing.T) { p *ExecOptions name string expectError bool - expectedPod string + expectedPod *api.Pod expectedContainer string expectedArgs []string }{ @@ -81,10 +81,18 @@ func TestPodAndContainer(t *testing.T) { p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}}, args: []string{"cmd"}, argsLenAtDash: -1, - expectedPod: "foo", + expectedPod: execPod(), expectedArgs: []string{"cmd"}, name: "pod in flags", }, + { + p: &ExecOptions{StreamOptions: StreamOptions{PodName: "pod/foo"}}, + args: []string{"cmd"}, + argsLenAtDash: -1, + expectedPod: execPod(), + expectedArgs: []string{"cmd"}, + name: "pod with 'pod/' prefix in flags", + }, { p: &ExecOptions{}, args: []string{"foo", "cmd"}, @@ -103,15 +111,23 @@ func TestPodAndContainer(t *testing.T) { p: &ExecOptions{}, args: []string{"foo", "cmd"}, argsLenAtDash: -1, - expectedPod: "foo", + expectedPod: execPod(), expectedArgs: []string{"cmd"}, name: "cmd, w/o flags", }, + { + p: &ExecOptions{}, + args: []string{"pod/foo", "cmd"}, + argsLenAtDash: -1, + expectedPod: execPod(), + expectedArgs: []string{"cmd"}, + name: "pod with 'pod/' prefix, cmd, w/o flags", + }, { p: &ExecOptions{}, args: []string{"foo", "cmd"}, argsLenAtDash: 1, - expectedPod: "foo", + expectedPod: execPod(), expectedArgs: []string{"cmd"}, name: "cmd, cmd is behind dash", }, @@ -119,17 +135,27 @@ func TestPodAndContainer(t *testing.T) { p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}}, args: []string{"foo", "cmd"}, argsLenAtDash: -1, - expectedPod: "foo", + expectedPod: execPod(), expectedContainer: "bar", expectedArgs: []string{"cmd"}, name: "cmd, container in flag", }, } for _, test := range tests { - f, tf, _, ns := NewAPIFactory() + f, tf, codec, ns := NewAPIFactory() tf.Client = &fake.RESTClient{ NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }), + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch m := req.Method; { + case m == "GET": + body := objBody(codec, test.expectedPod) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil + default: + //Ensures no GET is performed when deleting by name + t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), } tf.Namespace = "test" tf.ClientConfig = defaultClientConfig() @@ -146,8 +172,8 @@ func TestPodAndContainer(t *testing.T) { if err != nil { continue } - if options.PodName != test.expectedPod { - t.Errorf("expected: %s, got: %s (%s)", test.expectedPod, options.PodName, test.name) + if options.PodName != test.expectedPod.Name { + t.Errorf("expected: %s, got: %s (%s)", test.expectedPod.Name, options.PodName, test.name) } if options.ContainerName != test.expectedContainer { t.Errorf("expected: %s, got: %s (%s)", test.expectedContainer, options.ContainerName, test.name) @@ -161,22 +187,24 @@ func TestPodAndContainer(t *testing.T) { func TestExec(t *testing.T) { version := registered.GroupOrDie(api.GroupName).GroupVersion.Version tests := []struct { - name, podPath, execPath, container string - pod *api.Pod - execErr bool + name, shortPodPath, podPath, execPath, container string + pod *api.Pod + execErr bool }{ { - name: "pod exec", - podPath: "/api/" + version + "/namespaces/test/pods/foo", - execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", - pod: execPod(), + name: "pod exec", + shortPodPath: "/namespaces/test/pods/foo", + podPath: "/api/" + version + "/namespaces/test/pods/foo", + execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", + pod: execPod(), }, { - name: "pod exec error", - podPath: "/api/" + version + "/namespaces/test/pods/foo", - execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", - pod: execPod(), - execErr: true, + name: "pod exec error", + shortPodPath: "/namespaces/test/pods/foo", + podPath: "/api/" + version + "/namespaces/test/pods/foo", + execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", + pod: execPod(), + execErr: true, }, } for _, test := range tests { @@ -188,6 +216,9 @@ func TestExec(t *testing.T) { case p == test.podPath && m == "GET": body := objBody(codec, test.pod) return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil + case p == test.shortPodPath && m == "GET": + body := objBody(codec, test.pod) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil default: // Ensures no GET is performed when deleting by name t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)