kubectl/pkg/cmd/debug/debug_test.go

2945 lines
77 KiB
Go

/*
Copyright 2020 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.
*/
package debug
import (
"fmt"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"k8s.io/utils/ptr"
)
func TestGenerateDebugContainer(t *testing.T) {
// Slightly less randomness for testing.
defer func(old func(int) string) { nameSuffixFunc = old }(nameSuffixFunc)
var suffixCounter int
nameSuffixFunc = func(int) string {
suffixCounter++
return fmt.Sprint(suffixCounter)
}
for _, tc := range []struct {
name string
opts *DebugOptions
pod *corev1.Pod
expected *corev1.EphemeralContainer
}{
{
name: "minimum fields",
opts: &DebugOptions{
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "namespace targeting",
opts: &DebugOptions{
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
TargetContainer: "myapp",
Profile: ProfileLegacy,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
TargetContainerName: "myapp",
},
},
{
name: "debug args as container command",
opts: &DebugOptions{
Args: []string{"/bin/echo", "one", "two", "three"},
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger",
Command: []string{"/bin/echo", "one", "two", "three"},
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "debug args as container args",
opts: &DebugOptions{
ArgsOnly: true,
Container: "debugger",
Args: []string{"echo", "one", "two", "three"},
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger",
Args: []string{"echo", "one", "two", "three"},
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "random name generation",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "random name collision",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
pod: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger-1",
},
},
},
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-2",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "pod with init containers",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
pod: &corev1.Pod{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init-container-1",
},
{
Name: "init-container-2",
},
},
Containers: []corev1.Container{
{
Name: "debugger",
},
},
},
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "pod with ephemeral containers",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
pod: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "ephemeral-container-1",
},
},
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "ephemeral-container-2",
},
},
},
},
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
},
{
name: "general profile",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileGeneral,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"SYS_PTRACE"},
},
},
},
},
},
{
name: "baseline profile",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
{
name: "restricted profile",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
AllowPrivilegeEscalation: ptr.To(false),
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
},
},
},
},
{
name: "netadmin profile",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileNetadmin,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"},
},
},
},
},
},
{
name: "sysadmin profile",
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileSysadmin,
},
expected: &corev1.EphemeralContainer{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
SecurityContext: &corev1.SecurityContext{
Privileged: ptr.To(true),
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("failed to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
suffixCounter = 0
if tc.pod == nil {
tc.pod = &corev1.Pod{}
}
_, debugContainer, err := tc.opts.generateDebugContainer(tc.pod)
if err != nil {
t.Fatalf("fail to generate debug container: %v", err)
}
if diff := cmp.Diff(tc.expected, debugContainer); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}
func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
defer func(old func(int) string) { nameSuffixFunc = old }(nameSuffixFunc)
var suffixCounter int
nameSuffixFunc = func(int) string {
suffixCounter++
return fmt.Sprint(suffixCounter)
}
for _, tc := range []struct {
name string
opts *DebugOptions
havePod, wantPod *corev1.Pod
}{
{
name: "basic",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
},
},
},
{
name: "same node",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
SameNode: true,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
Labels: map[string]string{
"app": "business",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
NodeName: "node-1",
},
},
},
{
name: "metadata stripping",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
Labels: map[string]string{
"app": "business",
},
Annotations: map[string]string{
"test": "test",
},
ResourceVersion: "1",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
Annotations: map[string]string{
"test": "test",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
},
},
},
{
name: "add a debug container",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "customize envs",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{{
Name: "TEST",
Value: "test",
}},
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Env: []corev1.EnvVar{{
Name: "TEST",
Value: "test",
}},
},
},
},
},
},
{
name: "debug args as container command",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Args: []string{"/bin/echo", "one", "two", "three"},
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
{
Name: "debugger",
Image: "busybox",
Command: []string{"/bin/echo", "one", "two", "three"},
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "debug args as container command",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Args: []string{"one", "two", "three"},
ArgsOnly: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
{
Name: "debugger",
Image: "busybox",
Args: []string{"one", "two", "three"},
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "modify existing command to debug args",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Args: []string{"sleep", "1d"},
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Command: []string{"echo"},
Image: "app",
Args: []string{"one", "two", "three"},
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "app",
Command: []string{"sleep", "1d"},
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "random name",
opts: &DebugOptions{
CopyTo: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
},
{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "random name collision",
opts: &DebugOptions{
CopyTo: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger-1",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger-1",
},
{
Name: "debugger-2",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "pod with probes",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
KeepLiveness: true,
KeepReadiness: true,
KeepStartup: true,
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
LivenessProbe: &corev1.Probe{},
ReadinessProbe: &corev1.Probe{},
StartupProbe: &corev1.Probe{},
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "business",
LivenessProbe: &corev1.Probe{},
ReadinessProbe: &corev1.Probe{},
StartupProbe: &corev1.Probe{},
},
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "pod with init containers",
opts: &DebugOptions{
CopyTo: "debugger",
Image: "busybox",
KeepInitContainers: true,
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init-container-1",
},
{
Name: "init-container-2",
},
},
Containers: []corev1.Container{
{
Name: "debugger-1",
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init-container-1",
},
{
Name: "init-container-2",
},
},
Containers: []corev1.Container{
{
Name: "debugger-1",
},
{
Name: "debugger-2",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "pod with ephemeral containers",
opts: &DebugOptions{
CopyTo: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger-1",
},
},
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "ephemeral-container-1",
},
},
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "ephemeral-container-2",
},
},
},
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger-1",
},
{
Name: "debugger-2",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
},
},
},
{
name: "shared process namespace",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
ShareProcesses: true,
shareProcessedChanged: true,
Profile: ProfileLegacy,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
ImagePullPolicy: corev1.PullAlways,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "Change image for a named container",
opts: &DebugOptions{
Args: []string{},
CopyTo: "myapp-copy",
Container: "app",
Image: "busybox",
TargetNames: []string{"myapp"},
Profile: ProfileLegacy,
},
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"},
Profile: ProfileLegacy,
},
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",
SetImages: map[string]string{"*": "busybox"},
Profile: ProfileLegacy,
},
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",
SetImages: map[string]string{"*": "busybox", "app": "app-debugger"},
Profile: ProfileLegacy,
},
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,
Profile: ProfileLegacy,
},
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,
Profile: ProfileLegacy,
},
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,
},
},
},
},
},
{
name: "general profile",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileGeneral,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"SYS_PTRACE"},
},
},
},
},
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "baseline profile",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "baseline profile not share process when user explicitly disables it",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
ShareProcesses: false,
shareProcessedChanged: true,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
ShareProcessNamespace: ptr.To(false),
},
},
},
{
name: "restricted profile",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
AllowPrivilegeEscalation: ptr.To(false),
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
},
},
},
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "netadmin profile",
opts: &DebugOptions{
CopyTo: "debugger",
Container: "debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileNetadmin,
},
havePod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "target",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
},
},
NodeName: "node-1",
},
},
wantPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "debugger",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"},
},
},
},
},
ShareProcessNamespace: ptr.To(true),
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
suffixCounter = 0
if tc.havePod == nil {
tc.havePod = &corev1.Pod{}
}
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)
}
})
}
}
func TestGenerateNodeDebugPod(t *testing.T) {
defer func(old func(int) string) { nameSuffixFunc = old }(nameSuffixFunc)
var suffixCounter int
nameSuffixFunc = func(int) string {
suffixCounter++
return fmt.Sprint(suffixCounter)
}
for _, tc := range []struct {
name string
node *corev1.Node
opts *DebugOptions
expected *corev1.Pod
}{
{
name: "minimum options",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: []corev1.VolumeMount{
{
MountPath: "/host",
Name: "host-root",
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: []corev1.Volume{
{
Name: "host-root",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/"},
},
},
},
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "debug args as container command",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Args: []string{"/bin/echo", "one", "two", "three"},
Container: "custom-debugger",
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "custom-debugger",
Command: []string{"/bin/echo", "one", "two", "three"},
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: []corev1.VolumeMount{
{
MountPath: "/host",
Name: "host-root",
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: []corev1.Volume{
{
Name: "host-root",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/"},
},
},
},
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "debug args as container args",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
ArgsOnly: true,
Container: "custom-debugger",
Args: []string{"echo", "one", "two", "three"},
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileLegacy,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "custom-debugger",
Args: []string{"echo", "one", "two", "three"},
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: []corev1.VolumeMount{
{
MountPath: "/host",
Name: "host-root",
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: []corev1.Volume{
{
Name: "host-root",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/"},
},
},
},
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "general profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileGeneral,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: []corev1.VolumeMount{
{
MountPath: "/host",
Name: "host-root",
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: []corev1.Volume{
{
Name: "host-root",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/"},
},
},
},
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "baseline profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "restricted profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
AllowPrivilegeEscalation: ptr.To(false),
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "netadmin profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileNetadmin,
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"},
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
suffixCounter = 0
pod, err := tc.opts.generateNodeDebugPod(tc.node)
if err != nil {
t.Fatalf("Fail to generate node debug pod: %v", err)
}
if diff := cmp.Diff(tc.expected, pod); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}
func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
for _, tc := range []struct {
name string
node *corev1.Node
opts *DebugOptions
expected *corev1.Pod
}{
{
name: "baseline profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "restricted profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
},
},
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
AllowPrivilegeEscalation: ptr.To(false),
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "netadmin profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileNetadmin,
CustomProfile: &corev1.Container{
Env: []corev1.EnvVar{
{
Name: "TEST_KEY",
Value: "TEST_VALUE",
},
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Env: []corev1.EnvVar{
{
Name: "TEST_KEY",
Value: "TEST_VALUE",
},
},
VolumeMounts: nil,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"},
},
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: nil,
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
{
name: "sysadmin profile",
node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-XXX",
},
},
opts: &DebugOptions{
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileSysadmin,
CustomProfile: &corev1.Container{
Env: []corev1.EnvVar{
{
Name: "TEST_KEY",
Value: "TEST_VALUE",
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "host-root",
ReadOnly: true,
MountPath: "/host",
},
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "debugger",
Image: "busybox",
ImagePullPolicy: corev1.PullIfNotPresent,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
Env: []corev1.EnvVar{
{
Name: "TEST_KEY",
Value: "TEST_VALUE",
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "host-root",
ReadOnly: true,
MountPath: "/host",
},
},
SecurityContext: &corev1.SecurityContext{
Privileged: ptr.To(true),
},
},
},
HostIPC: true,
HostNetwork: true,
HostPID: true,
NodeName: "node-XXX",
RestartPolicy: corev1.RestartPolicyNever,
Volumes: []corev1.Volume{
{
Name: "host-root",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/",
},
},
},
},
Tolerations: []corev1.Toleration{
{
Operator: corev1.TolerationOpExists,
},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
pod, err := tc.opts.generateNodeDebugPod(tc.node)
if err != nil {
t.Fatalf("Fail to generate node debug pod: %v", err)
}
tc.expected.Name = pod.Name
if diff := cmp.Diff(tc.expected, pod); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}
func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
for _, tc := range []struct {
name string
copyPod *corev1.Pod
opts *DebugOptions
expected *corev1.Pod
}{
{
name: "baseline profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
Containers: []corev1.Container{
{
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "restricted profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
Containers: []corev1.Container{
{
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false),
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
LocalhostProfile: nil,
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
ShareProcessNamespace: ptr.To(true),
},
},
},
{
name: "sysadmin profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
Containers: []corev1.Container{
{
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false),
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
LocalhostProfile: nil,
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
ShareProcessNamespace: ptr.To(true),
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
if err != nil {
t.Fatalf("Fail to generate node debug pod: %v", err)
}
tc.expected.Spec.Containers[0].Name = dc
if diff := cmp.Diff(tc.expected, pod); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}
func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
for _, tc := range []struct {
name string
copyPod *corev1.Pod
opts *DebugOptions
expected *corev1.Pod
}{
{
name: "baseline profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileBaseline,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
},
},
},
{
name: "restricted profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false),
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
LocalhostProfile: nil,
},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
},
},
},
{
name: "sysadmin profile",
copyPod: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
},
},
opts: &DebugOptions{
SameNode: true,
Image: "busybox",
PullPolicy: corev1.PullIfNotPresent,
Profile: ProfileRestricted,
CustomProfile: &corev1.Container{
ImagePullPolicy: corev1.PullNever,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
RunAsNonRoot: ptr.To(false),
},
},
},
expected: &corev1.Pod{
Spec: corev1.PodSpec{
ServiceAccountName: "test",
NodeName: "test-node",
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "debugger-1",
Image: "busybox",
ImagePullPolicy: corev1.PullNever,
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
VolumeMounts: nil,
Stdin: true,
TTY: false,
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false),
RunAsNonRoot: ptr.To(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
LocalhostProfile: nil,
},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var err error
kflags := KeepFlags{
Labels: tc.opts.KeepLabels,
Annotations: tc.opts.KeepAnnotations,
Liveness: tc.opts.KeepLiveness,
Readiness: tc.opts.KeepReadiness,
Startup: tc.opts.KeepStartup,
InitContainers: tc.opts.KeepInitContainers,
}
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
if err != nil {
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
}
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
if err != nil {
t.Fatalf("Fail to generate node debug pod: %v", err)
}
tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
if diff := cmp.Diff(tc.expected, pod); diff != "" {
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}
func TestCompleteAndValidate(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
cmpFilter := cmp.FilterPath(func(p cmp.Path) bool {
switch p.String() {
// IOStreams contains unexported fields
case "IOStreams", "Applier":
return true
}
return false
}, cmp.Ignore())
tests := []struct {
name, args string
wantOpts *DebugOptions
wantError bool
}{
{
name: "No targets",
args: "--image=image",
wantError: true,
},
{
name: "Invalid environment variables",
args: "--image=busybox --env=FOO mypod",
wantError: true,
},
{
name: "Invalid image name",
args: "--image=image:label@deadbeef mypod",
wantError: true,
},
{
name: "Invalid pull policy",
args: "--image=image --image-pull-policy=whenever-you-feel-like-it",
wantError: true,
},
{
name: "TTY without stdin",
args: "--image=image --tty",
wantError: true,
},
{
name: "Set image pull policy",
args: "--image=busybox --image-pull-policy=Always mypod",
wantOpts: &DebugOptions{
Args: []string{},
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
PullPolicy: corev1.PullPolicy("Always"),
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
},
},
{
name: "Multiple targets",
args: "--image=busybox mypod1 mypod2",
wantOpts: &DebugOptions{
Args: []string{},
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod1", "mypod2"},
},
},
{
name: "Arguments with dash",
args: "--image=busybox mypod1 mypod2 -- echo 1 2",
wantOpts: &DebugOptions{
Args: []string{"echo", "1", "2"},
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod1", "mypod2"},
},
},
{
name: "Interactive no attach",
args: "-ti --image=busybox --attach=false mypod",
wantOpts: &DebugOptions{
Args: []string{},
Attach: false,
Image: "busybox",
KeepInitContainers: true,
Interactive: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Set environment variables",
args: "--image=busybox --env=FOO=BAR mypod",
wantOpts: &DebugOptions{
Args: []string{},
Env: []corev1.EnvVar{{Name: "FOO", Value: "BAR"}},
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
},
},
{
name: "Ephemeral container: interactive session minimal args",
args: "mypod -it --image=busybox",
wantOpts: &DebugOptions{
Args: []string{},
Attach: true,
Image: "busybox",
Interactive: true,
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Ephemeral container: non-interactive debugger with image and name",
args: "--image=myproj/debug-tools --image-pull-policy=Always -c debugger mypod",
wantOpts: &DebugOptions{
Args: []string{},
Container: "debugger",
Image: "myproj/debug-tools",
KeepInitContainers: true,
Namespace: "test",
PullPolicy: corev1.PullPolicy("Always"),
Profile: ProfileLegacy,
ShareProcesses: true,
TargetNames: []string{"mypod"},
},
},
{
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",
wantError: true,
},
{
name: "Ephemeral container: same-node not allowed",
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",
wantOpts: &DebugOptions{
Args: []string{},
Attach: true,
CopyTo: "my-debugger",
Image: "busybox",
Interactive: true,
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Pod copy: non-interactive with debug container, image name and command",
args: "mypod --image=busybox --container=my-container --copy-to=my-debugger -- sleep 1d",
wantOpts: &DebugOptions{
Args: []string{"sleep", "1d"},
Container: "my-container",
CopyTo: "my-debugger",
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
},
},
{
name: "Pod copy: explicit attach",
args: "mypod --image=busybox --copy-to=my-debugger --attach -- sleep 1d",
wantOpts: &DebugOptions{
Args: []string{"sleep", "1d"},
Attach: true,
CopyTo: "my-debugger",
Image: "busybox",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
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",
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
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",
KeepInitContainers: true,
Namespace: "test",
SetImages: map[string]string{
"*": "busybox",
"app": "app-debugger",
},
ShareProcesses: true,
Profile: ProfileLegacy,
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,
KeepInitContainers: true,
Namespace: "test",
SetImages: map[string]string{
"app": "app:debug",
"sidecar": "sidecar:debug",
},
ShareProcesses: true,
Profile: ProfileLegacy,
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,
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"mypod"},
TTY: true,
},
},
{
name: "Pod copy: change keep options from defaults",
args: "mypod -it --image=busybox --copy-to=my-debugger --keep-labels=true --keep-annotations=true --keep-liveness=true --keep-readiness=true --keep-startup=true --keep-init-containers=false",
wantOpts: &DebugOptions{
Args: []string{},
Attach: true,
CopyTo: "my-debugger",
Image: "busybox",
Interactive: true,
KeepLabels: true,
KeepAnnotations: true,
KeepLiveness: true,
KeepReadiness: true,
KeepStartup: true,
KeepInitContainers: false,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
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: "Pod copy: specifying attach without existing or newly created container",
args: "mypod --set-image=*=busybox --copy-to=my-debugger --attach",
wantError: true,
},
{
name: "Node: interactive session minimal args",
args: "node/mynode -it --image=busybox",
wantOpts: &DebugOptions{
Args: []string{},
Attach: true,
Image: "busybox",
Interactive: true,
KeepInitContainers: true,
Namespace: "test",
ShareProcesses: true,
Profile: ProfileLegacy,
TargetNames: []string{"node/mynode"},
TTY: true,
},
},
{
name: "Node: no image specified",
args: "node/mynode -it",
wantError: true,
},
{
name: "Node: --replace not allowed",
args: "--image=busybox --replace node/mynode",
wantError: true,
},
{
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",
wantError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
opts := NewDebugOptions(ioStreams)
var gotError error
cmd := &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
gotError = opts.Complete(tf, cmd, args)
if gotError != nil {
return
}
gotError = opts.Validate()
},
}
cmd.SetArgs(strings.Split(tc.args, " "))
opts.AddFlags(cmd)
cmdError := cmd.Execute()
if tc.wantError {
if cmdError != nil || gotError != nil {
return
}
t.Fatalf("CompleteAndValidate got nil errors but wantError: %v", tc.wantError)
} else if cmdError != nil {
t.Fatalf("cmd.Execute got error '%v' executing test cobra.Command, wantError: %v", cmdError, tc.wantError)
} else if gotError != nil {
t.Fatalf("CompleteAndValidate got error: '%v', wantError: %v", gotError, tc.wantError)
}
if diff := cmp.Diff(tc.wantOpts, opts, cmpFilter, cmpopts.IgnoreFields(DebugOptions{},
"attachChanged", "shareProcessedChanged", "podClient", "WarningPrinter", "Applier", "explicitNamespace", "Builder", "AttachFunc")); diff != "" {
t.Error("CompleteAndValidate unexpected diff in generated object: (-want +got):\n", diff)
}
})
}
}