diff --git a/.goreleaser.yml b/.goreleaser.yml index 4906d93..51ef008 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,11 +2,11 @@ # Build customization builds: - binary: gograpple - main: ./cmd/main.go + main: ./main.go env: - CGO_ENABLED=0 ldflags: - - -s -w -X github.com/foomo/gograpple/cmd/actions.version={{.Version}} + - -s -w -X github.com/foomo/gograpple/cmd.version={{.Version}} goos: - darwin - linux diff --git a/Makefile b/Makefile index c3b751a..c7ad405 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ build: - go build -o bin/gograpple cmd/gograpple/main.go + go build -o bin/gograpple main.go install: - go build -o /usr/local/bin/gograpple cmd/gograpple/main.go + go build -o /usr/local/bin/gograpple main.go test: go test ./... \ No newline at end of file diff --git a/README.md b/README.md index ff4e7fb..ac312e9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,16 @@ gograpple that go program and delve into the high seas ... or in other words: delve debugger injection for your golang code running in k8 pods +## requirements + - helm + - kubectl + - docker + ## quick start ``` -go install github.com/foomo/gograpple/cmd/gograpple@latest +brew install foomo/gograpple/gograpple +OR +go install github.com/foomo/gograpple@latest ``` start patch debugging in interactive mode ``` @@ -15,6 +22,12 @@ when you configure your patch correctly a file will be saved in your cwd and the ## common issues +### stuck with patched deployment +in case your deployment is styck in patched state, use +``` +gograpple rollback [namespace] [deployment] +``` + ### vscode > The debug session doesnt start until the entrypoint is triggered more than once. diff --git a/cmd/gograpple/actions/flags.go b/cmd/flags.go similarity index 89% rename from cmd/gograpple/actions/flags.go rename to cmd/flags.go index 38a4114..0d8f394 100644 --- a/cmd/gograpple/actions/flags.go +++ b/cmd/flags.go @@ -1,11 +1,11 @@ -package actions +package cmd import ( "fmt" "strconv" "strings" - "github.com/foomo/gograpple" + "github.com/foomo/gograpple/internal/grapple" ) type HostPort struct { @@ -14,7 +14,7 @@ type HostPort struct { } func NewHostPort(host string, port int) *HostPort { - addr, err := gograpple.CheckTCPConnection(host, port) + addr, err := grapple.CheckTCPConnection(host, port) if err == nil { host = addr.IP.String() port = addr.Port @@ -45,7 +45,7 @@ func (lf *HostPort) Set(value string) error { default: return fmt.Errorf("invalid address %q provided", value) } - addr, err := gograpple.CheckTCPConnection(lf.Host, lf.Port) + addr, err := grapple.CheckTCPConnection(lf.Host, lf.Port) if err != nil { return err } diff --git a/cmd/gograpple/actions/root.go b/cmd/gograpple/actions/root.go deleted file mode 100644 index 7577ad9..0000000 --- a/cmd/gograpple/actions/root.go +++ /dev/null @@ -1,124 +0,0 @@ -package actions - -import ( - "github.com/foomo/gograpple" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -func init() { - - rootCmd.PersistentFlags().StringVarP(&flagDir, "dir", "d", ".", "Specifies working directory") - rootCmd.PersistentFlags().StringVarP(&flagNamespace, "namespace", "n", "default", "namespace name") - rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "Specifies should command output be displayed") - rootCmd.PersistentFlags().StringVarP(&flagPod, "pod", "p", "", "pod name (default most recent one)") - rootCmd.PersistentFlags().StringVarP(&flagContainer, "container", "c", "", "container name (default deployment name)") - rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "", false, "debug mode") - patchCmd.Flags().StringVar(&flagImage, "image", "alpine:latest", "image to be used for patching (default alpine:latest)") - 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 waiting for client connection") - delveCmd.Flags().BoolVar(&flagJSONLog, "json-log", false, "log as json") - interactiveCmd.Flags().BoolVar(&flagAttach, "attach", false, "debug with attach (default will patch)") - interactiveCmd.Flags().StringVar(&flagSaveDir, "save", ".", "directory to save interactive configuration") - rootCmd.AddCommand(versionCmd, patchCmd, shellCmd, delveCmd, interactiveCmd) -} - -var ( - flagImage string - flagDir string - flagVerbose bool - flagNamespace string - flagPod string - flagContainer string - flagRepo string - flagMounts []string - flagSourcePath string - flagArgs = NewStringList(" ") - flagRollback bool - flagListen = NewHostPort("127.0.0.1", 0) - flagVscode bool - flagContinue bool - flagJSONLog bool - flagDebug bool -) - -var ( - l *logrus.Entry - grapple *gograpple.Grapple - - rootCmd = &cobra.Command{ - Use: "gograpple", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if cmd.Name() == commandNameVersion || cmd.Name() == commandNameInteractive { - return nil - } - l = newLogger(flagVerbose, flagJSONLog) - var err error - err = gograpple.ValidatePath(".", &flagDir) - if err != nil { - return err - } - grapple, err = gograpple.NewGrapple(l, flagNamespace, args[0], flagDebug) - if err != nil { - return err - } - return gograpple.ValidatePath(flagDir, &flagSourcePath) - }, - } - patchCmd = &cobra.Command{ - Use: "patch [DEPLOYMENT] -c {CONTAINER} -n {NAMESPACE} -i {IMAGE} -t {TAG} -m {MOUNT}", - Short: "applies a development patch for a deployment", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if flagRollback { - return grapple.Rollback() - } - mounts, err := gograpple.ValidateMounts(flagDir, flagMounts) - if err != nil { - return err - } - return grapple.Patch(flagImage, flagContainer, mounts) - }, - } - shellCmd = &cobra.Command{ - Use: "shell [DEPLOYMENT] -n {NAMESPACE} -c {CONTAINER}", - Short: "shell into the dev patched deployment", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return grapple.Shell(flagPod) - }, - } - delveCmd = &cobra.Command{ - Use: "delve [DEPLOYMENT] --source {SRC} -n {NAMESPACE} -c {CONTAINER}", - 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, flagArgs.items, flagListen.Host, flagListen.Port, flagVscode, flagContinue) - }, - } -) - -func Execute() { - if err := rootCmd.Execute(); err != nil { - l = logrus.NewEntry(logrus.New()) - l.Fatal(err) - } -} - -func newLogger(verbose, jsonLog bool) *logrus.Entry { - logger := logrus.New() - if jsonLog { - logger.SetFormatter(&logrus.JSONFormatter{ - DisableTimestamp: true, - }) - } - if verbose { - logger.SetLevel(logrus.TraceLevel) - } - return logrus.NewEntry(logger) -} diff --git a/cmd/gograpple/actions/version.go b/cmd/gograpple/actions/version.go deleted file mode 100644 index f17a270..0000000 --- a/cmd/gograpple/actions/version.go +++ /dev/null @@ -1,22 +0,0 @@ -package actions - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var version = "latest" - -const commandNameVersion = "version" - -var ( - versionCmd = &cobra.Command{ - Use: commandNameVersion, - Short: "prints cli version", - Long: "prints the current installed cli version", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(version) - }, - } -) diff --git a/cmd/gograpple/main.go b/cmd/gograpple/main.go deleted file mode 100644 index 6e93f25..0000000 --- a/cmd/gograpple/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/foomo/gograpple/cmd/gograpple/actions" - -func main() { - actions.Execute() -} diff --git a/cmd/gograpple/actions/interactive.go b/cmd/interactive.go similarity index 70% rename from cmd/gograpple/actions/interactive.go rename to cmd/interactive.go index b9e7914..b46686d 100644 --- a/cmd/gograpple/actions/interactive.go +++ b/cmd/interactive.go @@ -1,15 +1,19 @@ -package actions +package cmd import ( "path" - "github.com/foomo/gograpple" - "github.com/foomo/gograpple/config" - "github.com/foomo/gograpple/kubectl" + "github.com/foomo/gograpple/internal/config" + "github.com/foomo/gograpple/internal/grapple" + "github.com/foomo/gograpple/internal/kubectl" "github.com/spf13/cobra" ) -const commandNameInteractive = "interactive" +func init() { + interactiveCmd.Flags().BoolVar(&flagAttach, "attach", false, "debug with attach (default will patch)") + interactiveCmd.Flags().StringVar(&flagSaveDir, "save", ".", "directory to save interactive configuration") + rootCmd.AddCommand(interactiveCmd) +} var ( flagAttach bool @@ -37,7 +41,7 @@ func attachDebug(baseDir string) error { if err != nil { return err } - g, err := gograpple.NewGrapple(newLogger(flagVerbose, flagJSONLog), c.Namespace, c.Deployment, flagDebug) + g, err := grapple.NewGrapple(newLogEntry(flagDebug), c.Namespace, c.Deployment) if err != nil { return err } @@ -48,7 +52,7 @@ func attachDebug(baseDir string) error { if err := kubectl.SetContext(c.Cluster); err != nil { return err } - return g.Attach(c.Namespace, c.Deployment, c.Container, c.AttachTo, c.Arch, host, port) + return g.Attach(c.Namespace, c.Deployment, c.Container, c.AttachTo, c.Arch, host, port, flagDebug) } func patchDebug(baseDir string) error { @@ -64,7 +68,7 @@ func patchDebug(baseDir string) error { if &c == nil { return nil } - g, err := gograpple.NewGrapple(newLogger(flagVerbose, flagJSONLog), c.Namespace, c.Deployment, flagDebug) + g, err := grapple.NewGrapple(newLogEntry(flagDebug), c.Namespace, c.Deployment) if err != nil { return err } diff --git a/cmd/rollback.go b/cmd/rollback.go new file mode 100644 index 0000000..16139f1 --- /dev/null +++ b/cmd/rollback.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "github.com/foomo/gograpple/internal/grapple" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(rollbackCmd) +} + +var ( + rollbackCmd = &cobra.Command{ + Use: "rollback [namespace] [deployment]", + Short: "rollback the patched deployment", + Args: cobra.MinimumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + g, err := grapple.NewGrapple(newLogEntry(flagDebug), args[0], args[1]) + if err != nil { + return err + } + return g.Rollback() + }, + } +) diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..ce885e7 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "", false, "debug mode") +} + +var ( + // flagImage string + // flagDir string + flagDebug bool + // flagNamespace string + // flagPod string + // flagContainer string + // flagRepo string + // flagMounts []string + // flagSourcePath string + // flagArgs = NewStringList(" ") + // flagRollback bool + // flagListen = NewHostPort("127.0.0.1", 0) + // flagVscode bool + // flagContinue bool + // flagJSONLog bool + // flagDebug bool +) + +var ( + rootCmd = &cobra.Command{ + Use: "gograpple", + } +) + +func Execute() { + if err := rootCmd.Execute(); err != nil { + le := newLogEntry(flagDebug) + le.Fatal(err) + } +} + +func newLogEntry(debug bool) *logrus.Entry { + logger := logrus.New() + if debug { + logger.SetLevel(logrus.TraceLevel) + } + return logrus.NewEntry(logger) +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..5b3d09e --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +// set on build +var version = "" + +var ( + versionCmd = &cobra.Command{ + Use: "version", + Short: "prints cli version", + Long: "prints the current installed cli version", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(getVersion()) + }, + } +) + +func getVersion() string { + if version == "" { + return "latest" + } + return version +} diff --git a/config/attach.go b/internal/config/attach.go similarity index 97% rename from config/attach.go rename to internal/config/attach.go index 12aeeb0..235bcf6 100644 --- a/config/attach.go +++ b/internal/config/attach.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/c-bata/go-prompt" - "github.com/foomo/gograpple/kubectl" - "github.com/foomo/gograpple/suggest" + "github.com/foomo/gograpple/internal/kubectl" + "github.com/foomo/gograpple/internal/suggest" "gopkg.in/yaml.v3" ) diff --git a/config/config.go b/internal/config/config.go similarity index 100% rename from config/config.go rename to internal/config/config.go diff --git a/config/patch.go b/internal/config/patch.go similarity index 97% rename from config/patch.go rename to internal/config/patch.go index 1bb9d9c..163f366 100644 --- a/config/patch.go +++ b/internal/config/patch.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/c-bata/go-prompt" - "github.com/foomo/gograpple/kubectl" - "github.com/foomo/gograpple/suggest" + "github.com/foomo/gograpple/internal/kubectl" + "github.com/foomo/gograpple/internal/suggest" "gopkg.in/yaml.v3" ) diff --git a/delve/client.go b/internal/delve/client.go similarity index 100% rename from delve/client.go rename to internal/delve/client.go diff --git a/delve/server.go b/internal/delve/server.go similarity index 97% rename from delve/server.go rename to internal/delve/server.go index 0b0bb98..c98b0b1 100644 --- a/delve/server.go +++ b/internal/delve/server.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/foomo/gograpple/exec" + "github.com/foomo/gograpple/internal/exec" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) diff --git a/exec/cmd.go b/internal/exec/cmd.go similarity index 100% rename from exec/cmd.go rename to internal/exec/cmd.go diff --git a/exec/docker.go b/internal/exec/docker.go similarity index 100% rename from exec/docker.go rename to internal/exec/docker.go diff --git a/exec/go.go b/internal/exec/go.go similarity index 100% rename from exec/go.go rename to internal/exec/go.go diff --git a/exec/helm.go b/internal/exec/helm.go similarity index 100% rename from exec/helm.go rename to internal/exec/helm.go diff --git a/exec/kubectl.go b/internal/exec/kubectl.go similarity index 100% rename from exec/kubectl.go rename to internal/exec/kubectl.go diff --git a/attach.go b/internal/grapple/attach.go similarity index 94% rename from attach.go rename to internal/grapple/attach.go index 9e5295a..f92e096 100644 --- a/attach.go +++ b/internal/grapple/attach.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "fmt" @@ -7,12 +7,12 @@ import ( "runtime" "github.com/bitfield/script" - "github.com/foomo/gograpple/kubectl" - "github.com/foomo/gograpple/log" + "github.com/foomo/gograpple/internal/kubectl" + "github.com/foomo/gograpple/internal/log" "github.com/pkg/errors" ) -func (g Grapple) Attach(namespace, deployment, container, bin, arch, host string, port int) error { +func (g Grapple) Attach(namespace, deployment, container, bin, arch, host string, port int, debug bool) error { pod, err := kubectl.GetMostRecentRunningPodBySelectors(namespace, g.deployment.Spec.Selector.MatchLabels) if err != nil { return err @@ -35,7 +35,7 @@ func (g Grapple) Attach(namespace, deployment, container, bin, arch, host string if len(pids) != 1 { return fmt.Errorf("found none or more than one process named %q", bin) } - go attachDelveOnPod(namespace, pod, container, dlvDest, pids[0], host, port, g.debug) + go attachDelveOnPod(namespace, pod, container, dlvDest, pids[0], host, port, debug) // launchVSCode(context.Background(), g.l, "./test/app", "", port, 3) return kubectl.PortForwardPod(namespace, pod, port) } diff --git a/delve.go b/internal/grapple/delve.go similarity index 96% rename from delve.go rename to internal/grapple/delve.go index 5414b65..871ab65 100644 --- a/delve.go +++ b/internal/grapple/delve.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "context" @@ -7,8 +7,9 @@ import ( "path" "time" - "github.com/foomo/gograpple/delve" - "github.com/foomo/gograpple/exec" + "github.com/foomo/gograpple/internal/delve" + "github.com/foomo/gograpple/internal/exec" + "github.com/foomo/gograpple/util" "github.com/sirupsen/logrus" ) @@ -41,7 +42,7 @@ func (g Grapple) Delve(pod, container, sourcePath string, binArgs []string, host return fmt.Errorf("couldnt find go.mod path for source %q", sourcePath) } - RunWithInterrupt(g.l, func(ctx context.Context) { + util.RunWithInterrupt(g.l, func(ctx context.Context) { g.l.Infof("waiting for deployment to get ready") _, err := g.kubeCmd.WaitForRollout(g.deployment.Name, defaultWaitTimeout).Run(ctx) if err != nil { diff --git a/delve_test.go b/internal/grapple/delve_test.go similarity index 95% rename from delve_test.go rename to internal/grapple/delve_test.go index b34e677..cbcf5cf 100644 --- a/delve_test.go +++ b/internal/grapple/delve_test.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "net" @@ -10,7 +10,7 @@ import ( const testNamespace = "test" func testGrapple(t *testing.T, deployment string) *Grapple { - g, err := NewGrapple(logrus.NewEntry(logrus.StandardLogger()), testNamespace, deployment, false) + g, err := NewGrapple(logrus.NewEntry(logrus.StandardLogger()), testNamespace, deployment) if err != nil { t.Fatal(err) } diff --git a/gograpple.go b/internal/grapple/grapple.go similarity index 88% rename from gograpple.go rename to internal/grapple/grapple.go index 49656d5..e357ee3 100644 --- a/gograpple.go +++ b/internal/grapple/grapple.go @@ -1,9 +1,9 @@ -package gograpple +package grapple import ( "context" - "github.com/foomo/gograpple/exec" + "github.com/foomo/gograpple/internal/exec" "github.com/sirupsen/logrus" v1 "k8s.io/api/apps/v1" ) @@ -30,11 +30,10 @@ type Grapple struct { kubeCmd *exec.KubectlCmd dockerCmd *exec.DockerCmd goCmd *exec.GoCmd - debug bool } -func NewGrapple(l *logrus.Entry, namespace, deployment string, debug bool) (*Grapple, error) { - g := &Grapple{l: l, debug: debug} +func NewGrapple(l *logrus.Entry, namespace, deployment string) (*Grapple, error) { + g := &Grapple{l: l} g.kubeCmd = exec.NewKubectlCommand() g.dockerCmd = exec.NewDockerCommand() g.goCmd = exec.NewGoCommand() diff --git a/patch.go b/internal/grapple/patch.go similarity index 98% rename from patch.go rename to internal/grapple/patch.go index 71088bd..3d6d473 100644 --- a/patch.go +++ b/internal/grapple/patch.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "context" @@ -9,6 +9,7 @@ import ( "path" "path/filepath" + "github.com/foomo/gograpple/util" "github.com/pkg/errors" ) @@ -109,7 +110,7 @@ func (g Grapple) Patch(image, container string, mounts []Mount) error { return err } // get repo from deployment image - imageRepo, name, tag, err := ParseImage(deploymentImage) + imageRepo, name, tag, err := util.ParseImage(deploymentImage) if err != nil { return err } diff --git a/patch_test.go b/internal/grapple/patch_test.go similarity index 97% rename from patch_test.go rename to internal/grapple/patch_test.go index 422a6b4..f503d36 100644 --- a/patch_test.go +++ b/internal/grapple/patch_test.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "testing" diff --git a/shell.go b/internal/grapple/shell.go similarity index 97% rename from shell.go rename to internal/grapple/shell.go index 6ee47c6..a25abaf 100644 --- a/shell.go +++ b/internal/grapple/shell.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "context" diff --git a/the-hook/Dockerfile b/internal/grapple/the-hook/Dockerfile similarity index 100% rename from the-hook/Dockerfile rename to internal/grapple/the-hook/Dockerfile diff --git a/the-hook/deployment-patch.yaml b/internal/grapple/the-hook/deployment-patch.yaml similarity index 100% rename from the-hook/deployment-patch.yaml rename to internal/grapple/the-hook/deployment-patch.yaml diff --git a/utils.go b/internal/grapple/utils.go similarity index 63% rename from utils.go rename to internal/grapple/utils.go index 1c0b0a7..1d766d9 100644 --- a/utils.go +++ b/internal/grapple/utils.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "bytes" @@ -7,13 +7,8 @@ import ( "net" "os" "path/filepath" - "runtime" - "strings" "text/template" "time" - - "github.com/foomo/gograpple/exec" - "github.com/sirupsen/logrus" ) func FindFreePort(host string) (int, error) { @@ -37,21 +32,6 @@ func CheckTCPConnection(host string, port int) (*net.TCPAddr, error) { return l.Addr().(*net.TCPAddr), nil } -func Open(l *logrus.Entry, ctx context.Context, path string) (string, error) { - var cmd *exec.Cmd - switch runtime.GOOS { - case "linux": - cmd = exec.NewCommand("xdg-open").Logger(l).Args(path) - case "windows": - cmd = exec.NewCommand("rundll32").Logger(l).Args("url.dll,FileProtocolHandler", path) - case "darwin": - cmd = exec.NewCommand("open").Logger(l).Args(path) - default: - return "", fmt.Errorf("unsupported platform") - } - return cmd.Run(ctx) -} - func TryCall(tries int, waitBetweenAttempts time.Duration, f func(i int) error) error { var err error for i := 1; i < tries+1; i++ { @@ -129,24 +109,3 @@ func stringIsInSlice(a string, list []string) bool { } return false } - -func GetPlatformInfo(platform string) (os, arch string, err error) { - pieces := strings.Split(platform, "/") - if len(pieces) != 2 { - return os, arch, fmt.Errorf("invalid format for platform %q", platform) - } - return pieces[0], pieces[1], nil -} - -func ParseImage(s string) (repo, name, tag string, err error) { - pieces := strings.Split(s, "/") - switch true { - case len(pieces) == 1 && pieces[0] == s: - imageTag := strings.Split(s, ":") - return "", imageTag[0], imageTag[1], nil - case len(pieces) > 1: - imageTag := strings.Split(pieces[len(pieces)-1], ":") - return strings.Join(pieces[:len(pieces)-1], "/"), imageTag[0], imageTag[1], nil - } - return "", "", "", fmt.Errorf("invalid image value %q provided", s) -} diff --git a/validation.go b/internal/grapple/validation.go similarity index 98% rename from validation.go rename to internal/grapple/validation.go index a38294c..4a227bd 100644 --- a/validation.go +++ b/internal/grapple/validation.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "fmt" diff --git a/vscode.go b/internal/grapple/vscode.go similarity index 91% rename from vscode.go rename to internal/grapple/vscode.go index bc9e130..8aa8f78 100644 --- a/vscode.go +++ b/internal/grapple/vscode.go @@ -1,4 +1,4 @@ -package gograpple +package grapple import ( "context" @@ -10,7 +10,8 @@ import ( "strings" "time" - "github.com/foomo/gograpple/exec" + "github.com/foomo/gograpple/internal/exec" + "github.com/foomo/gograpple/util" "github.com/sirupsen/logrus" ) @@ -79,7 +80,7 @@ func launchVSCode(ctx context.Context, l *logrus.Entry, goModDir, host string, p if err != nil { return err } - _, err = Open(l, ctx, `vscode://fabiospampinato.vscode-debug-launcher/launch?args=`+url.QueryEscape(la)) + _, err = util.Open(l, ctx, `vscode://fabiospampinato.vscode-debug-launcher/launch?args=`+url.QueryEscape(la)) if err != nil { return err } diff --git a/kubectl/kubectl.go b/internal/kubectl/kubectl.go similarity index 98% rename from kubectl/kubectl.go rename to internal/kubectl/kubectl.go index c8b7599..a38adda 100644 --- a/kubectl/kubectl.go +++ b/internal/kubectl/kubectl.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/bitfield/script" - "github.com/foomo/gograpple/log" - "github.com/foomo/gograpple/suggest" + "github.com/foomo/gograpple/internal/log" + "github.com/foomo/gograpple/internal/suggest" "github.com/life4/genesis/slices" "github.com/pkg/errors" apps "k8s.io/api/apps/v1" diff --git a/log/log.go b/internal/log/log.go similarity index 100% rename from log/log.go rename to internal/log/log.go diff --git a/suggest/docker.go b/internal/suggest/docker.go similarity index 100% rename from suggest/docker.go rename to internal/suggest/docker.go diff --git a/suggest/util.go b/internal/suggest/util.go similarity index 100% rename from suggest/util.go rename to internal/suggest/util.go diff --git a/interrupt.go b/interrupt.go deleted file mode 100644 index 6e77e27..0000000 --- a/interrupt.go +++ /dev/null @@ -1,38 +0,0 @@ -package gograpple - -import ( - "context" - "os" - "os/signal" - "time" - - "github.com/sirupsen/logrus" -) - -func RunWithInterrupt(l *logrus.Entry, callback func(ctx context.Context)) { - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt) - durReload := 3 * time.Second - for { - ctx, cancelCtx := context.WithCancel(context.Background()) - // do stuff - go callback(ctx) - select { - case <-signalChan: // first signal - l.Info("-") - l.Infof("interrupt received, trigger one more within %v to terminate", durReload) - cancelCtx() - select { - case <-time.After(durReload): // reloads durReload after first signal - l.Info("-") - l.Info("reloading") - case <-signalChan: // second signal, hard exit - l.Info("-") - l.Info("terminating") - signal.Stop(signalChan) - // exit loop - return - } - } - } -} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e87d002 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/foomo/gograpple/cmd" + +func main() { + cmd.Execute() +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..a83bfd9 --- /dev/null +++ b/util/util.go @@ -0,0 +1,78 @@ +package util + +import ( + "context" + "fmt" + "os" + "os/signal" + "runtime" + "strings" + "time" + + "github.com/foomo/gograpple/internal/exec" + "github.com/sirupsen/logrus" +) + +func RunWithInterrupt(l *logrus.Entry, callback func(ctx context.Context)) { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + durReload := 3 * time.Second + for { + ctx, cancelCtx := context.WithCancel(context.Background()) + // do stuff + go callback(ctx) + select { + case <-signalChan: // first signal + l.Info("-") + l.Infof("interrupt received, trigger one more within %v to terminate", durReload) + cancelCtx() + select { + case <-time.After(durReload): // reloads durReload after first signal + l.Info("-") + l.Info("reloading") + case <-signalChan: // second signal, hard exit + l.Info("-") + l.Info("terminating") + signal.Stop(signalChan) + // exit loop + return + } + } + } +} + +func Open(l *logrus.Entry, ctx context.Context, path string) (string, error) { + var cmd *exec.Cmd + switch runtime.GOOS { + case "linux": + cmd = exec.NewCommand("xdg-open").Logger(l).Args(path) + case "windows": + cmd = exec.NewCommand("rundll32").Logger(l).Args("url.dll,FileProtocolHandler", path) + case "darwin": + cmd = exec.NewCommand("open").Logger(l).Args(path) + default: + return "", fmt.Errorf("unsupported platform") + } + return cmd.Run(ctx) +} + +func GetPlatformInfo(platform string) (os, arch string, err error) { + pieces := strings.Split(platform, "/") + if len(pieces) != 2 { + return os, arch, fmt.Errorf("invalid format for platform %q", platform) + } + return pieces[0], pieces[1], nil +} + +func ParseImage(s string) (repo, name, tag string, err error) { + pieces := strings.Split(s, "/") + switch true { + case len(pieces) == 1 && pieces[0] == s: + imageTag := strings.Split(s, ":") + return "", imageTag[0], imageTag[1], nil + case len(pieces) > 1: + imageTag := strings.Split(pieces[len(pieces)-1], ":") + return strings.Join(pieces[:len(pieces)-1], "/"), imageTag[0], imageTag[1], nil + } + return "", "", "", fmt.Errorf("invalid image value %q provided", s) +}