github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/plugin.go (about) 1 package commands 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 11 "github.com/argoproj/argo-cd/v3/util/cli" 12 13 log "github.com/sirupsen/logrus" 14 ) 15 16 // DefaultPluginHandler implements the PluginHandler interface 17 type DefaultPluginHandler struct { 18 ValidPrefixes []string 19 lookPath func(file string) (string, error) 20 run func(cmd *exec.Cmd) error 21 } 22 23 // NewDefaultPluginHandler instantiates the DefaultPluginHandler 24 func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler { 25 return &DefaultPluginHandler{ 26 ValidPrefixes: validPrefixes, 27 lookPath: exec.LookPath, 28 run: func(cmd *exec.Cmd) error { 29 return cmd.Run() 30 }, 31 } 32 } 33 34 // HandleCommandExecutionError processes the error returned from executing the command. 35 // It handles both standard Argo CD commands and plugin commands. We don't require to return 36 // error but we are doing it to cover various test scenarios. 37 func (h *DefaultPluginHandler) HandleCommandExecutionError(err error, isArgocdCLI bool, args []string) error { 38 // the log level needs to be setup manually here since the initConfig() 39 // set by the cobra.OnInitialize() was never executed because cmd.Execute() 40 // gave us a non-nil error. 41 initConfig() 42 cli.SetLogFormat("text") 43 // If it's an unknown command error, attempt to handle it as a plugin. 44 // Unfortunately, cobra doesn't handle this error, so we need to assume 45 // that error consists of substring "unknown command". 46 // https://github.com/spf13/cobra/pull/2167 47 if isArgocdCLI && strings.Contains(err.Error(), "unknown command") { 48 pluginPath, pluginErr := h.handlePluginCommand(args[1:]) 49 // IMP: If a plugin doesn't exist, the returned path will be empty along with nil error 50 // This means the command is neither a normal Argo CD Command nor a plugin. 51 if pluginErr != nil { 52 // If plugin handling fails, report the plugin error and exit 53 fmt.Printf("Error: %v\n", pluginErr) 54 return pluginErr 55 } else if pluginPath == "" { 56 fmt.Printf("Error: %v\nRun 'argocd --help' for usage.\n", err) 57 return err 58 } 59 } else { 60 // If it's any other error (not an unknown command), report it directly and exit 61 fmt.Printf("Error: %v\n", err) 62 return err 63 } 64 65 return nil 66 } 67 68 // handlePluginCommand is responsible for finding and executing a plugin when a command isn't recognized as a built-in command 69 func (h *DefaultPluginHandler) handlePluginCommand(cmdArgs []string) (string, error) { 70 foundPluginPath := "" 71 path, found := h.lookForPlugin(cmdArgs[0]) 72 if !found { 73 return foundPluginPath, nil 74 } 75 76 foundPluginPath = path 77 78 // Execute the plugin that is found 79 if err := h.executePlugin(foundPluginPath, cmdArgs[1:], os.Environ()); err != nil { 80 return foundPluginPath, err 81 } 82 83 return foundPluginPath, nil 84 } 85 86 // lookForPlugin looks for a plugin in the PATH that starts with argocd prefix 87 func (h *DefaultPluginHandler) lookForPlugin(filename string) (string, bool) { 88 for _, prefix := range h.ValidPrefixes { 89 pluginName := fmt.Sprintf("%s-%s", prefix, filename) 90 path, err := h.lookPath(pluginName) 91 if err != nil { 92 // error if a plugin is found in a relative path 93 if errors.Is(err, exec.ErrDot) { 94 log.Errorf("Plugin '%s' found in relative path: %v", pluginName, err) 95 } else { 96 log.Warnf("error looking for plugin '%s': %v", pluginName, err) 97 } 98 continue 99 } 100 101 if path == "" { 102 return "", false 103 } 104 105 return path, true 106 } 107 108 return "", false 109 } 110 111 // executePlugin implements PluginHandler and executes a plugin found 112 func (h *DefaultPluginHandler) executePlugin(executablePath string, cmdArgs, environment []string) error { 113 cmd := h.command(executablePath, cmdArgs...) 114 cmd.Stdout = os.Stdout 115 cmd.Stderr = os.Stderr 116 cmd.Stdin = os.Stdin 117 cmd.Env = environment 118 119 err := h.run(cmd) 120 if err != nil { 121 return err 122 } 123 124 return nil 125 } 126 127 // command creates a new command for all OSs 128 func (h *DefaultPluginHandler) command(name string, arg ...string) *exec.Cmd { 129 cmd := &exec.Cmd{ 130 Path: name, 131 Args: append([]string{name}, arg...), 132 } 133 if filepath.Base(name) == name { 134 lp, err := h.lookPath(name) 135 if lp != "" && err != nil { 136 // Update cmd.Path even if err is non-nil. 137 // If err is ErrDot (especially on Windows), lp may include a resolved 138 // extension (like .exe or .bat) that should be preserved. 139 cmd.Path = lp 140 } 141 } 142 return cmd 143 }