Add custom debug profiles on top of static profiles

This PR adds `custom` flag to let user customizes debug resources.
`custom` flag accepts partial container spec in json format.

Kubernetes-commit: af2dadcb18a0411a0f9aa286c743801efdb81049
This commit is contained in:
Arda Güçlü 2023-09-01 11:46:01 +03:00 committed by Kubernetes Publisher
parent 2855ed117e
commit 14d23cc879
3 changed files with 838 additions and 21 deletions

View File

@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"time"
"github.com/distribution/reference"
@ -106,29 +107,33 @@ var (
var nameSuffixFunc = utilrand.String
type DebugAttachFunc func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
// DebugOptions holds the options for an invocation of kubectl debug.
type DebugOptions struct {
Args []string
ArgsOnly bool
Attach bool
AttachFunc func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
Container string
CopyTo string
Replace bool
Env []corev1.EnvVar
Image string
Interactive bool
Namespace string
TargetNames []string
PullPolicy corev1.PullPolicy
Quiet bool
SameNode bool
SetImages map[string]string
ShareProcesses bool
TargetContainer string
TTY bool
Profile string
Applier ProfileApplier
Args []string
ArgsOnly bool
Attach bool
AttachFunc DebugAttachFunc
Container string
CopyTo string
Replace bool
Env []corev1.EnvVar
Image string
Interactive bool
Namespace string
TargetNames []string
PullPolicy corev1.PullPolicy
Quiet bool
SameNode bool
SetImages map[string]string
ShareProcesses bool
TargetContainer string
TTY bool
Profile string
CustomProfileFile string
CustomProfile *corev1.Container
Applier ProfileApplier
explicitNamespace bool
attachChanged bool
@ -193,6 +198,9 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
if cmdutil.DebugCustomProfile.IsEnabled() {
cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON file containing a partial container spec to customize built-in debug profiles."))
}
}
// Complete finishes run-time initialization of debug.DebugOptions.
@ -256,6 +264,18 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
o.Applier = applier
}
if o.CustomProfileFile != "" {
customProfileBytes, err := os.ReadFile(o.CustomProfileFile)
if err != nil {
return fmt.Errorf("must pass a container spec json file for custom profile: %w", err)
}
err = json.Unmarshal(customProfileBytes, &o.CustomProfile)
if err != nil {
return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
}
}
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return err
@ -348,6 +368,12 @@ func (o *DebugOptions) Validate() error {
return fmt.Errorf("WarningPrinter can not be used without initialization")
}
if o.CustomProfile != nil {
if o.CustomProfile.Name != "" || len(o.CustomProfile.Command) > 0 || o.CustomProfile.Image != "" || o.CustomProfile.Lifecycle != nil || len(o.CustomProfile.VolumeDevices) > 0 {
return fmt.Errorf("name, command, image, lifecycle and volume devices are not modifiable via custom profile")
}
}
return nil
}
@ -467,6 +493,90 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
return result, debugContainer.Name, nil
}
// applyCustomProfile applies given partial container json file on to the profile
// incorporated debug pod.
func (o *DebugOptions) applyCustomProfile(debugPod *corev1.Pod, containerName string) error {
o.CustomProfile.Name = containerName
customJS, err := json.Marshal(o.CustomProfile)
if err != nil {
return fmt.Errorf("unable to marshall custom profile: %w", err)
}
var index int
found := false
for i, val := range debugPod.Spec.Containers {
if val.Name == containerName {
index = i
found = true
break
}
}
if !found {
return fmt.Errorf("unable to find the %s container in the pod %s", containerName, debugPod.Name)
}
var debugContainerJS []byte
debugContainerJS, err = json.Marshal(debugPod.Spec.Containers[index])
if err != nil {
return fmt.Errorf("unable to marshall container: %w", err)
}
patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
if err != nil {
return fmt.Errorf("error creating three way patch to add debug container: %w", err)
}
err = json.Unmarshal(patchedContainer, &debugPod.Spec.Containers[index])
if err != nil {
return fmt.Errorf("unable to unmarshall patched container to container: %w", err)
}
return nil
}
// applyCustomProfileEphemeral applies given partial container json file on to the profile
// incorporated ephemeral container of the pod.
func (o *DebugOptions) applyCustomProfileEphemeral(debugPod *corev1.Pod, containerName string) error {
o.CustomProfile.Name = containerName
customJS, err := json.Marshal(o.CustomProfile)
if err != nil {
return fmt.Errorf("unable to marshall custom profile: %w", err)
}
var index int
found := false
for i, val := range debugPod.Spec.EphemeralContainers {
if val.Name == containerName {
index = i
found = true
break
}
}
if !found {
return fmt.Errorf("unable to find the %s ephemeral container in the pod %s", containerName, debugPod.Name)
}
var debugContainerJS []byte
debugContainerJS, err = json.Marshal(debugPod.Spec.EphemeralContainers[index])
if err != nil {
return fmt.Errorf("unable to marshall ephemeral container:%w", err)
}
patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
if err != nil {
return fmt.Errorf("error creating three way patch to add debug container: %w", err)
}
err = json.Unmarshal(patchedContainer, &debugPod.Spec.EphemeralContainers[index])
if err != nil {
return fmt.Errorf("unable to unmarshall patched container to ephemeral container: %w", err)
}
return nil
}
// 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, err := o.generatePodCopyWithDebugContainer(pod)
@ -515,6 +625,13 @@ func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *co
return nil, nil, err
}
if o.CustomProfile != nil {
err := o.applyCustomProfileEphemeral(copied, ec.Name)
if err != nil {
return nil, nil, err
}
}
ec = &copied.Spec.EphemeralContainers[len(copied.Spec.EphemeralContainers)-1]
return copied, ec, nil
@ -574,6 +691,13 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
return nil, err
}
if o.CustomProfile != nil {
err := o.applyCustomProfile(p, cn)
if err != nil {
return nil, err
}
}
return p, nil
}
@ -656,6 +780,13 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
return nil, "", err
}
if o.CustomProfile != nil {
err = o.applyCustomProfile(copied, name)
if err != nil {
return nil, "", err
}
}
return copied, name, nil
}

View File

@ -22,6 +22,8 @@ import (
"testing"
"time"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
@ -1755,6 +1757,689 @@ func TestGenerateNodeDebugPod(t *testing.T) {
}
}
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: pointer.Bool(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: pointer.Bool(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: pointer.Bool(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
AllowPrivilegeEscalation: pointer.Bool(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: pointer.Bool(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) {
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
var err error
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
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: pointer.Bool(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: pointer.Bool(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
},
},
},
HostIPC: false,
HostNetwork: false,
HostPID: false,
Volumes: nil,
ShareProcessNamespace: pointer.Bool(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: pointer.Bool(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: pointer.Bool(false),
RunAsNonRoot: pointer.Bool(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: pointer.Bool(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: pointer.Bool(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: pointer.Bool(false),
RunAsNonRoot: pointer.Bool(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: pointer.Bool(true),
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
var err error
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
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: pointer.Bool(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: pointer.Bool(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: pointer.Bool(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: pointer.Bool(false),
RunAsNonRoot: pointer.Bool(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: pointer.Bool(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: pointer.Bool(false),
RunAsNonRoot: pointer.Bool(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) {
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
var err error
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
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()

View File

@ -431,6 +431,7 @@ const (
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
)
// IsEnabled returns true iff environment variable is set to true.