Skip to content

Commit

Permalink
fix cdebug exec for Kubernetes short-lived shells losing the output i…
Browse files Browse the repository at this point in the history
…ssue
  • Loading branch information
iximiuz committed Feb 25, 2024
1 parent 08d094d commit db3f04f
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 14 deletions.
38 changes: 25 additions & 13 deletions cmd/exec/exec_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -34,6 +35,8 @@ import (
"github.com/iximiuz/cdebug/pkg/uuid"
)

// TODO: Handle exit codes - terminate the `cdebug exec` command with the same exit code as the debugger container.

func runDebuggerKubernetes(ctx context.Context, cli cliutil.CLI, opts *options) error {
if opts.autoRemove {
return fmt.Errorf("--rm flag is not supported for Kubernetes")
Expand Down Expand Up @@ -309,6 +312,8 @@ func attachPodDebugger(
if status == nil {
return fmt.Errorf("error getting debugger container %q status: %+v", debuggerName, err)
}
logrus.Debugf("Debugger container %q status: %+v", debuggerName, status)

if status.State.Terminated != nil {
dumpDebuggerLogs(ctx, client, ns, podName, debuggerName, cli.OutputStream())

Expand Down Expand Up @@ -355,18 +360,26 @@ func attachPodDebugger(
TTY: opts.tty,
}, scheme.ParameterCodec)

ctx, cancel := context.WithCancel(ctx)
defer cancel()
streamingCtx, cancelStreamingCtx := context.WithCancel(ctx)
defer cancelStreamingCtx()

go func() {
// Container is not running anymore, stop streaming.
_, _ = waitForContainer(ctx, client, ns, podName, debuggerName, false)
cli.PrintAux("Debugger container %q is not running...\n", debuggerName)
cancel()

dumpDebuggerLogs(ctx, client, ns, podName, debuggerName, cli.OutputStream())
// Debugger container is not running anymore - streaming no longer needed.
cancelStreamingCtx()
}()

return stream(ctx, cli, req.URL(), config, opts.tty)
if err := stream(streamingCtx, cli, req.URL(), config, opts.tty); err != nil {
return fmt.Errorf("error streaming to/from debugger container: %v", err)
}

cli.PrintAux("Debugger container %q terminated...\n", debuggerName)

if err := dumpDebuggerLogs(ctx, client, ns, podName, debuggerName, cli.OutputStream()); err != nil {
return fmt.Errorf("error dumping debugger logs: %v", err)
}

return nil
}

func stream(
Expand Down Expand Up @@ -439,13 +452,12 @@ func dumpDebuggerLogs(
if _, err := out.Write(bytes); err != nil {
return err
}

if err != nil {
if err != io.EOF {
return err
}
if err == io.EOF {
return nil
}
if err != nil {
return err
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion e2e/exec/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestExecDockerShell(t *testing.T) {
defer cleanup()

res := icmd.RunCmd(
icmd.Command("cdebug", "exec", "--rm", "-i", targetID),
icmd.Command("cdebug", "exec", "--rm", "-q", "-i", targetID),
icmd.WithStdin(strings.NewReader("echo \"hello $((6*7)) world\"\nexit 0\n")),
)

Expand Down
74 changes: 74 additions & 0 deletions e2e/exec/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package exec

import (
"strings"
"testing"
"text/template"

"gotest.tools/assert"
"gotest.tools/assert/cmp"
"gotest.tools/v3/icmd"

"github.com/iximiuz/cdebug/e2e/internal/fixture"
"github.com/iximiuz/cdebug/pkg/uuid"
)

var (
simplePod = template.Must(template.New("simple-pod").Parse(`---
apiVersion: v1
kind: Pod
metadata:
name: {{.PodName}}
namespace: default
spec:
restartPolicy: Never
containers:
- image: {{.Image}}
imagePullPolicy: IfNotPresent
name: app
`))
)

func TestExecKubernetesSimple(t *testing.T) {
podName := "cdebug-" + strings.ToLower(t.Name()) + "-" + uuid.ShortID()
cleanup := fixture.KubectlApply(t, simplePod, map[string]string{
"PodName": podName,
"Image": fixture.ImageNginx,
})
defer cleanup()

fixture.KubectlWaitFor(t, "pod", podName, "Ready")

// Exec in the pod
res := icmd.RunCmd(
icmd.Command("cdebug", "exec", "-q", "pod/"+podName, "busybox"),
)
res.Assert(t, icmd.Success)
assert.Check(t, cmp.Contains(res.Stdout(), "BusyBox v1"))

// Exec in the pod's container
res = icmd.RunCmd(
icmd.Command("cdebug", "exec", "-q", "pod/"+podName+"/app", "cat", "/etc/os-release"),
)
res.Assert(t, icmd.Success)
assert.Check(t, cmp.Contains(res.Stdout(), "debian"))
}

func TestExecKubernetesShell(t *testing.T) {
podName := "cdebug-" + strings.ToLower(t.Name()) + "-" + uuid.ShortID()
cleanup := fixture.KubectlApply(t, simplePod, map[string]string{
"PodName": podName,
"Image": fixture.ImageNginx,
})
defer cleanup()

fixture.KubectlWaitFor(t, "pod", podName, "Ready")

res := icmd.RunCmd(
icmd.Command("cdebug", "exec", "-q", "-i", "pod/"+podName+"/app"),
icmd.WithStdin(strings.NewReader("echo \"hello $((6*7)) world\"\nexit 0\n")),
)
res.Assert(t, icmd.Success)
assert.Equal(t, res.Stderr(), "")
assert.Check(t, cmp.Contains(res.Stdout(), "hello 42 world"))
}
35 changes: 35 additions & 0 deletions e2e/internal/fixture/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os/exec"
"strings"
"testing"
"text/template"

"gotest.tools/icmd"

Expand Down Expand Up @@ -137,3 +138,37 @@ func NerdctlRunBackground(

return contID, cleanup
}

func KubectlApply(
t *testing.T,
manifestTmpl *template.Template,
data interface{},
) func() {
var buf strings.Builder
if err := manifestTmpl.Execute(&buf, data); err != nil {
t.Fatalf("cannot execute template: %v", err)
}

manifest := buf.String()

cmd := icmd.Command("kubectl", "apply", "-f", "-")
res := icmd.RunCmd(cmd, icmd.WithStdin(strings.NewReader(manifest)))
res.Assert(t, icmd.Success)

return func() {
cmd := icmd.Command("kubectl", "delete", "-f", "-")
res := icmd.RunCmd(cmd, icmd.WithStdin(strings.NewReader(manifest)))
res.Assert(t, icmd.Success)
}
}

func KubectlWaitFor(
t *testing.T,
kind string,
name string,
condition string,
) {
cmd := icmd.Command("kubectl", "wait", kind, name, "--for=condition="+condition, "--timeout=60s")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Success)
}

0 comments on commit db3f04f

Please sign in to comment.