Kubectl exec support resource/name format

This commit is contained in:
Ahmad Nurus S 2019-02-02 21:12:11 +07:00
parent 20b76a2b4a
commit 0c39d7d380
6 changed files with 242 additions and 54 deletions

View File

@ -7,6 +7,7 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/interrupt:go_default_library", "//pkg/kubectl/util/interrupt:go_default_library",
@ -15,6 +16,7 @@ go_library(
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library", "//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
@ -33,11 +35,11 @@ go_test(
"//pkg/kubectl/util/term:go_default_library", "//pkg/kubectl/util/term:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library", "//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library", "//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
], ],
) )

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"time"
dockerterm "github.com/docker/docker/pkg/term" dockerterm "github.com/docker/docker/pkg/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -27,10 +28,12 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
coreclient "k8s.io/client-go/kubernetes/typed/core/v1" coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/interrupt" "k8s.io/kubernetes/pkg/kubectl/util/interrupt"
@ -40,27 +43,34 @@ import (
var ( var (
execExample = templates.Examples(i18n.T(` execExample = templates.Examples(i18n.T(`
# Get output from running 'date' from pod 123456-7890, using the first container by default # Get output from running 'date' command from pod mypod, using the first container by default
kubectl exec 123456-7890 date kubectl exec mypod date
# Get output from running 'date' in ruby-container from pod 123456-7890 # Get output from running 'date' command in ruby-container from pod mypod
kubectl exec 123456-7890 -c ruby-container date kubectl exec mypod -c ruby-container date
# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890 # Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod mypod
# and sends stdout/stderr from 'bash' back to the client # and sends stdout/stderr from 'bash' back to the client
kubectl exec 123456-7890 -c ruby-container -i -t -- bash -il kubectl exec mypod -c ruby-container -i -t -- bash -il
# List contents of /usr from the first container of pod 123456-7890 and sort by modification time. # List contents of /usr from the first container of pod mypod and sort by modification time.
# If the command you want to execute in the pod has any flags in common (e.g. -i), # If the command you want to execute in the pod has any flags in common (e.g. -i),
# you must use two dashes (--) to separate your command's flags/arguments. # you must use two dashes (--) to separate your command's flags/arguments.
# Also note, do not surround your command and its flags/arguments with quotes # Also note, do not surround your command and its flags/arguments with quotes
# unless that is how you would execute it normally (i.e., do ls -t /usr, not "ls -t /usr"). # unless that is how you would execute it normally (i.e., do ls -t /usr, not "ls -t /usr").
kubectl exec 123456-7890 -i -t -- ls -t /usr kubectl exec mypod -i -t -- ls -t /usr
# Get output from running 'date' command from the first pod of the deployment mydeployment, using the first container by default
kubectl exec deploy/mydeployment date
# Get output from running 'date' command from the first pod of the service myservice, using the first container by default
kubectl exec svc/myservice date
`)) `))
) )
const ( const (
execUsageStr = "expected 'exec POD_NAME COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD_NAME and COMMAND are required arguments for the exec command" execUsageStr = "expected 'exec (POD | TYPE/NAME) COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD or TYPE/NAME and COMMAND are required arguments for the exec command"
defaultPodExecTimeout = 60 * time.Second
) )
func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
@ -72,7 +82,7 @@ func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
Executor: &DefaultRemoteExecutor{}, Executor: &DefaultRemoteExecutor{},
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "exec POD [-c CONTAINER] -- COMMAND [args...]", Use: "exec (POD | TYPE/NAME) [-c CONTAINER] -- COMMAND [args...]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: i18n.T("Execute a command in a container"), Short: i18n.T("Execute a command in a container"),
Long: "Execute a command in a container.", Long: "Execute a command in a container.",
@ -84,6 +94,7 @@ func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
cmdutil.CheckErr(options.Run()) cmdutil.CheckErr(options.Run())
}, },
} }
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodExecTimeout)
cmd.Flags().StringVarP(&options.PodName, "pod", "p", options.PodName, "Pod name") cmd.Flags().StringVarP(&options.PodName, "pod", "p", options.PodName, "Pod name")
cmd.Flags().MarkDeprecated("pod", "This flag is deprecated and will be removed in future. Use exec POD_NAME instead.") cmd.Flags().MarkDeprecated("pod", "This flag is deprecated and will be removed in future. Use exec POD_NAME instead.")
// TODO support UID // TODO support UID
@ -137,14 +148,21 @@ type StreamOptions struct {
type ExecOptions struct { type ExecOptions struct {
StreamOptions StreamOptions
Command []string ResourceName string
Command []string
FullCmdName string FullCmdName string
SuggestedCmdUsage string SuggestedCmdUsage string
Executor RemoteExecutor Builder func() *resource.Builder
PodClient coreclient.PodsGetter ExecutablePodFn polymorphichelpers.AttachablePodForObjectFunc
Config *restclient.Config restClientGetter genericclioptions.RESTClientGetter
Pod *corev1.Pod
Executor RemoteExecutor
PodClient coreclient.PodsGetter
GetPodTimeout time.Duration
Config *restclient.Config
} }
// Complete verifies command line arguments and loads data from the command environment // Complete verifies command line arguments and loads data from the command environment
@ -159,32 +177,39 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
} }
p.Command = argsIn p.Command = argsIn
} else { } else {
p.PodName = argsIn[0] p.ResourceName = argsIn[0]
p.Command = argsIn[1:] p.Command = argsIn[1:]
if len(p.Command) < 1 {
return cmdutil.UsageErrorf(cmd, execUsageStr)
}
} }
namespace, _, err := f.ToRawKubeConfigLoader().Namespace() var err error
p.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil { if err != nil {
return err return err
} }
p.Namespace = namespace
p.ExecutablePodFn = polymorphichelpers.AttachablePodForObjectFn
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
p.Builder = f.NewBuilder
p.restClientGetter = f
cmdParent := cmd.Parent() cmdParent := cmd.Parent()
if cmdParent != nil { if cmdParent != nil {
p.FullCmdName = cmdParent.CommandPath() p.FullCmdName = cmdParent.CommandPath()
} }
if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") { if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", p.FullCmdName, p.PodName, p.Namespace) p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe %s -n %s' to see all of the containers in this pod.", p.FullCmdName, p.ResourceName, p.Namespace)
} }
config, err := f.ToRESTConfig() p.Config, err = f.ToRESTConfig()
if err != nil { if err != nil {
return err return err
} }
p.Config = config
clientset, err := f.KubernetesClientSet() clientset, err := f.KubernetesClientSet()
if err != nil { if err != nil {
@ -197,8 +222,8 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
// Validate checks that the provided exec options are specified. // Validate checks that the provided exec options are specified.
func (p *ExecOptions) Validate() error { func (p *ExecOptions) Validate() error {
if len(p.PodName) == 0 { if len(p.PodName) == 0 && len(p.ResourceName) == 0 {
return fmt.Errorf("pod name must be specified") return fmt.Errorf("pod or type/name must be specified")
} }
if len(p.Command) == 0 { if len(p.Command) == 0 {
return fmt.Errorf("you must specify at least one command for the container") return fmt.Errorf("you must specify at least one command for the container")
@ -206,9 +231,6 @@ func (p *ExecOptions) Validate() error {
if p.Out == nil || p.ErrOut == nil { if p.Out == nil || p.ErrOut == nil {
return fmt.Errorf("both output and error output must be provided") return fmt.Errorf("both output and error output must be provided")
} }
if p.Executor == nil || p.PodClient == nil || p.Config == nil {
return fmt.Errorf("client, client config, and executor must be provided")
}
return nil return nil
} }
@ -266,11 +288,33 @@ func (o *StreamOptions) SetupTTY() term.TTY {
// Run executes a validated remote execution against a pod. // Run executes a validated remote execution against a pod.
func (p *ExecOptions) Run() error { func (p *ExecOptions) Run() error {
pod, err := p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{}) var err error
if err != nil { // we still need legacy pod getter when PodName in ExecOptions struct is provided,
return err // since there are any other command run this function by providing Podname with PodsGetter
// and without resource builder, eg: `kubectl cp`.
if len(p.PodName) != 0 {
p.Pod, err = p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{})
if err != nil {
return err
}
} else {
builder := p.Builder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
NamespaceParam(p.Namespace).DefaultNamespace().ResourceNames("pods", p.ResourceName)
obj, err := builder.Do().Object()
if err != nil {
return err
}
p.Pod, err = p.ExecutablePodFn(p.restClientGetter, obj, p.GetPodTimeout)
if err != nil {
return err
}
} }
pod := p.Pod
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed { if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase) return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase)
} }

View File

@ -27,10 +27,9 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
@ -62,6 +61,7 @@ func TestPodAndContainer(t *testing.T) {
expectedPod string expectedPod string
expectedContainer string expectedContainer string
expectedArgs []string expectedArgs []string
obj *corev1.Pod
}{ }{
{ {
p: &ExecOptions{}, p: &ExecOptions{},
@ -70,16 +70,18 @@ func TestPodAndContainer(t *testing.T) {
name: "empty", name: "empty",
}, },
{ {
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}}, p: &ExecOptions{},
argsLenAtDash: -1, argsLenAtDash: -1,
expectError: true, expectError: true,
name: "no cmd", name: "no cmd",
obj: execPod(),
}, },
{ {
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo", ContainerName: "bar"}}, p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
argsLenAtDash: -1, argsLenAtDash: -1,
expectError: true, expectError: true,
name: "no cmd, w/ container", name: "no cmd, w/ container",
obj: execPod(),
}, },
{ {
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}}, p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}},
@ -88,6 +90,7 @@ func TestPodAndContainer(t *testing.T) {
expectedPod: "foo", expectedPod: "foo",
expectedArgs: []string{"cmd"}, expectedArgs: []string{"cmd"},
name: "pod in flags", name: "pod in flags",
obj: execPod(),
}, },
{ {
p: &ExecOptions{}, p: &ExecOptions{},
@ -95,6 +98,7 @@ func TestPodAndContainer(t *testing.T) {
argsLenAtDash: 0, argsLenAtDash: 0,
expectError: true, expectError: true,
name: "no pod, pod name is behind dash", name: "no pod, pod name is behind dash",
obj: execPod(),
}, },
{ {
p: &ExecOptions{}, p: &ExecOptions{},
@ -102,6 +106,7 @@ func TestPodAndContainer(t *testing.T) {
argsLenAtDash: -1, argsLenAtDash: -1,
expectError: true, expectError: true,
name: "no cmd, w/o flags", name: "no cmd, w/o flags",
obj: execPod(),
}, },
{ {
p: &ExecOptions{}, p: &ExecOptions{},
@ -110,6 +115,7 @@ func TestPodAndContainer(t *testing.T) {
expectedPod: "foo", expectedPod: "foo",
expectedArgs: []string{"cmd"}, expectedArgs: []string{"cmd"},
name: "cmd, w/o flags", name: "cmd, w/o flags",
obj: execPod(),
}, },
{ {
p: &ExecOptions{}, p: &ExecOptions{},
@ -118,6 +124,7 @@ func TestPodAndContainer(t *testing.T) {
expectedPod: "foo", expectedPod: "foo",
expectedArgs: []string{"cmd"}, expectedArgs: []string{"cmd"},
name: "cmd, cmd is behind dash", name: "cmd, cmd is behind dash",
obj: execPod(),
}, },
{ {
p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}}, p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
@ -127,10 +134,12 @@ func TestPodAndContainer(t *testing.T) {
expectedContainer: "bar", expectedContainer: "bar",
expectedArgs: []string{"cmd"}, expectedArgs: []string{"cmd"},
name: "cmd, container in flag", name: "cmd, container in flag",
obj: execPod(),
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
var err error
tf := cmdtesting.NewTestFactory().WithNamespace("test") tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup() defer tf.Cleanup()
@ -142,10 +151,13 @@ func TestPodAndContainer(t *testing.T) {
} }
tf.ClientConfigVal = cmdtesting.DefaultClientConfig() tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
cmd := &cobra.Command{} cmd := NewCmdExec(tf, genericclioptions.NewTestIOStreamsDiscard())
options := test.p options := test.p
options.ErrOut = bytes.NewBuffer([]byte{}) options.ErrOut = bytes.NewBuffer([]byte{})
err := options.Complete(tf, cmd, test.args, test.argsLenAtDash) options.Out = bytes.NewBuffer([]byte{})
err = options.Complete(tf, cmd, test.args, test.argsLenAtDash)
err = options.Validate()
if test.expectError && err == nil { if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name) t.Errorf("%s: unexpected non-error", test.name)
} }
@ -155,7 +167,9 @@ func TestPodAndContainer(t *testing.T) {
if err != nil { if err != nil {
return return
} }
if options.PodName != test.expectedPod {
pod, err := options.ExecutablePodFn(tf, test.obj, defaultPodExecTimeout)
if pod.Name != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName) t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
} }
if options.ContainerName != test.expectedContainer { if options.ContainerName != test.expectedContainer {
@ -171,22 +185,26 @@ func TestPodAndContainer(t *testing.T) {
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
version := "v1" version := "v1"
tests := []struct { tests := []struct {
name, podPath, execPath string name, version, podPath, fetchPodPath, execPath string
pod *corev1.Pod pod *corev1.Pod
execErr bool execErr bool
}{ }{
{ {
name: "pod exec", name: "pod exec",
podPath: "/api/" + version + "/namespaces/test/pods/foo", version: version,
execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", podPath: "/api/" + version + "/namespaces/test/pods/foo",
pod: execPod(), fetchPodPath: "/namespaces/test/pods/foo",
execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
pod: execPod(),
}, },
{ {
name: "pod exec error", name: "pod exec error",
podPath: "/api/" + version + "/namespaces/test/pods/foo", version: version,
execPath: "/api/" + version + "/namespaces/test/pods/foo/exec", podPath: "/api/" + version + "/namespaces/test/pods/foo",
pod: execPod(), fetchPodPath: "/namespaces/test/pods/foo",
execErr: true, execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
pod: execPod(),
execErr: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -198,19 +216,23 @@ func TestExec(t *testing.T) {
ns := scheme.Codecs ns := scheme.Codecs
tf.Client = &fake.RESTClient{ tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET": case p == test.podPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod) body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request") return nil, fmt.Errorf("unexpected request")
} }
}), }),
} }
tf.ClientConfigVal = cmdtesting.DefaultClientConfig() tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
ex := &fakeRemoteExecutor{} ex := &fakeRemoteExecutor{}
if test.execErr { if test.execErr {
ex.execErr = fmt.Errorf("exec error") ex.execErr = fmt.Errorf("exec error")
@ -223,8 +245,8 @@ func TestExec(t *testing.T) {
}, },
Executor: ex, Executor: ex,
} }
cmd := &cobra.Command{} cmd := NewCmdExec(tf, genericclioptions.NewTestIOStreamsDiscard())
args := []string{"test", "command"} args := []string{"pod/foo", "command"}
if err := params.Complete(tf, cmd, args, -1); err != nil { if err := params.Complete(tf, cmd, args, -1); err != nil {
t.Fatal(err) t.Fatal(err)
} }

