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 metadata to start debugger in wait mode for g… #9105

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
16 changes: 8 additions & 8 deletions pkg/skaffold/debug/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ type containerTransformer interface {
// and required initContainer (an empty string if not required), or return a non-nil error if
// the container could not be transformed. The initContainer image is intended to install any
// required debug support tools.
Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error)
Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error)
}

// TransformContainer rewrites the container definition to enable debugging.
// Returns a debugging configuration description with associated language runtime support
// container image, or an error if the rewrite was unsuccessful.
func TransformContainer(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator) (types.ContainerDebugConfiguration, string, error) {
configuration, requiredImage, err := transformContainer(adapter, config, portAlloc)
func TransformContainer(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
configuration, requiredImage, err := transformContainer(adapter, config, portAlloc, dmd)
if err == nil {
configuration.Artifact = config.Artifact
if configuration.WorkingDir == "" {
Expand All @@ -70,7 +70,7 @@ func TransformContainer(adapter types.ContainerAdapter, config ImageConfiguratio
return configuration, requiredImage, err
}

func transformContainer(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator) (types.ContainerDebugConfiguration, string, error) {
func transformContainer(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
// Update the image configuration's environment with those set in the k8s manifest.
// (Environment variables in the k8s container's `env` add to the image configuration's `env` settings rather than replace.)
container := adapter.GetContainer()
Expand All @@ -91,7 +91,7 @@ func transformContainer(adapter types.ContainerAdapter, config ImageConfiguratio

// Apply command-line unwrapping for buildpack images and images using `sh -c`-style command-lines
next := func(adapter types.ContainerAdapter, config ImageConfiguration) (types.ContainerDebugConfiguration, string, error) {
return performContainerTransform(adapter, config, portAlloc)
return performContainerTransform(adapter, config, portAlloc, dmd)
}
if isCNBImage(config) {
return updateForCNBImage(adapter, config, next)
Expand Down Expand Up @@ -151,20 +151,20 @@ func isShDashC(cmd, arg string) bool {
return (cmd == "/bin/sh" || cmd == "/bin/bash") && arg == "-c"
}

func performContainerTransform(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator) (types.ContainerDebugConfiguration, string, error) {
func performContainerTransform(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
log.Entry(context.TODO()).Tracef("Examining container %q with config %v", adapter.GetContainer().Name, config)
// This approach prioritizes the user-defined runtime specified in the configuration. If the user explicitly defines the runtime, we assume they have a
// specific intention and want to use that specific transform. If no explicit runtime is specified, the code tries to infer the appropriate transform
// based on other indicators in the configuration.
for transform := range containerTransforms {
if transform.MatchRuntime(config) {
return transform.Apply(adapter, config, portAlloc, Protocols)
return transform.Apply(adapter, config, portAlloc, Protocols, dmd)
}
}

for transform := range containerTransforms {
if transform.IsApplicable(config) {
return transform.Apply(adapter, config, portAlloc, Protocols)
return transform.Apply(adapter, config, portAlloc, Protocols, dmd)
}
}
return types.ContainerDebugConfiguration{}, "", fmt.Errorf("unable to determine runtime for %q", adapter.GetContainer().Name)
Expand Down
22 changes: 22 additions & 0 deletions pkg/skaffold/debug/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ type PortAllocator func(int32) int32
// configurationRetriever retrieves an container image configuration
type ConfigurationRetriever func(string) (ImageConfiguration, error)

// DebuggerMetaData captures info for debug tranformers to construct the cli command appropriately
type DebuggerMetaData struct {
// ShouldWait hints that the process should start and wait for the debugger to be attached.
// For kubernetes this derives from an annotation at Pod-Level skaffold.dev/debug-suspend and
// for docker from an image label skaffold.dev/debug-suspend.
ShouldWait bool
}

// ImageConfiguration captures information from a docker/oci image configuration.
// It also includes a "artifact", usually containing the corresponding artifact's' image name from `skaffold.yaml`.
type ImageConfiguration struct {
Expand All @@ -92,6 +100,20 @@ var entrypointLaunchers []string

var Protocols = []string{}

// ExtractDebuggerMetaData extracts and returns the appropriate DebuggerMetaData based on the passed map[string]string
// which contains the context-appropriate metadata, e.g. for Kubernetes deployment the annotations and for Docker the image
// Labels.
func ExtractDebuggerMetaData(md map[string]string) *DebuggerMetaData {
dmd := &DebuggerMetaData{}

if md == nil {
return dmd
}

_, dmd.ShouldWait = md["skaffold.dev/debug-suspend"]
return dmd
}

// isEntrypointLauncher checks if the given entrypoint is a known entrypoint launcher,
// meaning an entrypoint that treats the image's CMD as a command-line.
func isEntrypointLauncher(entrypoint []string) bool {
Expand Down
23 changes: 19 additions & 4 deletions pkg/skaffold/debug/transform_go.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ type dlvSpec struct {
port uint16
headless bool
log bool
wait bool
apiVersion int
}

func newDlvSpec(port uint16) dlvSpec {
return dlvSpec{mode: "exec", port: port, apiVersion: defaultAPIVersion, headless: true}
return dlvSpec{mode: "exec", port: port, apiVersion: defaultAPIVersion, headless: true, wait: true}
}

// isLaunchingDlv determines if the arguments seems to be invoking Delve
Expand Down Expand Up @@ -110,7 +111,7 @@ func (t dlvTransformer) IsApplicable(config ImageConfiguration) bool {

// Apply configures a container definition for Go with Delve.
// Returns the debug configuration details, with the "go" support image
func (t dlvTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t dlvTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
container := adapter.GetContainer()
log.Entry(context.TODO()).Infof("Configuring %q for Go/Delve debugging", container.Name)

Expand All @@ -120,6 +121,13 @@ func (t dlvTransformer) Apply(adapter types.ContainerAdapter, config ImageConfig
if spec == nil {
newSpec := newDlvSpec(uint16(portAlloc(defaultDlvPort)))
spec = &newSpec

if dmd != nil {
spec.wait = dmd.ShouldWait
} else {
spec.wait = false
}

switch {
case len(config.Entrypoint) > 0 && !isEntrypointLauncher(config.Entrypoint):
container.Command = rewriteDlvCommandLine(config.Entrypoint, *spec, container.Args)
Expand Down Expand Up @@ -155,7 +163,7 @@ func extractDlvSpec(args []string) *dlvSpec {
return nil
}
// delve's defaults
spec := dlvSpec{apiVersion: 2, log: false, headless: false}
spec := dlvSpec{apiVersion: 2, log: false, headless: false, wait: true}
arguments:
for _, arg := range args {
switch {
Expand All @@ -167,6 +175,8 @@ arguments:
spec.headless = true
case arg == "--log":
spec.log = true
case arg == "--continue":
spec.wait = false
case strings.HasPrefix(arg, "--listen="):
address := strings.SplitN(arg, "=", 2)[1]
split := strings.SplitN(address, ":", 2)
Expand Down Expand Up @@ -207,7 +217,12 @@ func (spec dlvSpec) asArguments() []string {
if spec.headless {
args = append(args, "--headless")
}
args = append(args, "--continue", "--accept-multiclient")

if !spec.wait {
args = append(args, "--continue")
}

args = append(args, "--accept-multiclient")
if spec.port > 0 {
args = append(args, fmt.Sprintf("--listen=%s:%d", spec.host, spec.port))
} else {
Expand Down
19 changes: 10 additions & 9 deletions pkg/skaffold/debug/transform_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

func TestNewDlvSpecDefaults(t *testing.T) {
spec := newDlvSpec(20)
expected := dlvSpec{mode: "exec", port: 20, apiVersion: 2, headless: true, log: false}
expected := dlvSpec{mode: "exec", port: 20, apiVersion: 2, headless: true, log: false, wait: true}
testutil.CheckDeepEqual(t, expected, spec, cmp.AllowUnexported(spec))
}

Expand All @@ -40,14 +40,15 @@ func TestExtractDlvArg(t *testing.T) {
{nil, nil},
{[]string{"foo"}, nil},
{[]string{"foo", "--foo"}, nil},
{[]string{"dlv", "debug", "--headless"}, &dlvSpec{mode: "debug", headless: true, apiVersion: 2, log: false}},
{[]string{"dlv", "--headless", "exec"}, &dlvSpec{mode: "exec", headless: true, apiVersion: 2, log: false}},
{[]string{"dlv", "--headless", "exec", "--", "--listen=host:4345"}, &dlvSpec{mode: "exec", headless: true, apiVersion: 2, log: false}},
{[]string{"dlv", "test", "--headless", "--listen=host:4345"}, &dlvSpec{mode: "test", host: "host", port: 4345, headless: true, apiVersion: 2, log: false}},
{[]string{"dlv", "debug", "--headless", "--api-version=1"}, &dlvSpec{mode: "debug", headless: true, apiVersion: 1, log: false}},
{[]string{"dlv", "debug", "--listen=host:4345", "--headless", "--api-version=2", "--log"}, &dlvSpec{mode: "debug", host: "host", port: 4345, headless: true, apiVersion: 2, log: true}},
{[]string{"dlv", "debug", "--listen=:4345"}, &dlvSpec{mode: "debug", port: 4345, apiVersion: 2}},
{[]string{"dlv", "debug", "--listen=host:"}, &dlvSpec{mode: "debug", host: "host", apiVersion: 2}},
{[]string{"dlv", "debug", "--headless"}, &dlvSpec{mode: "debug", headless: true, apiVersion: 2, log: false, wait: true}},
{[]string{"dlv", "--headless", "exec"}, &dlvSpec{mode: "exec", headless: true, apiVersion: 2, log: false, wait: true}},
{[]string{"dlv", "--headless", "exec", "--", "--listen=host:4345"}, &dlvSpec{mode: "exec", headless: true, apiVersion: 2, log: false, wait: true}},
{[]string{"dlv", "test", "--headless", "--listen=host:4345"}, &dlvSpec{mode: "test", host: "host", port: 4345, headless: true, apiVersion: 2, log: false, wait: true}},
{[]string{"dlv", "debug", "--headless", "--api-version=1"}, &dlvSpec{mode: "debug", headless: true, apiVersion: 1, log: false, wait: true}},
{[]string{"dlv", "debug", "--listen=host:4345", "--headless", "--api-version=2", "--log"}, &dlvSpec{mode: "debug", host: "host", port: 4345, headless: true, apiVersion: 2, log: true, wait: true}},
{[]string{"dlv", "debug", "--listen=:4345"}, &dlvSpec{mode: "debug", port: 4345, apiVersion: 2, wait: true}},
{[]string{"dlv", "debug", "--listen=host:"}, &dlvSpec{mode: "debug", host: "host", apiVersion: 2, wait: true}},
{[]string{"dlv", "debug", "--listen=host:", "--continue"}, &dlvSpec{mode: "debug", host: "host", apiVersion: 2, wait: false}},
}
for _, test := range tests {
testutil.Run(t, strings.Join(test.in, " "), func(t *testutil.T) {
Expand Down
12 changes: 10 additions & 2 deletions pkg/skaffold/debug/transform_jvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type jdwpSpec struct {

// Apply configures a container definition for JVM debugging.
// Returns a simple map describing the debug configuration details.
func (t jdwpTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t jdwpTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
container := adapter.GetContainer()
log.Entry(context.TODO()).Infof("Configuring %q for JVM debugging", container.Name)
// try to find existing JAVA_TOOL_OPTIONS or jdwp command argument
Expand All @@ -88,7 +88,15 @@ func (t jdwpTransformer) Apply(adapter types.ContainerAdapter, config ImageConfi
port = int32(spec.port)
} else {
port = portAlloc(defaultJdwpPort)
jto := fmt.Sprintf("-agentlib:jdwp=transport=dt_socket,server=y,address=%d,suspend=n,quiet=y", port)

var suspendParam string
if dmd != nil && dmd.ShouldWait {
suspendParam = "y"
} else {
suspendParam = "n"
}

jto := fmt.Sprintf("-agentlib:jdwp=transport=dt_socket,server=y,address=%d,suspend=%s,quiet=y", port, suspendParam)
if existing, found := config.Env["JAVA_TOOL_OPTIONS"]; found {
jto = existing + " " + jto
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/skaffold/debug/transform_netcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (t netcoreTransformer) IsApplicable(config ImageConfiguration) bool {

// Apply configures a container definition for vsdbg.
// Returns a simple map describing the debug configuration details.
func (t netcoreTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t netcoreTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
container := adapter.GetContainer()
log.Entry(context.TODO()).Infof("Configuring %q for netcore debugging", container.Name)

Expand Down
8 changes: 7 additions & 1 deletion pkg/skaffold/debug/transform_nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,20 @@ func (t nodeTransformer) IsApplicable(config ImageConfiguration) bool {

// Apply configures a container definition for NodeJS Chrome V8 Inspector.
// Returns a simple map describing the debug configuration details.
func (t nodeTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t nodeTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
container := adapter.GetContainer()
log.Entry(context.TODO()).Infof("Configuring %q for node.js debugging", container.Name)

// try to find existing `--inspect` command
spec := retrieveNodeInspectSpec(config)
if spec == nil {
spec = &inspectSpec{host: "0.0.0.0", port: portAlloc(defaultDevtoolsPort)}
if dmd != nil {
spec.brk = dmd.ShouldWait
} else {
spec.brk = false
}

switch {
case isLaunchingNode(config.Entrypoint):
container.Command = rewriteNodeCommandLine(config.Entrypoint, *spec)
Expand Down
17 changes: 11 additions & 6 deletions pkg/skaffold/debug/transform_python.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (t pythonTransformer) IsApplicable(config ImageConfiguration) bool {

// Apply configures a container definition for Python with ptvsd/debugpy/pydevd.
// Returns a simple map describing the debug configuration details.
func (t pythonTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t pythonTransformer) Apply(adapter types.ContainerAdapter, config ImageConfiguration, portAlloc PortAllocator, overrideProtocols []string, dmd *DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
container := adapter.GetContainer()
log.Entry(context.TODO()).Infof("Configuring %q for python debugging", container.Name)

Expand All @@ -132,7 +132,12 @@ func (t pythonTransformer) Apply(adapter types.ContainerAdapter, config ImageCon
}, "", nil
}

spec := createPythonDebugSpec(overrideProtocols, portAlloc)
shouldWait := false
if dmd != nil {
shouldWait = dmd.ShouldWait
}

spec := createPythonDebugSpec(overrideProtocols, portAlloc, shouldWait)

switch {
case isLaunchingPython(config.Entrypoint):
Expand Down Expand Up @@ -177,17 +182,17 @@ func extractPythonDebugSpec(args []string) *pythonSpec {
return nil
}

func createPythonDebugSpec(overrideProtocols []string, portAlloc PortAllocator) *pythonSpec {
func createPythonDebugSpec(overrideProtocols []string, portAlloc PortAllocator, shouldWait bool) *pythonSpec {
for _, p := range overrideProtocols {
switch p {
case pydevdProtocol:
return &pythonSpec{debugger: pydevd, port: portAlloc(defaultPydevdPort)}
return &pythonSpec{debugger: pydevd, port: portAlloc(defaultPydevdPort), wait: shouldWait}
case dapProtocol:
return &pythonSpec{debugger: debugpy, port: portAlloc(defaultDebugpyPort)}
return &pythonSpec{debugger: debugpy, port: portAlloc(defaultDebugpyPort), wait: shouldWait}
}
}

return &pythonSpec{debugger: debugpy, port: portAlloc(defaultDebugpyPort)}
return &pythonSpec{debugger: debugpy, port: portAlloc(defaultDebugpyPort), wait: shouldWait}
}

func extractPtvsdSpec(args []string) *pythonSpec {
Expand Down
4 changes: 3 additions & 1 deletion pkg/skaffold/docker/debugger/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ func transformImage(ctx context.Context, artifact graph.Artifact, cfg *container
// the set of image IDs required to provide debugging support files
requiredSupportImages := make(map[string]bool)

if configuration, requiredImage, err := debug.TransformContainer(adapter, imageConfig, portAlloc); err == nil {
dmd := debug.ExtractDebuggerMetaData(imageConfig.Labels)

if configuration, requiredImage, err := debug.TransformContainer(adapter, imageConfig, portAlloc, dmd); err == nil {
configurations[adapter.GetContainer().Name] = configuration
if len(requiredImage) > 0 {
log.Entry(ctx).Infof("%q requires debugging support image %q", cfg.Image, requiredImage)
Expand Down
2 changes: 1 addition & 1 deletion pkg/skaffold/kubernetes/debugging/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (t testTransformer) MatchRuntime(config debug.ImageConfiguration) bool {
return true
}

func (t testTransformer) Apply(adapter types.ContainerAdapter, config debug.ImageConfiguration, portAlloc debug.PortAllocator, overrideProtocols []string) (types.ContainerDebugConfiguration, string, error) {
func (t testTransformer) Apply(adapter types.ContainerAdapter, config debug.ImageConfiguration, portAlloc debug.PortAllocator, overrideProtocols []string, dmd *debug.DebuggerMetaData) (types.ContainerDebugConfiguration, string, error) {
port := portAlloc(9999)
container := adapter.GetContainer()
container.Ports = append(container.Ports, types.ContainerPort{Name: "test", ContainerPort: port})
Expand Down
5 changes: 4 additions & 1 deletion pkg/skaffold/kubernetes/debugging/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,10 @@ func rewriteContainers(metadata *metav1.ObjectMeta, podSpec *v1.PodSpec, retriev
a := adapter.NewAdapter(&container)
// requiredImage, if not empty, is the image ID providing the debugging support files
// `err != nil` means that the container did not or could not be transformed
if configuration, requiredImage, err := debug.TransformContainer(a, imageConfig, portAlloc); err == nil {

dmd := debug.ExtractDebuggerMetaData(metadata.Annotations)

if configuration, requiredImage, err := debug.TransformContainer(a, imageConfig, portAlloc, dmd); err == nil {
configurations[container.Name] = configuration
podSpec.Containers[i] = container // apply any configuration changes
if len(requiredImage) > 0 {
Expand Down
Loading