Skip to content

Commit

Permalink
Merge pull request #4 from runz0rd/feature/config-interactive
Browse files Browse the repository at this point in the history
Feature/config interactive
  • Loading branch information
runz0rd authored Dec 7, 2022
2 parents eef35a8 + fd2911c commit 806ad97
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 132 deletions.
9 changes: 4 additions & 5 deletions cmd/actions/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package actions

import (
"github.com/foomo/gograpple"
"github.com/foomo/gograpple/suggest"
"github.com/foomo/gograpple/kubectl"
"github.com/spf13/cobra"
)

Expand All @@ -22,20 +22,19 @@ var (
if err := addr.Set(c.ListenAddr); err != nil {
return err
}
if err := suggest.KubeConfig(suggest.DefaultKubeConfig).SetContext(c.Cluster); err != nil {
if err := kubectl.SetContext(c.Cluster); err != nil {
return err
}
g, err := gograpple.NewGrapple(newLogger(flagVerbose, flagJSONLog), c.Namespace, c.Deployment)
if err != nil {
return err
}

if err := g.Patch(c.Repository, c.Image, c.Platform, c.Container, nil); err != nil {
if err := g.Patch(c.Repository, c.Image, c.Container, nil); err != nil {
return err
}
defer g.Rollback()
// todo support binargs from config
return g.Delve("", c.Container, c.SourcePath, c.Platform, nil, addr.Host, addr.Port, c.LaunchVscode, c.DelveContinue)
return g.Delve("", c.Container, c.SourcePath, nil, addr.Host, addr.Port, c.LaunchVscode, c.DelveContinue)
},
}
)
9 changes: 3 additions & 6 deletions cmd/actions/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,20 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&flagPod, "pod", "p", "", "pod name (default most recent one)")
rootCmd.PersistentFlags().StringVarP(&flagContainer, "container", "c", "", "container name (default deployment name)")
patchCmd.Flags().StringVar(&flagImage, "image", "alpine:latest", "image to be used for patching (default alpine:latest)")
patchCmd.Flags().StringVar(&flagPlatform, "platform", "linux/amd64", "platform to be used for patching (default linux/amd64)")
patchCmd.Flags().StringVarP(&flagRepo, "repo", "r", "", "repository to be used for pushing patched image (default none)")
patchCmd.Flags().StringArrayVarP(&flagMounts, "mount", "m", []string{}, "host path to be mounted (default none)")
patchCmd.Flags().BoolVar(&flagRollback, "rollback", false, "rollback deployment to a previous state")
delveCmd.Flags().StringVar(&flagSourcePath, "source", "", ".go file source path (default cwd)")
delveCmd.Flags().Var(flagArgs, "args", "go file args")
delveCmd.Flags().Var(flagListen, "listen", "delve host:port to listen on")
delveCmd.Flags().BoolVar(&flagVscode, "vscode", false, "launch a debug configuration in vscode")
delveCmd.Flags().BoolVar(&flagContinue, "continue", false, "start delve server execution without wiating for client connection")
delveCmd.Flags().BoolVar(&flagContinue, "continue", false, "start delve server execution without waiting for client connection")
delveCmd.Flags().BoolVar(&flagJSONLog, "json-log", false, "log as json")
delveCmd.Flags().StringVar(&flagPlatform, "image", "linux/amd64", "platform to be used for patching (default linux/amd64)")
rootCmd.AddCommand(versionCmd, patchCmd, shellCmd, delveCmd, configCmd)
}