100
test/cmd/exec.sh Executable file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env bash
# Copyright 2019 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
run_kubectl_exec_pod_tests() {
set -o nounset
set -o errexit
create_and_use_new_namespace
kube::log::status "Testing kubectl exec POD COMMAND"
### Test execute non-existing POD
output_message=$(! kubectl exec abc date 2>&1)
# POD abc should error since it doesn't exist
kube::test::if_has_string "${output_message}" 'pods "abc" not found'
### Test execute existing POD
# Create test-pod
kubectl create -f hack/testdata/pod.yaml
# Execute existing POD
output_message=$(! kubectl exec test-pod date 2>&1)
# POD test-pod is exists this is shouldn't have output not found
kube::test::if_has_not_string "${output_message}" 'pods "test-pod" not found'
# These must be pass the validate
kube::test::if_has_not_string "${output_message}" 'pod or type/name must be specified'
# Clean up
kubectl delete pods test-pod
set +o nounset
set +o errexit
}
run_kubectl_exec_resource_name_tests() {
set +o nounset
set +o errexit
create_and_use_new_namespace
kube::log::status "Testing kubectl exec TYPE/NAME COMMAND"
### Test execute invalid resource type
output_message=$(! kubectl exec foo/bar date 2>&1)
# resource type foo should error since it's invalid
kube::test::if_has_string "${output_message}" 'error:'
### Test execute non-existing resources
output_message=$(! kubectl exec deployments/bar date 2>&1)
# resource type foo should error since it doesn't exist
kube::test::if_has_string "${output_message}" '"bar" not found'
kubectl create -f hack/testdata/pod.yaml
kubectl create -f hack/testdata/frontend-replicaset.yaml
kubectl create -f hack/testdata/configmap.yaml
### Test execute non-implemented resources
output_message=$(! kubectl exec configmap/test-set-env-config date 2>&1)
# resource type configmap should error since configmap not implemented to be attached
kube::test::if_has_string "${output_message}" 'not implemented'
### Test execute exists and valid resource type.
# Just check the output, since test-cmd not run kubelet, pod never be assigned.
# and not really can run `kubectl exec` command
output_message=$(! kubectl exec pods/test-pod date 2>&1)
# POD test-pod is exists this is shouldn't have output not found
kube::test::if_has_not_string "${output_message}" 'not found'
# These must be pass the validate
kube::test::if_has_not_string "${output_message}" 'pod or type/name must be specified'
output_message=$(! kubectl exec replicaset/frontend date 2>&1)
# Replicaset frontend is valid and exists will select the first pod.
# and Shouldn't have output not found
kube::test::if_has_not_string "${output_message}" 'not found'
# These must be pass the validate
kube::test::if_has_not_string "${output_message}" 'pod or type/name must be specified'
# Clean up
kubectl delete pods/test-pod
kubectl delete replicaset/frontend
kubectl delete configmap/test-set-env-config
set +o nounset
set +o errexit
}

