From 0257a2bceecf8c8119a096db63efd13df7157fb2 Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Fri, 1 Feb 2019 11:24:55 -0500 Subject: [PATCH] allow re-usability of plugin handler, allow support for multiple valid plugin filename prefixes --- pkg/kubectl/cmd/cmd.go | 48 +++++++++++++++++++++++--------- pkg/kubectl/cmd/cmd_test.go | 15 ++++++---- pkg/kubectl/cmd/plugin/plugin.go | 16 +++++++++-- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 6440faa646..df2be6d44f 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -293,7 +293,7 @@ var ( // NewDefaultKubectlCommand creates the `kubectl` command with default arguments func NewDefaultKubectlCommand() *cobra.Command { - return NewDefaultKubectlCommandWithArgs(&defaultPluginHandler{}, os.Args, os.Stdin, os.Stdout, os.Stderr) + return NewDefaultKubectlCommandWithArgs(NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args, os.Stdin, os.Stdout, os.Stderr) } // NewDefaultKubectlCommandWithArgs creates the `kubectl` command with arguments @@ -310,7 +310,7 @@ func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string // only look for suitable extension executables if // the specified command does not already exist if _, _, err := cmd.Find(cmdPathPieces); err != nil { - if err := handleEndpointExtensions(pluginHandler, cmdPathPieces); err != nil { + if err := HandlePluginCommand(pluginHandler, cmdPathPieces); err != nil { fmt.Fprintf(errout, "%v\n", err) os.Exit(1) } @@ -324,29 +324,51 @@ func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string // and performing executable filename lookups to search // for valid plugin files, and execute found plugins. type PluginHandler interface { - // Lookup receives a potential filename and returns - // a full or relative path to an executable, if one - // exists at the given filename, or an error. - Lookup(filename string) (string, error) + // exists at the given filename, or a boolean false. + // Lookup will iterate over a list of given prefixes + // in order to recognize valid plugin filenames. + // The first filepath to match a prefix is returned. + Lookup(filename string) (string, bool) // Execute receives an executable's filepath, a slice // of arguments, and a slice of environment variables // to relay to the executable. Execute(executablePath string, cmdArgs, environment []string) error } -type defaultPluginHandler struct{} +// DefaultPluginHandler implements PluginHandler +type DefaultPluginHandler struct { + ValidPrefixes []string +} + +// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of +// given filename prefixes used to identify valid plugin filenames. +func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler { + return &DefaultPluginHandler{ + ValidPrefixes: validPrefixes, + } +} // Lookup implements PluginHandler -func (h *defaultPluginHandler) Lookup(filename string) (string, error) { - return exec.LookPath(filename) +func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) { + for _, prefix := range h.ValidPrefixes { + path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) + if err != nil || len(path) == 0 { + continue + } + return path, true + } + + return "", false } // Execute implements PluginHandler -func (h *defaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error { +func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error { return syscall.Exec(executablePath, cmdArgs, environment) } -func handleEndpointExtensions(pluginHandler PluginHandler, cmdArgs []string) error { +// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find +// a plugin executable on the PATH that satisfies the given arguments. +func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error { remainingArgs := []string{} // all "non-flag" arguments for idx := range cmdArgs { @@ -360,8 +382,8 @@ func handleEndpointExtensions(pluginHandler PluginHandler, cmdArgs []string) err // attempt to find binary, starting at longest possible name with given cmdArgs for len(remainingArgs) > 0 { - path, err := pluginHandler.Lookup(fmt.Sprintf("kubectl-%s", strings.Join(remainingArgs, "-"))) - if err != nil || len(path) == 0 { + path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-")) + if !found { remainingArgs = remainingArgs[:len(remainingArgs)-1] continue } diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 34da43ebb0..fab6dc6d36 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -175,32 +175,35 @@ type testPluginHandler struct { err error } -func (h *testPluginHandler) Lookup(filename string) (string, error) { +func (h *testPluginHandler) Lookup(filename string) (string, bool) { + // append supported plugin prefix to the filename + filename = fmt.Sprintf("%s-%s", "kubectl", filename) + dir, err := os.Stat(h.pluginsDirectory) if err != nil { h.err = err - return "", err + return "", false } if !dir.IsDir() { h.err = fmt.Errorf("expected %q to be a directory", h.pluginsDirectory) - return "", h.err + return "", false } plugins, err := ioutil.ReadDir(h.pluginsDirectory) if err != nil { h.err = err - return "", err + return "", false } for _, p := range plugins { if p.Name() == filename { - return fmt.Sprintf("%s/%s", h.pluginsDirectory, p.Name()), nil + return fmt.Sprintf("%s/%s", h.pluginsDirectory, p.Name()), true } } h.err = fmt.Errorf("unable to find a plugin executable %q", filename) - return "", h.err + return "", false } func (h *testPluginHandler) Execute(executablePath string, cmdArgs, env []string) error { diff --git a/pkg/kubectl/cmd/plugin/plugin.go b/pkg/kubectl/cmd/plugin/plugin.go index c5f7a01fbd..ebe1f09525 100644 --- a/pkg/kubectl/cmd/plugin/plugin.go +++ b/pkg/kubectl/cmd/plugin/plugin.go @@ -49,6 +49,8 @@ var ( - anywhere on the user's PATH - begin with "kubectl-" `) + + ValidPluginFilenamePrefixes = []string{"kubectl"} ) func NewCmdPlugin(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { @@ -127,12 +129,12 @@ func (o *PluginListOptions) Run() error { if f.IsDir() { continue } - if !strings.HasPrefix(f.Name(), "kubectl-") { + if !hasValidPrefix(f.Name(), ValidPluginFilenamePrefixes) { continue } if isFirstFile { - fmt.Fprintf(o.ErrOut, "The following kubectl-compatible plugins are available:\n\n") + fmt.Fprintf(o.ErrOut, "The following compatible plugins are available:\n\n") pluginsFound = true isFirstFile = false } @@ -262,3 +264,13 @@ func uniquePathsList(paths []string) []string { } return newPaths } + +func hasValidPrefix(filepath string, validPrefixes []string) bool { + for _, prefix := range validPrefixes { + if !strings.HasPrefix(filepath, prefix+"-") { + continue + } + return true + } + return false +}