Skip to content

Commit

Permalink
Add custom debug profiles on top of static profiles
Browse files Browse the repository at this point in the history
This PR adds `custom` flag to let user customizes debug resources.
`custom` flag accepts partial container spec in json format.
  • Loading branch information
ardaguclu committed Mar 5, 2024
1 parent 50f4b1e commit af2dadc
Show file tree
Hide file tree
Showing 3 changed files with 838 additions and 21 deletions.
173 changes: 152 additions & 21 deletions staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"time"

"github.com/distribution/reference"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit af2dadc

Please sign in to comment.