diff --git a/hack/testdata/pod-with-metadata-and-probes.yaml b/hack/testdata/pod-with-metadata-and-probes.yaml new file mode 100644 index 00000000000..4087c14b408 --- /dev/null +++ b/hack/testdata/pod-with-metadata-and-probes.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + test: test + labels: + run: target + name: target +spec: + containers: + - image: registry.k8s.io/nginx:1.7.9 + name: target + readinessProbe: + exec: + command: ["/bin/sh", "-c", "cat probe"] + livenessProbe: + exec: + command: ["/bin/sh", "-c", "cat probe"] + startupProbe: + exec: + command: ["/bin/sh", "-c", "cat probe"] + initContainers: + - image: busybox + name: init diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go index 53ca2660a5a..6a3bd6ece92 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go @@ -111,29 +111,35 @@ type DebugAttachFunc func(ctx context.Context, restClientGetter genericclioption // DebugOptions holds the options for an invocation of kubectl debug. type DebugOptions struct { - 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 + Args []string + ArgsOnly bool + Attach bool + AttachFunc DebugAttachFunc + Container string + CopyTo string + Replace bool + Env []corev1.EnvVar + Image string + Interactive bool + KeepLabels bool + KeepAnnotations bool + KeepLiveness bool + KeepReadiness bool + KeepStartup bool + KeepInitContainers 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 @@ -151,10 +157,11 @@ type DebugOptions struct { // NewDebugOptions returns a DebugOptions initialized with default values. func NewDebugOptions(streams genericiooptions.IOStreams) *DebugOptions { return &DebugOptions{ - Args: []string{}, - IOStreams: streams, - TargetNames: []string{}, - ShareProcesses: true, + Args: []string{}, + IOStreams: streams, + KeepInitContainers: true, + TargetNames: []string{}, + ShareProcesses: true, } } @@ -189,6 +196,12 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.Replace, "replace", o.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(&o.Image, "image", o.Image, i18n.T("Container image to use for debug container.")) + cmd.Flags().BoolVar(&o.KeepLabels, "keep-labels", o.KeepLabels, i18n.T("If true, keep the original pod labels.(This flag only works when used with '--copy-to')")) + cmd.Flags().BoolVar(&o.KeepAnnotations, "keep-annotations", o.KeepAnnotations, i18n.T("If true, keep the original pod annotations.(This flag only works when used with '--copy-to')")) + cmd.Flags().BoolVar(&o.KeepLiveness, "keep-liveness", o.KeepLiveness, i18n.T("If true, keep the original pod liveness probes.(This flag only works when used with '--copy-to')")) + cmd.Flags().BoolVar(&o.KeepReadiness, "keep-readiness", o.KeepReadiness, i18n.T("If true, keep the original pod readiness probes.(This flag only works when used with '--copy-to')")) + cmd.Flags().BoolVar(&o.KeepStartup, "keep-startup", o.KeepStartup, i18n.T("If true, keep the original startup probes.(This flag only works when used with '--copy-to')")) + cmd.Flags().BoolVar(&o.KeepInitContainers, "keep-init-containers", o.KeepInitContainers, i18n.T("Run the init containers for the pod. Defaults to true.(This flag only works when used with '--copy-to')")) cmd.Flags().StringToStringVar(&o.SetImages, "set-image", o.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(&o.Interactive, "stdin", "i", o.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached.")) @@ -257,7 +270,15 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet } if o.Applier == nil { - applier, err := NewProfileApplier(o.Profile) + kflags := KeepFlags{ + Labels: o.KeepLabels, + Annotations: o.KeepAnnotations, + Liveness: o.KeepLiveness, + Readiness: o.KeepReadiness, + Startup: o.KeepStartup, + InitContainers: o.KeepInitContainers, + } + applier, err := NewProfileApplier(o.Profile, kflags) if err != nil { return err } @@ -708,6 +729,7 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core Name: o.CopyTo, Namespace: pod.Namespace, Annotations: pod.Annotations, + Labels: pod.Labels, }, Spec: *pod.Spec.DeepCopy(), } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go index a4c25606b72..06150b077e8 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go @@ -339,18 +339,24 @@ func TestGenerateDebugContainer(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard() - suffixCounter = 0 - - if tc.pod == nil { - tc.pod = &corev1.Pod{} + 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, } - - applier, err := NewProfileApplier(tc.opts.Profile) + 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.Applier = applier + 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 { @@ -426,6 +432,9 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "target", + Labels: map[string]string{ + "app": "business", + }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -792,13 +801,63 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { }, }, }, + { + 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", - PullPolicy: corev1.PullIfNotPresent, - Profile: ProfileLegacy, + CopyTo: "debugger", + Image: "busybox", + KeepInitContainers: true, + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1356,7 +1415,15 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var err error - tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile) + 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) } @@ -1739,7 +1806,15 @@ func TestGenerateNodeDebugPod(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var err error - tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile) + 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) } @@ -2015,7 +2090,15 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) { 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) + 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) } @@ -2215,7 +2298,15 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) { 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) + 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) } @@ -2421,7 +2512,15 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) { 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) + 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) } @@ -2486,94 +2585,101 @@ func TestCompleteAndValidate(t *testing.T) { name: "Set image pull policy", args: "--image=busybox --image-pull-policy=Always mypod", wantOpts: &DebugOptions{ - Args: []string{}, - Image: "busybox", - Namespace: "test", - PullPolicy: corev1.PullPolicy("Always"), - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod1", "mypod2"}, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod1", "mypod2"}, + 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", - Interactive: true, - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, - TTY: true, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, + 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, - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, - TTY: true, + 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", - Namespace: "test", - PullPolicy: corev1.PullPolicy("Always"), - Profile: ProfileLegacy, - ShareProcesses: true, - TargetNames: []string{"mypod"}, + Args: []string{}, + Container: "debugger", + Image: "myproj/debug-tools", + KeepInitContainers: true, + Namespace: "test", + PullPolicy: corev1.PullPolicy("Always"), + Profile: ProfileLegacy, + ShareProcesses: true, + TargetNames: []string{"mypod"}, }, }, { @@ -2605,67 +2711,72 @@ func TestCompleteAndValidate(t *testing.T) { 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, - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, - TTY: true, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, + 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", - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, + 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", - Namespace: "test", + Args: []string{}, + CopyTo: "my-debugger", + KeepInitContainers: true, + Namespace: "test", SetImages: map[string]string{ "*": "busybox", "app": "app-debugger", @@ -2679,12 +2790,13 @@ func TestCompleteAndValidate(t *testing.T) { 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: "test", + 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", @@ -2699,16 +2811,39 @@ func TestCompleteAndValidate(t *testing.T) { 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: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"mypod"}, - TTY: true, + 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, }, }, { @@ -2740,15 +2875,16 @@ func TestCompleteAndValidate(t *testing.T) { name: "Node: interactive session minimal args", args: "node/mynode -it --image=busybox", wantOpts: &DebugOptions{ - Args: []string{}, - Attach: true, - Image: "busybox", - Interactive: true, - Namespace: "test", - ShareProcesses: true, - Profile: ProfileLegacy, - TargetNames: []string{"node/mynode"}, - TTY: true, + Args: []string{}, + Attach: true, + Image: "busybox", + Interactive: true, + KeepInitContainers: true, + Namespace: "test", + ShareProcesses: true, + Profile: ProfileLegacy, + TargetNames: []string{"node/mynode"}, + TTY: true, }, }, { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles.go index 2e0f8621ee7..4f6ef2f5ffa 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles.go @@ -64,54 +64,91 @@ type ProfileApplier interface { } // NewProfileApplier returns a new Options for the given profile name. -func NewProfileApplier(profile string) (ProfileApplier, error) { +func NewProfileApplier(profile string, kflags KeepFlags) (ProfileApplier, error) { switch profile { case ProfileLegacy: - return &legacyProfile{}, nil + return &legacyProfile{kflags}, nil case ProfileGeneral: - return &generalProfile{}, nil + return &generalProfile{kflags}, nil case ProfileBaseline: - return &baselineProfile{}, nil + return &baselineProfile{kflags}, nil case ProfileRestricted: - return &restrictedProfile{}, nil + return &restrictedProfile{kflags}, nil case ProfileNetadmin: - return &netadminProfile{}, nil + return &netadminProfile{kflags}, nil case ProfileSysadmin: - return &sysadminProfile{}, nil + return &sysadminProfile{kflags}, nil } - return nil, fmt.Errorf("unknown profile: %s", profile) } type legacyProfile struct { + KeepFlags } type generalProfile struct { + KeepFlags } type baselineProfile struct { + KeepFlags } type restrictedProfile struct { + KeepFlags } type netadminProfile struct { + KeepFlags } type sysadminProfile struct { + KeepFlags } -func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { - switch target.(type) { - case *corev1.Pod: - // do nothing to the copied pod - return nil - case *corev1.Node: - mountRootPartition(pod, containerName) - useHostNamespaces(pod) - return nil - default: - return fmt.Errorf("the %s profile doesn't support objects of type %T", ProfileLegacy, target) +// KeepFlags holds the flag set that determine which fields to keep in the copy pod. +type KeepFlags struct { + Labels bool + Annotations bool + Liveness bool + Readiness bool + Startup bool + InitContainers bool +} + +// RemoveLabels removes labels from the pod. +func (kflags KeepFlags) RemoveLabels(p *corev1.Pod) { + if !kflags.Labels { + p.Labels = nil + } +} + +// RemoveAnnotations remove annotations from the pod. +func (kflags KeepFlags) RemoveAnnotations(p *corev1.Pod) { + if !kflags.Annotations { + p.Annotations = nil + } +} + +// RemoveProbes remove probes from all containers of the pod. +func (kflags KeepFlags) RemoveProbes(p *corev1.Pod) { + for i := range p.Spec.Containers { + if !kflags.Liveness { + p.Spec.Containers[i].LivenessProbe = nil + } + if !kflags.Readiness { + p.Spec.Containers[i].ReadinessProbe = nil + } + if !kflags.Startup { + p.Spec.Containers[i].StartupProbe = nil + } + } +} + +// RemoveInitContainers remove initContainers from the pod. +func (kflags KeepFlags) RemoveInitContainers(p *corev1.Pod) { + if !kflags.InitContainers { + p.Spec.InitContainers = nil } } @@ -130,10 +167,32 @@ func getDebugStyle(pod *corev1.Pod, target runtime.Object) (debugStyle, error) { return unsupported, fmt.Errorf("objects of type %T are not supported", target) } +func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { + style, err := getDebugStyle(pod, target) + if err != nil { + return fmt.Errorf("legacy profile: %w", err) + } + + switch style { + case node: + mountRootPartition(pod, containerName) + useHostNamespaces(pod) + + case podCopy: + p.Labels = false + p.RemoveLabels(pod) + + case ephemeral: + // no additional modifications needed + } + + return nil +} + func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { style, err := getDebugStyle(pod, target) if err != nil { - return fmt.Errorf("general profile: %s", err) + return fmt.Errorf("general profile: %w", err) } switch style { @@ -143,7 +202,10 @@ func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target run useHostNamespaces(pod) case podCopy: - removeLabelsAndProbes(pod) + p.RemoveLabels(pod) + p.RemoveAnnotations(pod) + p.RemoveProbes(pod) + p.RemoveInitContainers(pod) allowProcessTracing(pod, containerName) shareProcessNamespace(pod) @@ -157,14 +219,17 @@ func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target run func (p *baselineProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { style, err := getDebugStyle(pod, target) if err != nil { - return fmt.Errorf("baseline profile: %s", err) + return fmt.Errorf("baseline profile: %w", err) } clearSecurityContext(pod, containerName) switch style { case podCopy: - removeLabelsAndProbes(pod) + p.RemoveLabels(pod) + p.RemoveAnnotations(pod) + p.RemoveProbes(pod) + p.RemoveInitContainers(pod) shareProcessNamespace(pod) case ephemeral, node: @@ -177,7 +242,7 @@ func (p *baselineProfile) Apply(pod *corev1.Pod, containerName string, target ru func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { style, err := getDebugStyle(pod, target) if err != nil { - return fmt.Errorf("restricted profile: %s", err) + return fmt.Errorf("restricted profile: %w", err) } clearSecurityContext(pod, containerName) @@ -188,6 +253,10 @@ func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target switch style { case podCopy: + p.RemoveLabels(pod) + p.RemoveAnnotations(pod) + p.RemoveProbes(pod) + p.RemoveInitContainers(pod) shareProcessNamespace(pod) case ephemeral, node: @@ -200,7 +269,7 @@ func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { style, err := getDebugStyle(pod, target) if err != nil { - return fmt.Errorf("netadmin profile: %s", err) + return fmt.Errorf("netadmin profile: %w", err) } allowNetadminCapability(pod, containerName) @@ -210,6 +279,10 @@ func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target ru useHostNamespaces(pod) case podCopy: + p.RemoveLabels(pod) + p.RemoveAnnotations(pod) + p.RemoveProbes(pod) + p.RemoveInitContainers(pod) shareProcessNamespace(pod) case ephemeral: @@ -222,7 +295,7 @@ func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target ru func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error { style, err := getDebugStyle(pod, target) if err != nil { - return fmt.Errorf("sysadmin profile: %s", err) + return fmt.Errorf("sysadmin profile: %w", err) } setPrivileged(pod, containerName) @@ -234,7 +307,12 @@ func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target ru case podCopy: // to mimic general, default and baseline + p.RemoveLabels(pod) + p.RemoveAnnotations(pod) + p.RemoveProbes(pod) + p.RemoveInitContainers(pod) shareProcessNamespace(pod) + case ephemeral: // no additional modifications needed } @@ -242,17 +320,6 @@ func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target ru return nil } -// removeLabelsAndProbes removes labels from the pod and remove probes -// from all containers of the pod. -func removeLabelsAndProbes(p *corev1.Pod) { - p.Labels = nil - for i := range p.Spec.Containers { - p.Spec.Containers[i].LivenessProbe = nil - p.Spec.Containers[i].ReadinessProbe = nil - p.Spec.Containers[i].StartupProbe = nil - } -} - // mountRootPartition mounts the host's root path at "/host" in the container. func mountRootPartition(p *corev1.Pod, containerName string) { const volumeName = "host-root" diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go index 8d76ab642a1..c10d0842e10 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go @@ -34,6 +34,203 @@ var testNode = &corev1.Node{ }, } +func TestLegacyProfile(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + Name: "dbg", Image: "dbgimage", + }, + }, + }}, + } + + tests := map[string]struct { + pod *corev1.Pod + containerName string + target runtime.Object + expectPod *corev1.Pod + expectErr bool + }{ + "bad inputs results in error": { + pod: nil, + containerName: "dbg", + target: runtime.Object(nil), + expectErr: true, + }, + "debug by ephemeral container": { + pod: pod, + containerName: "dbg", + target: pod, + expectPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{Name: "dbg", Image: "dbgimage"}, + }, + }}, + }, + }, + "debug by pod copy": { + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, + Containers: []corev1.Container{ + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + containerName: "dbg", + target: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, + Containers: []corev1.Container{ + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, + }, + }, + }, + expectPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Annotations: map[string]string{ + "test": "test", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, + Containers: []corev1.Container{ + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + }, + "debug by node": { + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + containerName: "dbg", + target: testNode, + expectPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{ + HostNetwork: true, + HostPID: true, + HostIPC: true, + Containers: []corev1.Container{ + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/host", + Name: "host-root", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "host-root", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Path: "/"}, + }, + }, + }, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + applier := &legacyProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) + if (err != nil) != test.expectErr { + t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil)) + } + if err != nil { + return + } + if diff := cmp.Diff(test.expectPod, test.pod); diff != "" { + t.Error("unexpected diff in generated object: (-want +got):\n", diff) + } + }) + } +} + func TestGeneralProfile(t *testing.T) { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, @@ -81,10 +278,25 @@ func TestGeneralProfile(t *testing.T) { }, "debug by pod copy": { pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, { Name: "dbg", Image: "dbgimage", @@ -99,16 +311,32 @@ func TestGeneralProfile(t *testing.T) { }, containerName: "dbg", target: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, expectPod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ {Name: "app", Image: "appimage"}, { @@ -169,7 +397,8 @@ func TestGeneralProfile(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - err := (&generalProfile{}).Apply(test.pod, test.containerName, test.target) + applier := &generalProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) if (err != nil) != test.expectErr { t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil)) } @@ -230,20 +459,50 @@ func TestBaselineProfile(t *testing.T) { }, "debug by pod copy": { pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, {Name: "dbg", Image: "dbgimage"}, }, }, }, containerName: "dbg", target: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, @@ -251,6 +510,7 @@ func TestBaselineProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ ShareProcessNamespace: pointer.Bool(true), + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ {Name: "app", Image: "appimage"}, { @@ -288,7 +548,8 @@ func TestBaselineProfile(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - err := (&baselineProfile{}).Apply(test.pod, test.containerName, test.target) + applier := &baselineProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) if (err != nil) != test.expectErr { t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil)) } @@ -357,20 +618,50 @@ func TestRestrictedProfile(t *testing.T) { }, "debug by pod copy": { pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, {Name: "dbg", Image: "dbgimage"}, }, }, }, containerName: "dbg", target: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, @@ -378,6 +669,7 @@ func TestRestrictedProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ ShareProcessNamespace: pointer.Bool(true), + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ {Name: "app", Image: "appimage"}, { @@ -441,7 +733,8 @@ func TestRestrictedProfile(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - err := (&restrictedProfile{}).Apply(test.pod, test.containerName, test.target) + applier := &restrictedProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) if (err != nil) != test.expectErr { t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil)) } @@ -506,20 +799,50 @@ func TestNetAdminProfile(t *testing.T) { { name: "debug by pod copy", pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, {Name: "dbg", Image: "dbgimage"}, }, }, }, containerName: "dbg", target: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, @@ -527,6 +850,7 @@ func TestNetAdminProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ ShareProcessNamespace: pointer.Bool(true), + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ {Name: "app", Image: "appimage"}, { @@ -548,7 +872,13 @@ func TestNetAdminProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, { Name: "dbg", Image: "dbgimage", @@ -566,7 +896,13 @@ func TestNetAdminProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, @@ -665,7 +1001,8 @@ func TestNetAdminProfile(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := (&netadminProfile{}).Apply(test.pod, test.containerName, test.target) + applier := &netadminProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) if (err == nil) != (test.expectErr == nil) || (err != nil && test.expectErr != nil && err.Error() != test.expectErr.Error()) { t.Fatalf("expect error: %v, got error: %v", test.expectErr, err) } @@ -728,26 +1065,57 @@ func TestSysAdminProfile(t *testing.T) { { name: "debug by pod copy", pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, {Name: "dbg", Image: "dbgimage"}, }, }, }, containerName: "dbg", target: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "podcopy", + Labels: map[string]string{ + "app": "podcopy", + }, + Annotations: map[string]string{ + "test": "test", + }, + }, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, expectPod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{Name: "init-container"}}, Containers: []corev1.Container{ {Name: "app", Image: "appimage"}, { @@ -768,7 +1136,13 @@ func TestSysAdminProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, { Name: "dbg", Image: "dbgimage", @@ -786,7 +1160,13 @@ func TestSysAdminProfile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, Spec: corev1.PodSpec{ Containers: []corev1.Container{ - {Name: "app", Image: "appimage"}, + { + Name: "app", + Image: "appimage", + LivenessProbe: &corev1.Probe{}, + ReadinessProbe: &corev1.Probe{}, + StartupProbe: &corev1.Probe{}, + }, }, }, }, @@ -899,7 +1279,8 @@ func TestSysAdminProfile(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := (&sysadminProfile{}).Apply(test.pod, test.containerName, test.target) + applier := &sysadminProfile{KeepFlags{InitContainers: true}} + err := applier.Apply(test.pod, test.containerName, test.target) if (err == nil) != (test.expectErr == nil) || (err != nil && test.expectErr != nil && err.Error() != test.expectErr.Error()) { t.Fatalf("expect error: %v, got error: %v", test.expectErr, err) } diff --git a/test/cmd/debug.sh b/test/cmd/debug.sh index 73cde0bfd6b..2163eff90a8 100755 --- a/test/cmd/debug.sh +++ b/test/cmd/debug.sh @@ -26,7 +26,6 @@ run_kubectl_debug_pod_tests() { kube::log::status "Testing kubectl debug (pod tests)" ### Pod Troubleshooting by ephemeral containers - # Pre-Condition: Pod "nginx" is created kubectl run target "--image=${IMAGE_NGINX:?}" "${kube_flags[@]:?}" kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' @@ -38,7 +37,6 @@ run_kubectl_debug_pod_tests() { kubectl delete pod target "${kube_flags[@]:?}" ### Pod Troubleshooting by Copy - # Pre-Condition: Pod "nginx" is created kubectl run target "--image=${IMAGE_NGINX:?}" "${kube_flags[@]:?}" kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' @@ -51,6 +49,26 @@ run_kubectl_debug_pod_tests() { # Clean up kubectl delete pod target target-copy "${kube_flags[@]:?}" + # Pre-Condition: Pod "nginx" with labels, annotations, probes and initContainers is created + kubectl create -f hack/testdata/pod-with-metadata-and-probes.yaml + kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' + # Command: create a copy of target with a new debug container with --keep-* flags + # --keep-* flags intentionally don't work with legacyProfile(Only labels are removed) + kubectl debug target -it --copy-to=target-copy --image=busybox --container=debug-container --keep-labels=true --keep-annotations=true --keep-liveness=true --keep-readiness=true --keep-startup=true --keep-init-containers=false --attach=false "${kube_flags[@]:?}" + # Post-Conditions + kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:target-copy:' + kube::test::get_object_assert pod/target-copy '{{.metadata.labels}}' '' + kube::test::get_object_assert pod/target-copy '{{.metadata.annotations}}' 'map\[test:test\]' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.name}}:{{end}}' 'target:debug-container:' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.image}}:{{end}}' "${IMAGE_NGINX:?}:busybox:" + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "livenessProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "readinessProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "startupProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{range.spec.initContainers}}{{.name}}:{{end}}' 'init:' + kube::test::get_object_assert pod/target-copy '{{range.spec.initContainers}}{{.image}}:{{end}}' "busybox:" + # Clean up + kubectl delete pod target target-copy "${kube_flags[@]:?}" + # Pre-Condition: Pod "nginx" is created kubectl run target "--image=${IMAGE_NGINX:?}" "${kube_flags[@]:?}" kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' @@ -133,20 +151,44 @@ run_kubectl_debug_general_tests() { kube::log::status "Testing kubectl debug profile general" ### Debug by pod copy - ### probes are removed, sets SYS_PTRACE in debugging container, sets shareProcessNamespace - - # Pre-Condition: Pod "nginx" is created - kubectl run target "--image=${IMAGE_NGINX:?}" "${kube_flags[@]:?}" + # Pre-Condition: Pod "nginx" with labels, annotations, probes and initContainers is created + kubectl create -f hack/testdata/pod-with-metadata-and-probes.yaml kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' # Command: create a copy of target with a new debug container + # labels, annotations, probes are removed and initContainers are kept, sets SYS_PTRACE in debugging container, sets shareProcessNamespace kubectl debug --profile general target -it --copy-to=target-copy --image=busybox --container=debug-container --attach=false "${kube_flags[@]:?}" # Post-Conditions kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:target-copy:' + kube::test::get_object_assert pod/target-copy '{{.metadata.labels}}' '' + kube::test::get_object_assert pod/target-copy '{{.metadata.annotations}}' '' kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.name}}:{{end}}' 'target:debug-container:' kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.image}}:{{end}}' "${IMAGE_NGINX:?}:busybox:" kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "livenessProbe")}}:{{end}}{{end}}' '' kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "readinessProbe")}}:{{end}}{{end}}' '' kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "startupProbe")}}:{{end}}{{end}}' '' + kube::test::get_object_assert pod/target-copy '{{range.spec.initContainers}}{{.name}}:{{end}}' 'init:' + kube::test::get_object_assert pod/target-copy '{{range.spec.initContainers}}{{.image}}:{{end}}' "busybox:" + kube::test::get_object_assert pod/target-copy '{{(index (index .spec.containers 1).securityContext.capabilities.add 0)}}' 'SYS_PTRACE' + kube::test::get_object_assert pod/target-copy '{{.spec.shareProcessNamespace}}' 'true' + # Clean up + kubectl delete pod target target-copy "${kube_flags[@]:?}" + + # Pre-Condition: Pod "nginx" with labels, annotations, probes and initContainers is created + kubectl create -f hack/testdata/pod-with-metadata-and-probes.yaml + kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:' + # Command: create a copy of target with a new debug container with --keep-* flags + # labels, annotations, probes are kept and initContainers are removed, sets SYS_PTRACE in debugging container, sets shareProcessNamespace + kubectl debug --profile general target -it --copy-to=target-copy --image=busybox --container=debug-container --keep-labels=true --keep-annotations=true --keep-liveness=true --keep-readiness=true --keep-startup=true --keep-init-containers=false --attach=false "${kube_flags[@]:?}" + # Post-Conditions + kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target:target-copy:' + kube::test::get_object_assert pod/target-copy '{{.metadata.labels}}' 'map\[run:target\]' + kube::test::get_object_assert pod/target-copy '{{.metadata.annotations}}' 'map\[test:test\]' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.name}}:{{end}}' 'target:debug-container:' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.image}}:{{end}}' "${IMAGE_NGINX:?}:busybox:" + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "livenessProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "readinessProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{if (index . "startupProbe")}}:{{end}}{{end}}' ':' + kube::test::get_object_assert pod/target-copy '{{.spec.initContainers}}' '' kube::test::get_object_assert pod/target-copy '{{(index (index .spec.containers 1).securityContext.capabilities.add 0)}}' 'SYS_PTRACE' kube::test::get_object_assert pod/target-copy '{{.spec.shareProcessNamespace}}' 'true' # Clean up