Skip to content

Commit

Permalink
(feat) add rpaas debug (#136)
Browse files Browse the repository at this point in the history
* api/web/client: add initial instance debug support

* api: proper attach and wait for status for debug

* api: add tests for debug // return debug container logs when unable to attach to it

* integration: add integration tests for debug plugin cmd

* integration: bump rpaas-api chart

* k8s: minor refactor on debug and add tests

* k8s: fix debugPodWithContainerStatus signature and add debug test with no pod defined

* Update internal/pkg/rpaas/k8s.go

Co-authored-by: Claudio Netto <[email protected]>

* k8s: minor refactor on patchEphemeralContainers

Co-authored-by: Claudio Netto <[email protected]>

* api: add missing license header to transport

* plugin: fix typo for debug/exec cmds

---------

Co-authored-by: Claudio Netto <[email protected]>
  • Loading branch information
morpheu and nettoclaudio authored Aug 8, 2023
1 parent 3ac2590 commit cdbed04
Show file tree
Hide file tree
Showing 25 changed files with 1,468 additions and 215 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ build: build/api build/manager build/plugin/rpaasv2 build/purger

.PHONY: build/api
build/api: build-dirs
go build -o $(GO_BUILD_DIR)/ ./cmd/api
CGO_ENABLED=0 go build -o $(GO_BUILD_DIR)/ ./cmd/api

.PHONY: build/manager
build/manager: manager
Expand Down
1 change: 1 addition & 0 deletions cmd/plugin/rpaasv2/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewApp(o, e io.Writer, client rpaasclient.Client) (app *cli.App) {
NewCmdRoutes(),
NewCmdInfo(),
NewCmdAutoscale(),
NewCmdDebug(),
NewCmdExec(),
NewCmdShell(),
NewCmdLogs(),
Expand Down
138 changes: 138 additions & 0 deletions cmd/plugin/rpaasv2/cmd/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2023 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
"fmt"
"os"

"github.com/gorilla/websocket"
"github.com/urfave/cli/v2"
"k8s.io/kubectl/pkg/util/term"

rpaasclient "github.com/tsuru/rpaas-operator/pkg/rpaas/client"
)

func NewCmdDebug() *cli.Command {
return &cli.Command{
Name: "debug",
Usage: "Run debug in an pod from instance",
ArgsUsage: "[-p POD] [-c CONTAINER] [--debug-image image] [--] COMMAND [args...]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "service",
Aliases: []string{"tsuru-service", "s"},
Usage: "the Tsuru service name",
},
&cli.StringFlag{
Name: "instance",
Aliases: []string{"tsuru-service-instance", "i"},
Usage: "the reverse proxy instance name",
Required: true,
},
&cli.StringFlag{
Name: "pod",
Aliases: []string{"p"},
Usage: "pod name - if omitted, the first pod will be chosen",
},
&cli.StringFlag{
Name: "container",
Aliases: []string{"c"},
Usage: "container name - if omitted, the \"nginx\" container will be chosen",
},
&cli.StringFlag{
Name: "debug-image",
Aliases: []string{"d"},
Usage: "debug image name - if omitted, service default defined debug image will be chosen",
},
&cli.BoolFlag{
Name: "interactive",
Aliases: []string{"I", "stdin"},
Usage: "pass STDIN to container",
},
&cli.BoolFlag{
Name: "tty",
Aliases: []string{"t"},
Usage: "allocate a pseudo-TTY",
},
},
Before: setupClient,
Action: runDebug,
}
}

func runDebug(c *cli.Context) error {
client, err := getClient(c)
if err != nil {
return err
}

var width, height uint16
if ts := term.GetSize(os.Stdin.Fd()); ts != nil {
width, height = ts.Width, ts.Height
}

args := rpaasclient.DebugArgs{
Command: c.Args().Slice(),
Instance: c.String("instance"),
Pod: c.String("pod"),
Container: c.String("container"),
Interactive: c.Bool("interactive"),
Image: c.String("debug-image"),
TTY: c.Bool("tty"),
TerminalWidth: width,
TerminalHeight: height,
}

if args.Interactive {
args.In = os.Stdin
}

tty := &term.TTY{
In: args.In,
Out: c.App.Writer,
Raw: args.TTY,
}
return tty.Safe(func() error {
conn, err := client.Debug(c.Context, args)
if err != nil {
return err
}
defer conn.Close()

done := make(chan error, 1)
go func() {
defer close(done)
for {
mtype, message, nerr := conn.ReadMessage()
if nerr != nil {
closeErr, ok := nerr.(*websocket.CloseError)
if !ok {
done <- fmt.Errorf("ERROR: received an unexpected error while reading messages: %w", err)
return
}

switch closeErr.Code {
case websocket.CloseNormalClosure:
case websocket.CloseInternalServerErr:
done <- fmt.Errorf("ERROR: the command may not be executed as expected - reason: %s", closeErr.Text)
default:
done <- fmt.Errorf("ERROR: unexpected close error: %s", closeErr.Error())
}

return
}

switch mtype {
case websocket.TextMessage, websocket.BinaryMessage:
c.App.Writer.Write(message)
}
}
}()
err = <-done
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
return err
})
}
91 changes: 91 additions & 0 deletions cmd/plugin/rpaasv2/cmd/debug_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2023 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
"bytes"
"context"
"fmt"
"os"
"testing"

