kubectl debug: Allow mutating image names

Kubernetes-commit: ee9f11b95f01b32dade5d8dc7329625c40ac0e63
This commit is contained in:
Lee Verberne 2020-10-30 18:23:34 +01:00 committed by Kubernetes Publisher
parent 6a3c6f9a42
commit fdd6fda024
2 changed files with 439 additions and 105 deletions

View File

@ -55,7 +55,7 @@ var (
Debug cluster resources using interactive debugging containers.
'debug' provides automation for common debugging tasks for cluster objects identified by
resource and name. Pods will be used by default if resource is not specified.
resource and name. Pods will be used by default if no resource is specified.
The action taken by 'debug' varies depending on what resource is specified. Supported
actions include:
@ -66,8 +66,7 @@ var (
debugging utilities without restarting the pod.
* Node: Create a new pod that runs in the node's host namespaces and can access
the node's filesystem.
Alpha disclaimer: command line flags may change`))
`))
debugExample = templates.Examples(i18n.T(`
# Create an interactive debugging session in pod mypod and immediately attach to it.
@ -78,11 +77,17 @@ var (
# (requires the EphemeralContainers feature to be enabled in the cluster)
kubectl alpha debug --image=myproj/debug-tools -c debugger mypod
# Create a debug container as a copy of the original Pod and attach to it
# Create a copy of mypod adding a debug container and attach to it
kubectl alpha debug mypod -it --image=busybox --copy-to=my-debugger
# Create a copy of mypod named my-debugger with my-container's image changed to busybox
kubectl alpha debug mypod --image=busybox --container=my-container --copy-to=my-debugger -- sleep 1d
# Create a copy of mypod changing the command of mycontainer
kubectl alpha debug mypod -it --copy-to=my-debugger --container=mycontainer -- sh
# Create a copy of mypod changing all container images to busybox
kubectl alpha debug mypod --copy-to=my-debugger --set-image=*=busybox
# Create a copy of mypod adding a debug container and changing container images
kubectl alpha debug mypod -it --copy-to=my-debugger --image=debian --set-image=app=app:debug,sidecar=sidecar:debug
# Create an interactive debugging session on a node and immediately attach to it.
# The container will run in the host namespaces and the host's filesystem will be mounted at /host
@ -108,6 +113,7 @@ type DebugOptions struct {
PullPolicy corev1.PullPolicy
Quiet bool
SameNode bool
SetImages map[string]string
ShareProcesses bool
TargetContainer string
TTY bool
@ -134,9 +140,9 @@ func NewCmdDebug(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.
o := NewDebugOptions(streams)
cmd := &cobra.Command{
Use: "debug (POD | TYPE[[.VERSION].GROUP]/NAME) --image=image [ -- COMMAND [args...] ]",
Use: "debug (POD | TYPE[[.VERSION].GROUP]/NAME) [ -- COMMAND [args...] ]",
DisableFlagsInUseLine: true,
Short: i18n.T("Attach a debug container to a running pod"),
Short: i18n.T("Create debugging sessions for troubleshooting workloads and nodes"),
Long: debugLong,
Example: debugExample,
Run: func(cmd *cobra.Command, args []string) {
@ -155,11 +161,11 @@ func addDebugFlags(cmd *cobra.Command, opt *DebugOptions) {
cmd.Flags().BoolVar(&opt.Attach, "attach", opt.Attach, i18n.T("If true, wait for the container to start running, and then attach as if 'kubectl attach ...' were called. Default false, unless '-i/--stdin' is set, in which case the default is true."))
cmd.Flags().StringVarP(&opt.Container, "container", "c", opt.Container, i18n.T("Container name to use for debug container."))
cmd.Flags().StringVar(&opt.CopyTo, "copy-to", opt.CopyTo, i18n.T("Create a copy of the target Pod with this name."))
cmd.Flags().BoolVar(&opt.Replace, "replace", opt.Replace, i18n.T("When used with '--copy-to', delete the original Pod"))
cmd.Flags().BoolVar(&opt.Replace, "replace", opt.Replace, i18n.T("When used with '--copy-to', delete the original Pod."))
cmd.Flags().StringToString("env", nil, i18n.T("Environment variables to set in the container."))
cmd.Flags().StringVar(&opt.Image, "image", opt.Image, i18n.T("Container image to use for debug container."))
cmd.MarkFlagRequired("image")
cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server"))
cmd.Flags().StringToStringVar(&opt.SetImages, "set-image", opt.SetImages, i18n.T("When used with '--copy-to', a list of name=image pairs for changing container images, similar to how 'kubectl set image' works."))
cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server."))
cmd.Flags().BoolVarP(&opt.Interactive, "stdin", "i", opt.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached."))
cmd.Flags().BoolVar(&opt.Quiet, "quiet", opt.Quiet, i18n.T("If true, suppress informational messages."))
cmd.Flags().BoolVar(&opt.SameNode, "same-node", opt.SameNode, i18n.T("When used with '--copy-to', schedule the copy of target Pod on the same node."))
@ -212,22 +218,30 @@ func (o *DebugOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
// Validate checks that the provided debug options are specified.
func (o *DebugOptions) Validate(cmd *cobra.Command) error {
// CopyTo
// These flags are exclusive to --copy-to
if len(o.CopyTo) == 0 {
if len(o.CopyTo) > 0 {
if len(o.Image) == 0 && len(o.SetImages) == 0 && len(o.Args) == 0 {
return fmt.Errorf("you must specify --image, --set-image or command arguments.")
}
if len(o.Args) > 0 && len(o.Container) == 0 && len(o.Image) == 0 {
return fmt.Errorf("you must specify an existing container or a new image when specifying args.")
}
} else {
// These flags are exclusive to --copy-to
switch {
case o.Replace:
return fmt.Errorf("--replace may only be used with --copy-to.")
case o.SameNode:
return fmt.Errorf("--same-node may only be used with --copy-to.")
case len(o.SetImages) > 0:
return fmt.Errorf("--set-image may only be used with --copy-to.")
case len(o.Image) == 0:
return fmt.Errorf("you must specify --image when not using --copy-to.")
}
}
// Image
if len(o.Image) == 0 {
return fmt.Errorf("--image is required")
}
if !reference.ReferenceRegexp.MatchString(o.Image) {
return fmt.Errorf("Invalid image name %q: %v", o.Image, reference.ErrReferenceInvalidFormat)
if len(o.Image) > 0 && !reference.ReferenceRegexp.MatchString(o.Image) {
return fmt.Errorf("invalid image name %q: %v", o.Image, reference.ErrReferenceInvalidFormat)
}
// Name
@ -243,6 +257,13 @@ func (o *DebugOptions) Validate(cmd *cobra.Command) error {
return fmt.Errorf("invalid image pull policy: %s", o.PullPolicy)
}
// SetImages
for name, image := range o.SetImages {
if !reference.ReferenceRegexp.MatchString(image) {
return fmt.Errorf("invalid image name %q for container %q: %v", image, name, reference.ErrReferenceInvalidFormat)
}
}
// TargetContainer
if len(o.TargetContainer) > 0 && len(o.CopyTo) > 0 {
return fmt.Errorf("--target is incompatible with --copy-to. Use --share-processes instead.")
@ -377,8 +398,11 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
// debugByCopy runs a copy of the target Pod with a debug container added or an original container modified
func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
copied, dc := o.generatePodCopyWithDebugContainer(pod)
copied, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
copied, dc, err := o.generatePodCopyWithDebugContainer(pod)
if err != nil {
return nil, "", err
}
created, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
if err != nil {
return nil, "", err
}
@ -388,7 +412,7 @@ func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev
return nil, "", err
}
}
return copied, dc, nil
return created, dc, nil
}
// generateDebugContainer returns an EphemeralContainer suitable for use as a debug container
@ -483,8 +507,8 @@ func (o *DebugOptions) generateNodeDebugPod(node string) *corev1.Pod {
return p
}
// generatePodCopy takes a Pod and returns a copy and the debug container name of that copy
func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*corev1.Pod, string) {
// generatePodCopyWithDebugContainer takes a Pod and returns a copy and the debug container name of that copy
func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*corev1.Pod, string, error) {
copied := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: o.CopyTo,
@ -503,30 +527,59 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
copied.Spec.NodeName = ""
}
// Apply image mutations
for i, c := range copied.Spec.Containers {
override := o.SetImages["*"]
if img, ok := o.SetImages[c.Name]; ok {
override = img
}
if len(override) > 0 {
copied.Spec.Containers[i].Image = override
}
}
containerByName := containerNameToRef(copied)
c, containerExists := containerByName[o.Container]
// Add a new container if the specified container does not exist
if !containerExists {
name := o.computeDebugContainerName(copied)
c = &corev1.Container{Name: name}
// envs are customizable when adding new container
name := o.Container
if len(name) == 0 {
name = o.computeDebugContainerName(copied)
}
c, ok := containerByName[name]
if !ok {
// Adding a new debug container
if len(o.Image) == 0 {
return nil, "", fmt.Errorf("you must specify image when creating new container")
}
c = &corev1.Container{
Name: name,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
}
defer func() {
copied.Spec.Containers = append(copied.Spec.Containers, *c)
}()
}
if len(o.Args) > 0 {
if o.ArgsOnly {
c.Args = o.Args
} else {
c.Command = o.Args
c.Args = nil
}
}
if len(o.Env) > 0 {
c.Env = o.Env
}
c.Image = o.Image
c.ImagePullPolicy = o.PullPolicy
if len(o.Image) > 0 {
c.Image = o.Image
}
if len(o.PullPolicy) > 0 {
c.ImagePullPolicy = o.PullPolicy
}
c.Stdin = o.Interactive
c.TerminationMessagePolicy = corev1.TerminationMessageReadFile
c.TTY = o.TTY
if o.ArgsOnly {
c.Args = o.Args
} else {
c.Command = o.Args
c.Args = nil
}
if !containerExists {
copied.Spec.Containers = append(copied.Spec.Containers, *c)
}
return copied, c.Name
return copied, name, nil
}
func (o *DebugOptions) computeDebugContainerName(pod *corev1.Pod) string {
@ -632,7 +685,7 @@ func handleAttachPod(ctx context.Context, f cmdutil.Factory, podClient corev1cli
status := getContainerStatusByName(pod, containerName)
if status == nil {
// impossible path
return fmt.Errorf("Error get container status of %s: %+v", containerName, err)
return fmt.Errorf("error getting container status of container name %q: %+v", containerName, err)
}
if status.State.Terminated != nil {
klog.V(1).Info("Ephemeral container terminated, falling back to logs")

View File

@ -250,10 +250,9 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
}
for _, tc := range []struct {
name string
opts *DebugOptions
pod *corev1.Pod
expected *corev1.Pod
name string
opts *DebugOptions
havePod, wantPod *corev1.Pod
}{
{
name: "basic",
@ -263,7 +262,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -276,17 +275,16 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
NodeName: "node-1",
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
},
@ -301,7 +299,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
PullPolicy: corev1.PullIfNotPresent,
SameNode: true,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -314,17 +312,16 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
NodeName: "node-1",
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
NodeName: "node-1",
@ -339,7 +336,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
Labels: map[string]string{
@ -359,7 +356,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
Annotations: map[string]string{
@ -369,10 +366,9 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
},
@ -386,7 +382,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -398,7 +394,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -429,7 +425,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Value: "test",
}},
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -441,7 +437,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -473,7 +469,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -485,7 +481,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -515,7 +511,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -527,7 +523,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -553,24 +549,25 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
CopyTo: "debugger",
Container: "debugger",
Args: []string{"sleep", "1d"},
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Command: []string{"echo"},
Args: []string{"one", "two", "three"},
Name: "debugger",
Command: []string{"echo"},
Image: "app",
Args: []string{"one", "two", "three"},
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -578,7 +575,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
Image: "app",
Command: []string{"sleep", "1d"},
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
@ -594,7 +591,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -606,7 +603,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -632,7 +629,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -644,7 +641,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -670,7 +667,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -690,7 +687,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -724,7 +721,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
@ -748,7 +745,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -777,20 +774,22 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
ShareProcesses: true,
shareProcessedChanged: true,
},
pod: &corev1.Pod{
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Name: "debugger",
ImagePullPolicy: corev1.PullAlways,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
NodeName: "node-1",
},
},
expected: &corev1.Pod{
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
@ -807,17 +806,215 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
},
},
},
{
name: "Change image for a named container",
opts: &DebugOptions{
Args: []string{},
CopyTo: "myapp-copy",
Container: "app",
Image: "busybox",
TargetNames: []string{"myapp"},
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "myapp"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "myapp-copy"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "busybox"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
},
{
name: "Change image for a named container with set-image",
opts: &DebugOptions{
CopyTo: "myapp-copy",
Container: "app",
SetImages: map[string]string{"app": "busybox"},
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp-copy",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "busybox"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
},
{
name: "Change image for all containers with set-image",
opts: &DebugOptions{
CopyTo: "myapp-copy",
Container: "app",
SetImages: map[string]string{"*": "busybox"},
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp-copy",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "busybox"},
{Name: "sidecar", Image: "busybox"},
},
},
},
},
{
name: "Change image for multiple containers with set-image",
opts: &DebugOptions{
CopyTo: "myapp-copy",
Container: "app",
SetImages: map[string]string{"*": "busybox", "app": "app-debugger"},
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp-copy",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "app-debugger"},
{Name: "sidecar", Image: "busybox"},
},
},
},
},
{
name: "Add interactive debug container minimal args",
opts: &DebugOptions{
Args: []string{},
Attach: true,
CopyTo: "my-debugger",
Image: "busybox",
Interactive: true,
TargetNames: []string{"mypod"},
TTY: true,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "mypod"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "my-debugger"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
{
Name: "debugger-1",
Image: "busybox",
Stdin: true,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
TTY: true,
},
},
},
},
},
{
name: "Pod copy: add container and also mutate images",
opts: &DebugOptions{
Args: []string{},
Attach: true,
CopyTo: "my-debugger",
Image: "debian",
Interactive: true,
Namespace: "default",
SetImages: map[string]string{
"app": "app:debug",
"sidecar": "sidecar:debug",
},
ShareProcesses: true,
TargetNames: []string{"mypod"},
TTY: true,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "mypod"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "appimage"},
{Name: "sidecar", Image: "sidecarimage"},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "my-debugger"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "app", Image: "app:debug"},
{Name: "sidecar", Image: "sidecar:debug"},
{
Name: "debugger-1",
Image: "debian",
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Stdin: true,
TTY: true,
},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard()
suffixCounter = 0
if tc.pod == nil {
tc.pod = &corev1.Pod{}
if tc.havePod == nil {
tc.havePod = &corev1.Pod{}
}
pod, _ := tc.opts.generatePodCopyWithDebugContainer(tc.pod)
if diff := cmp.Diff(tc.expected, pod); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
gotPod, _, _ := tc.opts.generatePodCopyWithDebugContainer(tc.havePod)
if diff := cmp.Diff(tc.wantPod, gotPod); diff != "" {
t.Error("TestGeneratePodCopyWithDebugContainer: diff in generated object: (-want +got):\n", diff)
}
})
}
@ -1074,13 +1271,10 @@ func TestCompleteAndValidate(t *testing.T) {
},
{
name: "Set environment variables",
args: "--image=busybox --env=FOO=BAR,BAZ=BAZ mypod",
args: "--image=busybox --env=FOO=BAR mypod",
wantOpts: &DebugOptions{
Args: []string{},
Env: []v1.EnvVar{
{Name: "FOO", Value: "BAR"},
{Name: "BAZ", Value: "BAZ"},
},
Args: []string{},
Env: []v1.EnvVar{{Name: "FOO", Value: "BAR"}},
Image: "busybox",
Namespace: "default",
ShareProcesses: true,
@ -1115,10 +1309,15 @@ func TestCompleteAndValidate(t *testing.T) {
},
},
{
name: "Ephemeral container: image not specified",
name: "Ephemeral container: no image specified",
args: "mypod",
wantError: true,
},
{
name: "Ephemeral container: no image but args",
args: "mypod -- echo 1 2",
wantError: true,
},
{
name: "Ephemeral container: replace not allowed",
args: "--replace --image=busybox mypod",
@ -1129,6 +1328,11 @@ func TestCompleteAndValidate(t *testing.T) {
args: "--same-node --image=busybox mypod",
wantError: true,
},
{
name: "Ephemeral container: incompatible with --set-image",
args: "--set-image=*=busybox mypod",
wantError: true,
},
{
name: "Pod copy: interactive debug container minimal args",
args: "mypod -it --image=busybox --copy-to=my-debugger",
@ -1157,16 +1361,88 @@ func TestCompleteAndValidate(t *testing.T) {
TargetNames: []string{"mypod"},
},
},
{
name: "Pod copy: replace single image of existing container",
args: "mypod --image=busybox --container=my-container --copy-to=my-debugger",
wantOpts: &DebugOptions{
Args: []string{},
Container: "my-container",
CopyTo: "my-debugger",
Image: "busybox",
Namespace: "default",
ShareProcesses: true,
TargetNames: []string{"mypod"},
},
},
{
name: "Pod copy: mutate existing container images",
args: "mypod --set-image=*=busybox,app=app-debugger --copy-to=my-debugger",
wantOpts: &DebugOptions{
Args: []string{},
CopyTo: "my-debugger",
Namespace: "default",
SetImages: map[string]string{
"*": "busybox",
"app": "app-debugger",
},
ShareProcesses: true,
TargetNames: []string{"mypod"},
},
},
{
name: "Pod copy: add container and also mutate images",
args: "mypod -it --copy-to=my-debugger --image=debian --set-image=app=app:debug,sidecar=sidecar:debug",
wantOpts: &DebugOptions{
Args: []string{},
Attach: true,
CopyTo: "my-debugger",
Image: "debian",
Interactive: true,
Namespace: "default",
SetImages: map[string]string{
"app": "app:debug",
"sidecar": "sidecar:debug",
},
ShareProcesses: true,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Pod copy: change command",
args: "mypod -it --copy-to=my-debugger --container=mycontainer -- sh",
wantOpts: &DebugOptions{
Attach: true,
Args: []string{"sh"},
Container: "mycontainer",
CopyTo: "my-debugger",
Interactive: true,
Namespace: "default",
ShareProcesses: true,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Pod copy: no image specified",
args: "mypod -it --copy-to=my-debugger",
wantError: true,
},
{
name: "Pod copy: args but no image specified",
args: "mypod --copy-to=my-debugger -- echo milo",
wantError: true,
},
{
name: "Pod copy: --target not allowed",
args: "mypod --target --image=busybox --copy-to=my-debugger",
wantError: true,
},
{
name: "Pod copy: invalid --set-image",
args: "mypod --set-image=*=SUPERGOODIMAGE#1!!!! --copy-to=my-debugger",
wantError: true,
},
{
name: "Node: interactive session minimal args",
args: "node/mynode -it --image=busybox",
@ -1187,15 +1463,20 @@ func TestCompleteAndValidate(t *testing.T) {
wantError: true,
},
{
name: "Node: replace not allowed",
name: "Node: --replace not allowed",
args: "--image=busybox --replace node/mynode",
wantError: true,
},
{
name: "Node: same-node not allowed",
name: "Node: --same-node not allowed",
args: "--image=busybox --same-node node/mynode",
wantError: true,
},
{
name: "Node: --set-image not allowed",
args: "--image=busybox --set-image=*=busybox node/mynode",
wantError: true,
},
{
name: "Node: --target not allowed",
args: "node/mynode --target --image=busybox",