allow re-usability of plugin handler, allow support for multiple valid plugin filename prefixes

pull/564/head
juanvallejo 2019-02-01 11:24:55 -05:00
parent 556c0b8593
commit 0257a2bcee
No known key found for this signature in database
GPG Key ID: 7D2C958002D6448D
3 changed files with 58 additions and 21 deletions

View File

@ -293,7 +293,7 @@ var (
// NewDefaultKubectlCommand creates the `kubectl` command with default arguments // NewDefaultKubectlCommand creates the `kubectl` command with default arguments
func NewDefaultKubectlCommand() *cobra.Command { 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 // NewDefaultKubectlCommandWithArgs creates the `kubectl` command with arguments
@ -310,7 +310,7 @@ func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string
// only look for suitable extension executables if // only look for suitable extension executables if
// the specified command does not already exist // the specified command does not already exist
if _, _, err := cmd.Find(cmdPathPieces); err != nil { 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) fmt.Fprintf(errout, "%v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -324,29 +324,51 @@ func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string
// and performing executable filename lookups to search // and performing executable filename lookups to search
// for valid plugin files, and execute found plugins. // for valid plugin files, and execute found plugins.
type PluginHandler interface { type PluginHandler interface {
// Lookup receives a potential filename and returns // exists at the given filename, or a boolean false.
// a full or relative path to an executable, if one // Lookup will iterate over a list of given prefixes
// exists at the given filename, or an error. // in order to recognize valid plugin filenames.
Lookup(filename string) (string, error) // The first filepath to match a prefix is returned.
Lookup(filename string) (string, bool)
// Execute receives an executable's filepath, a slice // Execute receives an executable's filepath, a slice
// of arguments, and a slice of environment variables // of arguments, and a slice of environment variables
// to relay to the executable. // to relay to the executable.
Execute(executablePath string, cmdArgs, environment []string) error 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 // Lookup implements PluginHandler
func (h *defaultPluginHandler) Lookup(filename string) (string, error) { func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) {
return exec.LookPath(filename) 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 // 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) 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 remainingArgs := []string{} // all "non-flag" arguments
for idx := range cmdArgs { 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 // attempt to find binary, starting at longest possible name with given cmdArgs
for len(remainingArgs) > 0 { for len(remainingArgs) > 0 {
path, err := pluginHandler.Lookup(fmt.Sprintf("kubectl-%s", strings.Join(remainingArgs, "-"))) path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
if err != nil || len(path) == 0 { if !found {
remainingArgs = remainingArgs[:len(remainingArgs)-1] remainingArgs = remainingArgs[:len(remainingArgs)-1]
continue continue
} }

View File

@ -175,32 +175,35 @@ type testPluginHandler struct {
err error 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) dir, err := os.Stat(h.pluginsDirectory)
if err != nil { if err != nil {
h.err = err h.err = err
return "", err return "", false
} }
if !dir.IsDir() { if !dir.IsDir() {
h.err = fmt.Errorf("expected %q to be a directory", h.pluginsDirectory) h.err = fmt.Errorf("expected %q to be a directory", h.pluginsDirectory)
return "", h.err return "", false
} }
plugins, err := ioutil.ReadDir(h.pluginsDirectory) plugins, err := ioutil.ReadDir(h.pluginsDirectory)
if err != nil { if err != nil {
h.err = err h.err = err
return "", err return "", false
} }
for _, p := range plugins { for _, p := range plugins {
if p.Name() == filename { 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) 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 { func (h *testPluginHandler) Execute(executablePath string, cmdArgs, env []string) error {

View File

@ -49,6 +49,8 @@ var (
- anywhere on the user's PATH - anywhere on the user's PATH
- begin with "kubectl-" - begin with "kubectl-"
`) `)
ValidPluginFilenamePrefixes = []string{"kubectl"}
) )
func NewCmdPlugin(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { func NewCmdPlugin(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
@ -127,12 +129,12 @@ func (o *PluginListOptions) Run() error {
if f.IsDir() { if f.IsDir() {
continue continue
} }
if !strings.HasPrefix(f.Name(), "kubectl-") { if !hasValidPrefix(f.Name(), ValidPluginFilenamePrefixes) {
continue continue
} }
if isFirstFile { 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 pluginsFound = true
isFirstFile = false isFirstFile = false
} }
@ -262,3 +264,13 @@ func uniquePathsList(paths []string) []string {
} }
return newPaths return newPaths
} }
func hasValidPrefix(filepath string, validPrefixes []string) bool {
for _, prefix := range validPrefixes {
if !strings.HasPrefix(filepath, prefix+"-") {
continue
}
return true
}
return false
}