"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/tsuru/rpaas-operator/pkg/rpaas/client"
"github.com/tsuru/rpaas-operator/pkg/rpaas/client/fake"
)

func TestDebug(t *testing.T) {
var called bool

tests := []struct {
name string
args []string
expected string
expectedError string
expectedCalled bool
client client.Client
}{
{
name: "with command and arguments",
args: []string{"rpaasv2", "debug", "-s", "rpaasv2", "-i", "my-instance", "--", "my-command", "-arg1", "--arg2"},
client: &fake.FakeClient{
FakeDebug: func(ctx context.Context, args client.DebugArgs) (*websocket.Conn, error) {
called = true
expected := client.DebugArgs{
Command: []string{"my-command", "-arg1", "--arg2"},
Instance: "my-instance",
}
assert.Equal(t, expected, args)
return nil, fmt.Errorf("some error")
},
},
expectedCalled: true,
expectedError: "some error",
},
{
name: "with all options activated",
args: []string{"rpaasv2", "debug", "-s", "rpaasv2", "-i", "my-instance", "--tty", "--interactive", "-p", "pod-1", "-c", "container-1", "--", "my-shell"},
client: &fake.FakeClient{
FakeDebug: func(ctx context.Context, args client.DebugArgs) (*websocket.Conn, error) {
called = true
expected := client.DebugArgs{
In: os.Stdin,
Command: []string{"my-shell"},
Instance: "my-instance",
Pod: "pod-1",
Container: "container-1",
TTY: true,
Interactive: true,
}
assert.Equal(t, expected, args)
return nil, fmt.Errorf("another error")
},
},
expectedCalled: true,
expectedError: "another error",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
called = false
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
app := NewApp(stdout, stderr, tt.client)
err := app.Run(tt.args)
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expectedCalled, called)
assert.Equal(t, tt.expected, stdout.String())
assert.Empty(t, stderr.String())
})
}
}
2 changes: 1 addition & 1 deletion cmd/plugin/rpaasv2/cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func runExec(c *cli.Context) error {
if nerr != nil {
closeErr, ok := nerr.(*websocket.CloseError)
if !ok {
done <- fmt.Errorf("ERROR: receveid an unexpected error while reading messages: %w", err)
done <- fmt.Errorf("ERROR: received an unexpected error while reading messages: %w", err)
return
}

Expand Down
23 changes: 21 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ require (
k8s.io/api v0.26.2
k8s.io/apimachinery v0.26.2
k8s.io/client-go v0.26.2
k8s.io/kubectl v0.24.2
k8s.io/kubectl v0.26.2
k8s.io/metrics v0.26.2
sigs.k8s.io/controller-runtime v0.14.5
sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10
Expand All @@ -49,18 +49,24 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
github.com/HdrHistogram/hdrhistogram-go v1.0.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/antihax/optional v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/docker/docker v20.10.20+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-ldap/ldap/v3 v3.4.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
Expand All @@ -70,18 +76,23 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-version v0.0.0-20180716215031-270f2f71b1ee // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/howeyc/fsnotify v0.9.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
Expand All @@ -95,9 +106,11 @@ require (
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
Expand All @@ -109,6 +122,7 @@ require (
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tsuru/config v0.0.0-20201023175036-375aaee8b560 // indirect
Expand All @@ -118,6 +132,8 @@ require (
github.com/uber/jaeger-lib v2.4.0+incompatible // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
Expand All @@ -134,6 +150,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.26.2 // indirect
k8s.io/cli-runtime v0.26.2 // indirect
k8s.io/component-base v0.26.2 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-aggregator v0.24.2 // indirect
Expand All @@ -142,6 +159,8 @@ require (
knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad // indirect
sigs.k8s.io/gateway-api v0.4.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
Expand Down
Loading

0 comments on commit cdbed04

Please sign in to comment.