var (
flagImage string
flagPlatform string
flagDir string
flagVerbose bool
flagNamespace string
Expand Down Expand Up @@ -82,7 +79,7 @@ var (
if err != nil {
return err
}
return grapple.Patch(flagRepo, flagImage, flagPlatform, flagContainer, mounts)
return grapple.Patch(flagRepo, flagImage, flagContainer, mounts)
},
}
shellCmd = &cobra.Command{
Expand All @@ -98,7 +95,7 @@ var (
Short: "start a headless delve debug server for .go input on a patched deployment",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return grapple.Delve(flagPod, flagContainer, flagSourcePath, flagPlatform, flagArgs.items, flagListen.Host, flagListen.Port, flagVscode, flagContinue)
return grapple.Delve(flagPod, flagContainer, flagSourcePath, flagArgs.items, flagListen.Host, flagListen.Port, flagVscode, flagContinue)
},
}
)
Expand Down
22 changes: 8 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/c-bata/go-prompt"
"github.com/c-bata/go-prompt/completer"
"github.com/foomo/gograpple/kubectl"
"github.com/foomo/gograpple/suggest"
"github.com/runz0rd/gencon"
"gopkg.in/yaml.v3"
Expand All @@ -25,7 +26,6 @@ type Config struct {
ListenAddr string `yaml:"listen_addr,omitempty"`
DelveContinue bool `yaml:"delve_continue,omitempty"`
Image string `yaml:"image,omitempty"`
Platform string `yaml:"platform,omitempty"`
}

func (c Config) MarshalYAML() (interface{}, error) {
Expand Down Expand Up @@ -55,34 +55,29 @@ func (c Config) SourcePathSuggest(d prompt.Document) []prompt.Suggest {
}

func (c Config) ClusterSuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
return suggest.Completer(d, suggest.MustList(kc.ListContexts))
return suggest.Completer(d, suggest.MustList(kubectl.ListContexts))
}

func (c Config) NamespaceSuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
kc.SetContext(c.Cluster)
return suggest.Completer(d, suggest.MustList(kc.ListNamespaces))
kubectl.SetContext(c.Cluster)
return suggest.Completer(d, suggest.MustList(kubectl.ListNamespaces))
}

func (c Config) DeploymentSuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
return suggest.Completer(d, suggest.MustList(func() ([]string, error) {
return kc.ListDeployments(c.Namespace)
return kubectl.ListDeployments(c.Namespace)
}))
}

func (c Config) ContainerSuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
return suggest.Completer(d, suggest.MustList(func() ([]string, error) {
return kc.ListContainers(c.Namespace, c.Deployment)
return kubectl.ListContainers(c.Namespace, c.Deployment)
}))
}

func (c Config) RepositorySuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
return suggest.Completer(d, suggest.MustList(func() ([]string, error) {
return kc.ListRepositories(c.Namespace, c.Deployment)
return kubectl.ListRepositories(c.Namespace, c.Deployment)
}))
}

Expand Down Expand Up @@ -113,9 +108,8 @@ func (c Config) PlatformSuggest(d prompt.Document) []prompt.Suggest {
}

func (c Config) ImageSuggest(d prompt.Document) []prompt.Suggest {
kc := suggest.KubeConfig(suggest.DefaultKubeConfig)
suggestions := suggest.Completer(d, suggest.MustList(func() ([]string, error) {
return kc.ListImages(c.Namespace, c.Deployment)
return kubectl.ListImages(c.Namespace, c.Deployment)
}))
return append(suggestions, prompt.Suggest{Text: defaultImage})
}
Expand Down
54 changes: 21 additions & 33 deletions delve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,21 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"

"github.com/foomo/gograpple/delve"
"github.com/foomo/gograpple/exec"
"github.com/sirupsen/logrus"
)

const delveBin = "dlv"

