From 440483e1839a297c39d4bc33cdde98b6ad58fb11 Mon Sep 17 00:00:00 2001 From: Oscar Tovar Date: Sat, 4 May 2024 03:13:39 -0400 Subject: [PATCH] feat(plugins): Support CLI plugins In the spirit of an iterative and boring solution this commit adds support for a binary plugin system in the style of `git` and `kubectl`. To use a plugin it must meet the following two conditions: 1. It must be prefixed with `glab-`. 2. It must be on the user's `$PATH`. If a command is not found and it's not a shell alias, the CLI will attempt to look for a binary that matches that matches the pattern `glab-`. If found, it will run the command and pass all remaining args to it. The previous implementation had a high degree of complexity, so the functionality to run a command and handle any errors has been abstracted into separate functions. --- cmd/glab/main.go | 76 +++++++++++++++++++++++++++++++++--------------- commands/root.go | 1 + 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/cmd/glab/main.go b/cmd/glab/main.go index d0e8ad089..0ac047757 100644 --- a/cmd/glab/main.go +++ b/cmd/glab/main.go @@ -16,6 +16,7 @@ import ( "github.com/mgutz/ansi" surveyCore "github.com/AlecAivazis/survey/v2/core" + "gitlab.com/gitlab-org/cli/commands" "gitlab.com/gitlab-org/cli/commands/alias/expand" "gitlab.com/gitlab-org/cli/commands/cmdutils" @@ -109,37 +110,30 @@ func main() { } cmd, _, err := rootCmd.Traverse(expandedArgs) - if err != nil || cmd == rootCmd { - originalArgs := expandedArgs - isShell := false + // If a command was not found during traversal it will always return the rootCmd + // which has no parent. + if err != nil || !cmd.HasParent() { + var ( + originalArgs = expandedArgs + isShell bool + ) + + if debug { + fmt.Printf("%v -> %v\n", originalArgs, expandedArgs) + } + expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil) if err != nil { cmdFactory.IO.LogInfof("Failed to process alias: %s\n", err) os.Exit(2) } - if debug { - fmt.Printf("%v -> %v\n", originalArgs, expandedArgs) - } - if isShell { - externalCmd := exec.Command(expandedArgs[0], expandedArgs[1:]...) - externalCmd.Stderr = os.Stderr - externalCmd.Stdout = os.Stdout - externalCmd.Stdin = os.Stdin - preparedCmd := run.PrepareCmd(externalCmd) - - err = preparedCmd.Run() - if err != nil { - if ee, ok := err.(*exec.ExitError); ok { - os.Exit(ee.ExitCode()) - } - - cmdFactory.IO.LogInfof("failed to run external command: %s", err) - os.Exit(3) - } + runCmd(cmdFactory, expandedArgs) + } - os.Exit(0) + if isPlugin(expandedArgs) { + runPlugin(cmdFactory, expandedArgs) } } @@ -243,3 +237,39 @@ func maybeOverrideDefaultHost(f *cmdutils.Factory, cfg config.Config) { glinstance.OverrideDefault(customGLHost) } } + +func isPlugin(args []string) bool { + if len(args) == 0 { + return false + } + searchTerm := "glab-" + args[0] + _, err := exec.LookPath(searchTerm) + return err == nil +} + +func runCmd(utils *cmdutils.Factory, args []string) { + externalCmd := exec.Command(args[0], args[1:]...) + externalCmd.Stderr = os.Stderr + externalCmd.Stdout = os.Stdout + externalCmd.Stdin = os.Stdin + + preparedCmd := run.PrepareCmd(externalCmd) + if err := preparedCmd.Run(); err != nil { + handleCmdError(utils, err) + } + + os.Exit(0) +} + +func runPlugin(utils *cmdutils.Factory, args []string) { + args[0] = "glab-" + args[0] + runCmd(utils, args) +} + +func handleCmdError(utils *cmdutils.Factory, err error) { + utils.IO.LogInfof("failed to run external command: %s", err) + if ee, ok := err.(*exec.ExitError); ok { + os.Exit(ee.ExitCode()) + } + os.Exit(3) +} diff --git a/commands/root.go b/commands/root.go index 4131d3122..170fdf022 100644 --- a/commands/root.go +++ b/commands/root.go @@ -6,6 +6,7 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" "github.com/spf13/pflag" + aliasCmd "gitlab.com/gitlab-org/cli/commands/alias" apiCmd "gitlab.com/gitlab-org/cli/commands/api" authCmd "gitlab.com/gitlab-org/cli/commands/auth" -- GitLab