View File

@ -38,6 +38,7 @@ source "${KUBE_ROOT}/test/cmd/create.sh"
source "${KUBE_ROOT}/test/cmd/delete.sh" source "${KUBE_ROOT}/test/cmd/delete.sh"
source "${KUBE_ROOT}/test/cmd/diff.sh" source "${KUBE_ROOT}/test/cmd/diff.sh"
source "${KUBE_ROOT}/test/cmd/discovery.sh" source "${KUBE_ROOT}/test/cmd/discovery.sh"
source "${KUBE_ROOT}/test/cmd/exec.sh"
source "${KUBE_ROOT}/test/cmd/generic-resources.sh" source "${KUBE_ROOT}/test/cmd/generic-resources.sh"
source "${KUBE_ROOT}/test/cmd/get.sh" source "${KUBE_ROOT}/test/cmd/get.sh"
source "${KUBE_ROOT}/test/cmd/kubeadm.sh" source "${KUBE_ROOT}/test/cmd/kubeadm.sh"
@ -506,6 +507,16 @@ runTests() {
record_command run_kubectl_old_print_tests record_command run_kubectl_old_print_tests
fi fi
################
# Kubectl exec #
################
if kube::test::if_supports_resource "${pods}"; then
record_command run_kubectl_exec_pod_tests
if kube::test::if_supports_resource "${replicasets}" && kube::test::if_supports_resource "${configmaps}"; then
record_command run_kubectl_exec_resource_name_tests
fi
fi
###################### ######################
# Create # # Create #

View File

@ -78,6 +78,7 @@ const (
guestbookResponseTimeout = 3 * time.Minute guestbookResponseTimeout = 3 * time.Minute
simplePodSelector = "name=nginx" simplePodSelector = "name=nginx"
simplePodName = "nginx" simplePodName = "nginx"
simplePodResourceName = "pod/nginx"
nginxDefaultOutput = "Welcome to nginx!" nginxDefaultOutput = "Welcome to nginx!"
simplePodPort = 80 simplePodPort = 80
pausePodSelector = "name=pause" pausePodSelector = "name=pause"
@ -408,6 +409,14 @@ var _ = SIGDescribe("Kubectl client", func() {
} }
}) })
ginkgo.It("should support exec using resource/name", func() {
ginkgo.By("executing a command in the container")
execOutput := framework.RunKubectlOrDie("exec", fmt.Sprintf("--namespace=%v", ns), simplePodResourceName, "echo", "running", "in", "container")
if e, a := "running in container", strings.TrimSpace(execOutput); e != a {
framework.Failf("Unexpected kubectl exec output. Wanted %q, got %q", e, a)
}
})
ginkgo.It("should support exec through an HTTP proxy", func() { ginkgo.It("should support exec through an HTTP proxy", func() {
// Fail if the variable isn't set // Fail if the variable isn't set
if framework.TestContext.Host == "" { if framework.TestContext.Host == "" {