Skip to content

Commit

Permalink
feat: support built-in profiles (#25)
Browse files Browse the repository at this point in the history
Signed-off-by: Mario Constanti <github@constanti.de>
  • Loading branch information
bavarianbidi authored Dec 9, 2024
1 parent a65d88d commit 77265a2
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 126 deletions.
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ profiles:
profile: <PATH_TO_PROFILE>
```
The `profile` field is a path to a json file which contains the `pod.Spec` of the debug container.
The `profile` field could be either the path to a json file which contains the `pod.Spec` of the debug container or one of the built-in profiles from `kubectl` itself

```json
{
Expand All @@ -94,14 +94,26 @@ With the above configuration, the following command can be executed:
kubectl dpm run --profile=<PROFILE_NAME> --config=<DPM_CONFIG_FILE> --image=alpine/k8s:1.29.0 --namespace=<NAMESPACE> <POD_NAME>
```

### minimal configuration with built-in profile

As a minimal configuration with a built-in profile, the following fields are needed:

```yaml
profiles:
- name: <PROFILE_NAME>
profile: <legacy|general|baseline|restricted|netadmin|sysadmin>
```

The usage is the same as with the minimal configuration.

### full configuration

The full configuration file looks like this:

```yaml
profiles:
- name: <PROFILE_NAME>
profile: <PATH_TO_PROFILE>
profile: <PATH_TO_PROFILE|BUILT_IN_DEBUG_PROFILE>
image: <DEBUG_CONTAINER_IMAGE>
namespace: <NAMESPACE>
matchLabels:
Expand All @@ -111,7 +123,7 @@ profiles:
With the above configuration, the following command can be executed:

```bash
kubectl dpm run --profile=<PROFILE_NAME> --config=<DPM_CONFIG_FILE>
kubectl dpm run -p <PROFILE_NAME>
```

`dpm` will use the defined `namespace` and `image` to generate the ephemeral debug container.
Expand All @@ -130,9 +142,9 @@ As standalone binary, the `kubectlPath` value must be defined.

The `dpm` has the following flags:

* `--profile` - the name of the profile to use
* `--config` - the path to the configuration file
* `--image` - the image of the debug container
* `-p|--profile` - the name of the profile to use
* `-c|--config` - the path to the configuration file
* `-i|--image` - the image of the debug container

As we also register the generic `kubectl` flags, the following _relevant_ flags (IMHO) are also available:

Expand Down
3 changes: 2 additions & 1 deletion cmd/kubectl-dpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ func main() {
// create root command
root := command.Root()

root.PersistentFlags().StringVar(
root.PersistentFlags().StringVarP(
&config.ConfigurationFile,
"config",
"c",
os.Getenv("HOME")+"/.kube-dpm/debug-profiles.yaml",
"config path",
)
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
Expand Down Expand Up @@ -66,6 +68,7 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
Expand All @@ -63,6 +65,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
Expand Down Expand Up @@ -225,6 +229,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
Expand Down Expand Up @@ -288,6 +294,8 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
Expand Down
51 changes: 36 additions & 15 deletions pkg/command/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,13 @@ func run(args []string) error {
return err
}

// validate and complete profile
if err := profile.ValidateAndCompleteProfile(flagProfileName); err != nil {
// complete profile
if err := profile.CompleteProfile(flagProfileName); err != nil {
return err
}

// validate profile
if err := profile.ValidateProfile(flagProfileName); err != nil {
return err
}

Expand Down Expand Up @@ -112,19 +117,35 @@ func run(args []string) error {
if flagDebug {
fmt.Printf("Using profile: %+v\n", debugProfile)
fmt.Printf("kubectl path: %s\n", os.ExpandEnv(profile.Config.KubectlPath))
fmt.Printf("profile path: %s\n", debugProfile.CustomProfileFile)
fmt.Printf("profile path resolved: %s\n", os.ExpandEnv(debugProfile.CustomProfileFile))
}

// nolint:gosec
debugCommand := exec.Command(
os.ExpandEnv(profile.Config.KubectlPath),
"debug",
"--namespace", namespace,
"--custom", os.ExpandEnv(debugProfile.CustomProfileFile),
"--image", debugProfile.Image, targetContainer,
"-it",
)
fmt.Printf("profile path: %s\n", debugProfile.Profile)
fmt.Printf("profile path resolved: %s\n", os.ExpandEnv(debugProfile.Profile))
}

var debugCommand *exec.Cmd

switch {
case debugProfile.IsBuiltInProfile():
// nolint:gosec
debugCommand = exec.Command(
os.ExpandEnv(profile.Config.KubectlPath),
"debug",
"--namespace", namespace,
"--profile", debugProfile.Profile,
"--image", debugProfile.Image, targetContainer,
"-it",
)
default:
// nolint:gosec
debugCommand = exec.Command(
os.ExpandEnv(profile.Config.KubectlPath),
"debug",
"--namespace", namespace,
"--custom", os.ExpandEnv(debugProfile.Profile),
"--image", debugProfile.Image, targetContainer,
"-it",
)
}

debugCommand.Env = os.Environ()
debugCommand.Env = append(debugCommand.Env, string(cmdutil.DebugCustomProfile)+"=true")

Expand Down
10 changes: 5 additions & 5 deletions pkg/command/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ func generateListOutput() error {
matchLabels += fmt.Sprintf("%s=%s, ", label, value)
}
tbl.AddRow(
p.ProfileName, // Name
p.CustomProfileFile, // Profile
p.Image, // Image
p.Namespace, // Namespace
matchLabels, // MatchLabels
p.ProfileName, // Name
p.Profile, // Profile
p.Image, // Image
p.Namespace, // Namespace
matchLabels, // MatchLabels
)
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/profile/test_data/profile3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"env": [
{
"name": "APP_ENV",
"value": "test"
}
]
}
25 changes: 18 additions & 7 deletions pkg/profile/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import (
)

type Profile struct {
ProfileName string `koanf:"name" yaml:"name" validate:"required"`
CustomProfileFile string `koanf:"profile" yaml:"profile" validate:"required"`
Image string `koanf:"image" yaml:"image" validate:"required"`
Namespace string `koanf:"namespace" yaml:"namespace" validate:"required"`
ImagePullPolicy corev1.PullPolicy `koanf:"imagePullPolicy" yaml:"imagePullPolicy" validate:"required"`
TargetContainer string `koanf:"targetContainer" yaml:"targetContainer" validate:"required"`
MatchLabels map[string]string `koanf:"matchLabels" yaml:"matchLabels" validate:"required"`
ProfileName string `koanf:"name" yaml:"name" validate:"required"`
Profile string `koanf:"profile" yaml:"profile" validate:"required"`
Image string `koanf:"image" yaml:"image" validate:"required"`
Namespace string `koanf:"namespace" yaml:"namespace" validate:"required"`
ImagePullPolicy corev1.PullPolicy `koanf:"imagePullPolicy" yaml:"imagePullPolicy" validate:"required"`
TargetContainer string `koanf:"targetContainer" yaml:"targetContainer" validate:"required"`
MatchLabels map[string]string `koanf:"matchLabels" yaml:"matchLabels" validate:"required"`

// only used internally
builtInProfile bool
}

type CustomDebugProfile struct {
Expand All @@ -23,3 +26,11 @@ type CustomDebugProfile struct {

// global Profile configuration
var Config CustomDebugProfile

func (p *Profile) IsBuiltInProfile() bool {
return p.builtInProfile
}

func (p *Profile) SetBuiltInProfile(b bool) {
p.builtInProfile = b
}
45 changes: 28 additions & 17 deletions pkg/profile/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

corev1 "k8s.io/api/core/v1"
kubectldebug "k8s.io/kubectl/pkg/cmd/debug"
)

func ValidateDebugProfileFile() error {
Expand Down Expand Up @@ -57,7 +58,7 @@ func ValidateAllProfiles() error {

compactProfiles := slices.CompactFunc(Config.Profiles, func(a, b Profile) bool {
if strings.EqualFold(a.ProfileName, b.ProfileName) {
log.Printf("duplicate profile name %s found - keep the one with profile file %s\n", a.ProfileName, a.CustomProfileFile)
log.Printf("duplicate profile name %s found - keep the one with profile file %s\n", a.ProfileName, a.Profile)
return true
}
return false
Expand All @@ -69,9 +70,9 @@ func ValidateAllProfiles() error {
for _, p := range Config.Profiles {
switch {
case p.ProfileName == "":
return fmt.Errorf("profile file %s is missing a profile name", p.CustomProfileFile)
case p.CustomProfileFile == "":
return fmt.Errorf("profile name %s is missing a profile file", p.ProfileName)
return fmt.Errorf("profile %s is missing a custom profile name", p.Profile)
case p.Profile == "":
return fmt.Errorf("profile name %s is either missing a profile file or the name of a built-in profile", p.ProfileName)
}

if err := ValidateProfile(p.ProfileName); err != nil {
Expand All @@ -87,8 +88,29 @@ func ValidateProfile(profileName string) error {
func(c Profile) bool { return c.ProfileName == profileName },
)

if err := validatePodSpec(Config.Profiles[idx].CustomProfileFile); err != nil {
return err
switch Config.Profiles[idx].Profile {
case kubectldebug.ProfileLegacy:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
case kubectldebug.ProfileGeneral:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
case kubectldebug.ProfileBaseline:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
case kubectldebug.ProfileRestricted:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
case kubectldebug.ProfileNetadmin:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
case kubectldebug.ProfileSysadmin:
Config.Profiles[idx].SetBuiltInProfile(true)
return nil
default:
if err := validatePodSpec(Config.Profiles[idx].Profile); err != nil {
return err
}
}

return nil
Expand All @@ -109,17 +131,6 @@ func validatePodSpec(podSpec string) error {
return nil
}

func ValidateAndCompleteProfile(profileName string) error {
if err := CompleteProfile(profileName); err != nil {
return err
}

if err := ValidateProfile(profileName); err != nil {
return err
}
return nil
}

// CompleteProfile completes a profile with default values
func CompleteProfile(profileName string) error {
// get the index of the profile where the profile name matches
Expand Down
Loading

0 comments on commit 77265a2

Please sign in to comment.