diff --git a/cmd/argocd/commands/root.go b/cmd/argocd/commands/root.go index 6041a91021436..7e8f8069ce6f5 100644 --- a/cmd/argocd/commands/root.go +++ b/cmd/argocd/commands/root.go @@ -1,16 +1,11 @@ package commands import ( - pluginError "errors" "fmt" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" "os" - "os/exec" - "path/filepath" - "runtime" "strings" - "syscall" "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/admin" "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize" @@ -24,88 +19,6 @@ import ( "github.com/argoproj/argo-cd/v2/util/localconfig" ) -type ArgoCDCLIOptions struct { - PluginHandler PluginHandler - Arguments []string -} - -// PluginHandler parses command line arguments -// and performs executable filename lookups to search -// for valid plugin files, and execute found plugins. -type PluginHandler interface { - // LookForPlugin will iterate over a list of given prefixes - // in order to recognize valid plugin filenames. - // The first filepath to match a prefix is returned. - LookForPlugin(filename string) (string, bool) - // ExecutePlugin receives an executable's filepath, a slice - // of arguments, and a slice of environment variables - // to relay to the executable. - ExecutePlugin(executablePath string, cmdArgs, environment []string) error -} - -// DefaultPluginHandler implements the PluginHandler interface -type DefaultPluginHandler struct { - ValidPrefixes []string -} - -func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler { - return &DefaultPluginHandler{ - ValidPrefixes: validPrefixes, - } -} - -// LookForPlugin implements PluginHandler -func (h *DefaultPluginHandler) LookForPlugin(filename string) (string, bool) { - for _, prefix := range h.ValidPrefixes { - path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) - if shouldSkipOnLookPathErr(err) || len(path) == 0 { - continue - } - return path, true - } - return "", false -} - -// ExecutePlugin implements PluginHandler -func (h *DefaultPluginHandler) ExecutePlugin(executablePath string, cmdArgs, environment []string) error { - // Windows does not support exec syscall. - if runtime.GOOS == "windows" { - cmd := Command(executablePath, cmdArgs...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - cmd.Env = environment - err := cmd.Run() - if err == nil { - os.Exit(0) - } - return err - } - - return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment) -} - -func Command(name string, arg ...string) *exec.Cmd { - cmd := &exec.Cmd{ - Path: name, - Args: append([]string{name}, arg...), - } - if filepath.Base(name) == name { - lp, err := exec.LookPath(name) - if lp != "" && !shouldSkipOnLookPathErr(err) { - // Update cmd.Path even if err is non-nil. - // If err is ErrDot (especially on Windows), lp may include a resolved - // extension (like .exe or .bat) that should be preserved. - cmd.Path = lp - } - } - return cmd -} - -func shouldSkipOnLookPathErr(err error) bool { - return err != nil && !pluginError.Is(err, exec.ErrDot) -} - func init() { cobra.OnInitialize(initConfig) } @@ -116,13 +29,13 @@ func initConfig() { } func NewDefaultArgoCDCommand() *cobra.Command { - return NewDefaultArgoCDCommandWithArgs(ArgoCDCLIOptions{ - PluginHandler: NewDefaultPluginHandler([]string{"argocd"}), + return NewDefaultArgoCDCommandWithArgs(cmdutil.ArgoCDCLIOptions{ + PluginHandler: cmdutil.NewDefaultPluginHandler([]string{"argocd"}), Arguments: os.Args, }) } -func NewDefaultArgoCDCommandWithArgs(o ArgoCDCLIOptions) *cobra.Command { +func NewDefaultArgoCDCommandWithArgs(o cmdutil.ArgoCDCLIOptions) *cobra.Command { cmd := NewCommand() if o.PluginHandler == nil { @@ -156,7 +69,7 @@ func NewDefaultArgoCDCommandWithArgs(o ArgoCDCLIOptions) *cobra.Command { return cmd } -func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string, minArgs int) error { +func HandlePluginCommand(pluginHandler cmdutil.PluginHandler, cmdArgs []string, minArgs int) error { var remainingArgs []string // this will contain all "non-flag" arguments for _, arg := range cmdArgs { // if you encounter a flag, break the loop diff --git a/cmd/main.go b/cmd/main.go index b1770d8b4de82..56c3a7493cf67 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/argoproj/argo-cd/v2/cmd/util" "os" "path/filepath" @@ -24,8 +25,9 @@ const ( func main() { var command *cobra.Command - o := cli.ArgoCDCLIOptions{ - PluginHandler: cli.NewDefaultPluginHandler([]string{"argocd"}), + + o := util.ArgoCDCLIOptions{ + PluginHandler: util.NewDefaultPluginHandler([]string{"argocd"}), Arguments: os.Args, } diff --git a/cmd/util/plugin.go b/cmd/util/plugin.go new file mode 100644 index 0000000000000..a94ef715992d1 --- /dev/null +++ b/cmd/util/plugin.go @@ -0,0 +1,94 @@ +package util + +import ( + pluginError "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "syscall" +) + +type ArgoCDCLIOptions struct { + PluginHandler PluginHandler + Arguments []string +} + +// PluginHandler parses command line arguments +// and performs executable filename lookups to search +// for valid plugin files, and execute found plugins. +type PluginHandler interface { + // LookForPlugin will iterate over a list of given prefixes + // in order to recognize valid plugin filenames. + // The first filepath to match a prefix is returned. + LookForPlugin(filename string) (string, bool) + // ExecutePlugin receives an executable's filepath, a slice + // of arguments, and a slice of environment variables + // to relay to the executable. + ExecutePlugin(executablePath string, cmdArgs, environment []string) error +} + +// DefaultPluginHandler implements the PluginHandler interface +type DefaultPluginHandler struct { + ValidPrefixes []string +} + +func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler { + return &DefaultPluginHandler{ + ValidPrefixes: validPrefixes, + } +} + +// LookForPlugin implements PluginHandler +func (h *DefaultPluginHandler) LookForPlugin(filename string) (string, bool) { + for _, prefix := range h.ValidPrefixes { + path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) + if shouldSkipOnLookPathErr(err) || len(path) == 0 { + continue + } + return path, true + } + return "", false +} + +// ExecutePlugin implements PluginHandler +func (h *DefaultPluginHandler) ExecutePlugin(executablePath string, cmdArgs, environment []string) error { + // Windows does not support exec syscall. + if runtime.GOOS == "windows" { + cmd := Command(executablePath, cmdArgs...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Env = environment + err := cmd.Run() + if err == nil { + os.Exit(0) + } + return err + } + + return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment) +} + +func Command(name string, arg ...string) *exec.Cmd { + cmd := &exec.Cmd{ + Path: name, + Args: append([]string{name}, arg...), + } + if filepath.Base(name) == name { + lp, err := exec.LookPath(name) + if lp != "" && !shouldSkipOnLookPathErr(err) { + // Update cmd.Path even if err is non-nil. + // If err is ErrDot (especially on Windows), lp may include a resolved + // extension (like .exe or .bat) that should be preserved. + cmd.Path = lp + } + } + return cmd +} + +// shouldSkipOnLookPathErr checks if the error is nil and it is of type ErrDot +func shouldSkipOnLookPathErr(err error) bool { + return err != nil && !pluginError.Is(err, exec.ErrDot) +}