Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support built-in profiles #25

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading