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

Add rpaas debug feature with custom image #136

Merged
merged 11 commits into from
Aug 8, 2023
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",
},
Comment on lines +50 to +59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd force the interactive mode (tty + interactive) in this sub-command... I think there's no other user case to use it without an interactive session.

},
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: receveid an unexpected error while reading messages: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo:
"ERROR: received an unexpected..."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦅 eyes :)
thanks!

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())
})
}
}
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
Loading