mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
Kubectl exec support resource/name format
This commit is contained in:
parent
20b76a2b4a
commit
0c39d7d380
@ -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",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
100
test/cmd/exec.sh
Executable 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
|
||||||
|
}
|
@ -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 #
|
||||||
|
@ -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 == "" {
|
||||||
|
Loading…
Reference in New Issue
Block a user