func (g Grapple) Delve(pod, container, sourcePath, platform string, binArgs []string, host string,
func (g Grapple) Delve(pod, container, sourcePath string, binArgs []string, host string,
port int, vscode, delveContinue bool) error {
ctx := context.Background()
if !g.isPatched() {
return fmt.Errorf("deployment not patched, stopping delve")
}
// get os and arch from platform
os_, arch, err := GetPlatformInfo(platform)
if err != nil {
return err
}

// populate bin args if empty
if len(binArgs) == 0 {
Expand Down Expand Up @@ -71,10 +65,23 @@ func (g Grapple) Delve(pod, container, sourcePath, platform string, binArgs []st
clog.Error(err)
return
}

// deploy bin
dlog := g.componentLog("deploy")
dlog.Info("building and deploying bin")
if err := g.deployBin(ctx, pod, container, os_, arch, goModPath, sourcePath); err != nil {
// get image used in the deployment so we can get platform
deploymentImage, err := g.kubeCmd.GetImage(ctx, g.deployment, container)
if err != nil {
dlog.Error(err)
return
}
// get platform from deployment image
deploymentPlatform, err := g.dockerCmd.GetPlatform(ctx, deploymentImage)
if err != nil {
dlog.Error(err)
return
}
if err := g.deployBin(ctx, pod, container, goModPath, sourcePath, deploymentPlatform); err != nil {
dlog.Error(err)
return
}
Expand Down Expand Up @@ -136,32 +143,13 @@ func (g Grapple) cleanupPIDs(ctx context.Context, pod, container string) error {
})
}

func (g Grapple) deployBin(ctx context.Context, pod, container, os_, arch, goModPath, sourcePath string) error {
func (g Grapple) deployBin(ctx context.Context, pod, container, goModPath, sourcePath string, p *exec.Platform) error {
// build bin
binSource := path.Join(os.TempDir(), g.binName())
var relInputs []string
inputInfo, errInputInfo := os.Stat(sourcePath)
if errInputInfo != nil {
return errInputInfo
}
if inputInfo.IsDir() {
if files, err := os.ReadDir(sourcePath); err != nil {
return err
} else {
for _, file := range files {
if path.Ext(file.Name()) == ".go" {
relInputs = append(relInputs, strings.TrimPrefix(path.Join(sourcePath,
file.Name()), goModPath+string(filepath.Separator)))
}
}
}
} else {
relInputs = append(relInputs, strings.TrimPrefix(sourcePath, goModPath+string(filepath.Separator)))
}

_, errBuild := g.goCmd.Build(goModPath, binSource, relInputs, "-gcflags", "-N -l").Env("GOOS="+os_, "GOARCH="+arch).Run(ctx)
if errBuild != nil {
return errBuild
_, err := g.goCmd.Build(binSource, []string{sourcePath}, "-gcflags", "-N -l").
Env(fmt.Sprintf("GOOS=%v", p.OS), fmt.Sprintf("GOARCH=%v", p.Arch), fmt.Sprintf("CGO_ENABLED=%v", 0)).Run(ctx)
if err != nil {
return err
}
// copy bin to pod
_, errCopyToPod := g.kubeCmd.CopyToPod(pod, container, binSource, g.binDestination()).Run(ctx)
Expand Down
2 changes: 1 addition & 1 deletion delve/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (kds KubeDelveServer) Port() int {

func NewKubeDelveServer(l *logrus.Entry, namespace, host string, port int) *KubeDelveServer {
kubectl := exec.NewKubectlCommand()
kubectl.Logger(l).Args("-n", namespace)
kubectl.Logger(l).Quiet().Args("-n", namespace)
return &KubeDelveServer{host, port, kubectl, nil}
}

Expand Down
2 changes: 1 addition & 1 deletion delve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestGrapple_Delve(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := g.Delve("", "", tt.args.sourcePath, "linux/amd64", nil, tt.args.host, tt.args.port, tt.args.vscode, false); (err != nil) != tt.wantErr {
if err := g.Delve("", "", tt.args.sourcePath, nil, tt.args.host, tt.args.port, tt.args.vscode, false); (err != nil) != tt.wantErr {
t.Errorf("Grapple.Delve() error = %v, wantErr %v", err, tt.wantErr)
}
})
Expand Down
23 changes: 18 additions & 5 deletions exec/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import (
"io"
"os"
goexec "os/exec"
"path/filepath"

"github.com/sirupsen/logrus"
)

type Cmd struct {
l *logrus.Entry
// actual *exec.Cmd
l *logrus.Entry
command []string
cwd string
env []string
stdin io.Reader
stdoutWriters []io.Writer
stderrWriters []io.Writer
wait bool
// t time.Duration
preStartFunc func() error
postStartFunc func(p *os.Process) error
postEndFunc func() error
chanStarted chan struct{}
chanDone chan struct{}
quiet bool
}

func NewCommand(name string) *Cmd {
Expand Down Expand Up @@ -53,6 +53,10 @@ func (c *Cmd) Args(args ...string) *Cmd {
}

func (c *Cmd) Cwd(path string) *Cmd {
fi, _ := os.Stat(path)
if !fi.IsDir() {
path = filepath.Dir(path)
}
c.cwd = path
return c
}
Expand Down Expand Up @@ -108,6 +112,11 @@ func (c *Cmd) Logger(l *logrus.Entry) *Cmd {
return c
}

func (c *Cmd) Quiet() *Cmd {
c.quiet = true
return c
}

func (c *Cmd) Run(ctx context.Context) (string, error) {
return c.run(goexec.CommandContext(ctx, c.command[0], c.command[1:]...))
}
Expand All @@ -130,9 +139,13 @@ func (c *Cmd) run(cmd *goexec.Cmd) (string, error) {
c.stdoutWriters = append(c.stdoutWriters, combinedBuf)
c.stderrWriters = append(c.stderrWriters, combinedBuf)
if c.l != nil {
c.l.Tracef("executing %q", cmd.String())
c.l.Tracef("executing %q in %q", cmd.String(), cmd.Dir)
c.stdoutWriters = append(c.stdoutWriters, c.l.WriterLevel(logrus.TraceLevel))
c.stderrWriters = append(c.stderrWriters, c.l.WriterLevel(logrus.TraceLevel))
if c.quiet {
c.stderrWriters = append(c.stderrWriters, c.l.WriterLevel(logrus.TraceLevel))
} else {
c.stderrWriters = append(c.stderrWriters, c.l.WriterLevel(logrus.WarnLevel))
}
}
cmd.Stdout = io.MultiWriter(c.stdoutWriters...)
cmd.Stderr = io.MultiWriter(c.stderrWriters...)
Expand Down
27 changes: 27 additions & 0 deletions exec/docker.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package exec

import (
"context"
"fmt"
"strings"
)

type DockerCmd struct {
Expand All @@ -23,3 +25,28 @@ func (c DockerCmd) Push(image, tag string, options ...string) *Cmd {
func (c DockerCmd) ImageInspect(options ...string) *Cmd {
return c.Args("image", "inspect").Args(options...)
}

func (c DockerCmd) GetPlatform(ctx context.Context, image string) (*Platform, error) {
out, err := c.ImageInspect("-f", "{{.Os}}/{{.Architecture}}", image).Run(ctx)
if err != nil {
return nil, err
}
return NewPlatform(strings.TrimRight(out, "\n"))
}

type Platform struct {
OS string
Arch string
}

func NewPlatform(v string) (*Platform, error) {
pieces := strings.Split(v, "/")
if len(pieces) != 2 {
return nil, fmt.Errorf("invalid platform format %q", v)
}
return &Platform{pieces[0], pieces[1]}, nil
}

func (p Platform) String() string {
return fmt.Sprintf("%v/%v", p.OS, p.Arch)
}
4 changes: 2 additions & 2 deletions exec/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ func NewGoCommand() *GoCmd {
return &GoCmd{*NewCommand("go")}
}

func (c GoCmd) Build(workDir, output string, inputs []string, flags ...string) *Cmd {
return c.Args("build", "-o", output).Cwd(workDir).Args(flags...).Args(inputs...)
func (c GoCmd) Build(output string, inputs []string, flags ...string) *Cmd {
return c.Args("build", "-o", output).Args(flags...).Args(inputs...)
}
10 changes: 10 additions & 0 deletions exec/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -266,6 +267,15 @@ func (c KubectlCmd) GetPIDsOf(ctx context.Context, pod, container, process strin
return stripped, nil
}

func (c KubectlCmd) GetImage(ctx context.Context, deployment v1.Deployment, container string) (string, error) {
for _, c := range deployment.Spec.Template.Spec.Containers {
if c.Name == container {
return c.Image, nil
}
}
return "", fmt.Errorf("couldnt find image for deployment %v in container %v", deployment.Name, container)
}

func (c KubectlCmd) ValidateNamespace(ctx context.Context, namespace string) error {
available, err := c.GetNamespaces(ctx)
if err != nil {
Expand Down
Loading

0 comments on commit 806ad97

Please sign in to comment.