Merge pull request #105867 from VilledeMontreal/feature/compPlugins
Shell completion for plugins Kubernetes-commit: 797536fc76854a856bc1d1156c2355705af461ca
This commit is contained in:
commit
182c1bcbee
16
go.mod
16
go.mod
|
@ -29,11 +29,11 @@ require (
|
|||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.0.0-20221012115127-0184bd884c5e
|
||||
k8s.io/apimachinery v0.0.0-20221017194938-70a38aaa19ef
|
||||
k8s.io/cli-runtime v0.0.0-20221015041739-b6a5653c8754
|
||||
k8s.io/api v0.0.0-20221019235207-4e8dc44b5ed4
|
||||
k8s.io/apimachinery v0.0.0-20221025074955-6809593a7033
|
||||
k8s.io/cli-runtime v0.0.0-20221023121201-175ec90deb21
|
||||
k8s.io/client-go v0.0.0-20221019075459-ef8a2e522779
|
||||
k8s.io/component-base v0.0.0-20221019000337-4f487d08c46c
|
||||
k8s.io/component-base v0.0.0-20221020195653-9fd3a641bd31
|
||||
k8s.io/component-helpers v0.0.0-20221017200342-f55d4a0c1767
|
||||
k8s.io/klog/v2 v2.80.1
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280
|
||||
|
@ -91,12 +91,12 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20221012115127-0184bd884c5e
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20221017194938-70a38aaa19ef
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20221015041739-b6a5653c8754
|
||||
k8s.io/api => k8s.io/api v0.0.0-20221019235207-4e8dc44b5ed4
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20221025074955-6809593a7033
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20221023121201-175ec90deb21
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20221019075459-ef8a2e522779
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20221018161754-557ce1f667c7
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20221019000337-4f487d08c46c
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20221020195653-9fd3a641bd31
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20221017200342-f55d4a0c1767
|
||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20221017201900-57e823688239
|
||||
)
|
||||
|
|
16
go.sum
16
go.sum
|
@ -539,16 +539,16 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.0.0-20221012115127-0184bd884c5e h1:N2+121lkGMNfLeos4/AlwuujPJd5xJfFkyi15BUzob8=
|
||||
k8s.io/api v0.0.0-20221012115127-0184bd884c5e/go.mod h1:Q2jmki3lhHeeAyYtbIiEe/2JoGg2Ge1cJoMgpFkjtzg=
|
||||
k8s.io/apimachinery v0.0.0-20221017194938-70a38aaa19ef h1:4lKLUMgwg9xi0UV4qjwS2NHmxBU2llLMdfO7czD9gUg=
|
||||
k8s.io/apimachinery v0.0.0-20221017194938-70a38aaa19ef/go.mod h1:/x4E+/xaA5ap3q0tWNh5IPFt63dzY1I2qP8zT4sr5Yg=
|
||||
k8s.io/cli-runtime v0.0.0-20221015041739-b6a5653c8754 h1:DFW+3b/iGJ7pDcxg0WxRwXIGzHh32tkKoGW7zvl/SFE=
|
||||
k8s.io/cli-runtime v0.0.0-20221015041739-b6a5653c8754/go.mod h1:VhlC3GpPPnhI7o+8McywZaDeruyDfbOeYgSxxChhlto=
|
||||
k8s.io/api v0.0.0-20221019235207-4e8dc44b5ed4 h1:bAysBSkbhejSgrDp/2bPnwmTKP4jCcgnOUK9uRF0HNU=
|
||||
k8s.io/api v0.0.0-20221019235207-4e8dc44b5ed4/go.mod h1:lufoDw/2RNpRjwTuQb5BJkcJa9ZMKWtv/KgvMgjl/p0=
|
||||
k8s.io/apimachinery v0.0.0-20221025074955-6809593a7033 h1:CdwrCR9273HRl4EmYYIkla7Gk71SzZfYIcUAARwo/g0=
|
||||
k8s.io/apimachinery v0.0.0-20221025074955-6809593a7033/go.mod h1:/x4E+/xaA5ap3q0tWNh5IPFt63dzY1I2qP8zT4sr5Yg=
|
||||
k8s.io/cli-runtime v0.0.0-20221023121201-175ec90deb21 h1:jaPF2P0caIjEsdKPvc+87EvfdoNpDkkc2udD5rPmoyY=
|
||||
k8s.io/cli-runtime v0.0.0-20221023121201-175ec90deb21/go.mod h1:n34JMBqs7Su0Cv0b6VU8l9TRy+wCZW2MkoIIrIjGG3A=
|
||||
k8s.io/client-go v0.0.0-20221019075459-ef8a2e522779 h1:+u8zTIggt94pLdjOWhaoW2iJwCaArrP2HnBlmJwk0mM=
|
||||
k8s.io/client-go v0.0.0-20221019075459-ef8a2e522779/go.mod h1:r+Jiu2RH1zXcJsmml1qRHg9oBq4sHHcMRaiEV0GN0ME=
|
||||
k8s.io/component-base v0.0.0-20221019000337-4f487d08c46c h1:JEJlkqGJMe1WCIri8MWhUc1vryNLbP6SFmspoSwMbt8=
|
||||
k8s.io/component-base v0.0.0-20221019000337-4f487d08c46c/go.mod h1:GzuOBZLTCAovez80ouX6fNL7eTjPnb0b2WDJkqL1k5c=
|
||||
k8s.io/component-base v0.0.0-20221020195653-9fd3a641bd31 h1:54deIoyIYAFi9VM0xBsnGa8bINsWZ2W31ol7rILtomM=
|
||||
k8s.io/component-base v0.0.0-20221020195653-9fd3a641bd31/go.mod h1:7LKizYk+wpA8YDdOijXEUHif9/8WZFqSvUxFo1YNl2M=
|
||||
k8s.io/component-helpers v0.0.0-20221017200342-f55d4a0c1767 h1:1NIJhCNc61Jv0Opi1E0ke8c5fwNsy2ZU0r/zXzcoh00=
|
||||
k8s.io/component-helpers v0.0.0-20221017200342-f55d4a0c1767/go.mod h1:QwKUXpBsJXPkBJiSLYaWUEvPyq0O7yWEvqo4/RXeRsc=
|
||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||
|
|
|
@ -279,8 +279,15 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
Run: runHelp,
|
||||
// Hook before and after Run initialize and write profiles to disk,
|
||||
// respectively.
|
||||
PersistentPreRunE: func(*cobra.Command, []string) error {
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
rest.SetDefaultWarningHandler(warningHandler)
|
||||
|
||||
if cmd.Name() == cobra.ShellCompRequestCmd {
|
||||
// This is the __complete or __completeNoDesc command which
|
||||
// indicates shell completion has been requested.
|
||||
plugin.SetupPluginCompletion(cmd, args)
|
||||
}
|
||||
|
||||
return initProfiling()
|
||||
},
|
||||
PersistentPostRunE: func(*cobra.Command, []string) error {
|
||||
|
|
|
@ -115,58 +115,27 @@ func (o *PluginListOptions) Complete(cmd *cobra.Command) error {
|
|||
}
|
||||
|
||||
func (o *PluginListOptions) Run() error {
|
||||
pluginsFound := false
|
||||
isFirstFile := true
|
||||
pluginErrors := []error{}
|
||||
pluginWarnings := 0
|
||||
plugins, pluginErrors := o.ListPlugins()
|
||||
|
||||
for _, dir := range uniquePathsList(o.PluginPaths) {
|
||||
if len(strings.TrimSpace(dir)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
fmt.Fprintf(o.ErrOut, "Unable to read directory %q from your PATH: %v. Skipping...\n", dir, err)
|
||||
continue
|
||||
}
|
||||
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: unable to read directory %q in your PATH: %v", dir, err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !hasValidPrefix(f.Name(), ValidPluginFilenamePrefixes) {
|
||||
continue
|
||||
}
|
||||
|
||||
if isFirstFile {
|
||||
fmt.Fprintf(o.Out, "The following compatible plugins are available:\n\n")
|
||||
pluginsFound = true
|
||||
isFirstFile = false
|
||||
}
|
||||
|
||||
pluginPath := f.Name()
|
||||
if !o.NameOnly {
|
||||
pluginPath = filepath.Join(dir, pluginPath)
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.Out, "%s\n", pluginPath)
|
||||
if errs := o.Verifier.Verify(filepath.Join(dir, f.Name())); len(errs) != 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(o.ErrOut, " - %s\n", err)
|
||||
pluginWarnings++
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(plugins) > 0 {
|
||||
fmt.Fprintf(o.Out, "The following compatible plugins are available:\n\n")
|
||||
} else {
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: unable to find any kubectl plugins in your PATH"))
|
||||
}
|
||||
|
||||
if !pluginsFound {
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: unable to find any kubectl plugins in your PATH"))
|
||||
pluginWarnings := 0
|
||||
for _, pluginPath := range plugins {
|
||||
if o.NameOnly {
|
||||
fmt.Fprintf(o.Out, "%s\n", filepath.Base(pluginPath))
|
||||
} else {
|
||||
fmt.Fprintf(o.Out, "%s\n", pluginPath)
|
||||
}
|
||||
if errs := o.Verifier.Verify(pluginPath); len(errs) != 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(o.ErrOut, " - %s\n", err)
|
||||
pluginWarnings++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pluginWarnings > 0 {
|
||||
|
@ -187,6 +156,42 @@ func (o *PluginListOptions) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ListPlugins returns list of plugin paths.
|
||||
func (o *PluginListOptions) ListPlugins() ([]string, []error) {
|
||||
plugins := []string{}
|
||||
errors := []error{}
|
||||
|
||||
for _, dir := range uniquePathsList(o.PluginPaths) {
|
||||
if len(strings.TrimSpace(dir)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
fmt.Fprintf(o.ErrOut, "Unable to read directory %q from your PATH: %v. Skipping...\n", dir, err)
|
||||
continue
|
||||
}
|
||||
|
||||
errors = append(errors, fmt.Errorf("error: unable to read directory %q in your PATH: %v", dir, err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !hasValidPrefix(f.Name(), ValidPluginFilenamePrefixes) {
|
||||
continue
|
||||
}
|
||||
|
||||
plugins = append(plugins, filepath.Join(dir, f.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
return plugins, errors
|
||||
}
|
||||
|
||||
// pathVerifier receives a path and determines if it is valid or not
|
||||
type PathVerifier interface {
|
||||
// Verify determines if a given path is valid
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
// SetupPluginCompletion adds a Cobra command to the command tree for each
|
||||
// plugin. This is only done when performing shell completion that relate
|
||||
// to plugins.
|
||||
func SetupPluginCompletion(cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
if strings.HasPrefix(args[0], "-") {
|
||||
// Plugins are not supported if the first argument is a flag,
|
||||
// so no need to add them in that case.
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
// We are completing a subcommand at the first level so
|
||||
// we should include all plugins names.
|
||||
addPluginCommands(cmd)
|
||||
return
|
||||
}
|
||||
|
||||
// We have more than one argument.
|
||||
// Check if we know the first level subcommand.
|
||||
// If we don't it could be a plugin and we'll need to add
|
||||
// the plugin commands for completion to work.
|
||||
found := false
|
||||
for _, subCmd := range cmd.Root().Commands() {
|
||||
if args[0] == subCmd.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// We don't know the subcommand for which completion
|
||||
// is being called: it could be a plugin.
|
||||
//
|
||||
// When using a plugin, the kubectl global flags are not supported.
|
||||
// Therefore, when doing completion, we need to remove these flags
|
||||
// to avoid them being included in the completion choices.
|
||||
// This must be done *before* adding the plugin commands so that
|
||||
// when creating those plugin commands, the flags don't exist.
|
||||
cmd.Root().ResetFlags()
|
||||
cobra.CompDebugln("Cleared global flags for plugin completion", true)
|
||||
|
||||
addPluginCommands(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addPluginCommand adds a Cobra command to the command tree
|
||||
// for each plugin so that the completion logic knows about the plugins
|
||||
func addPluginCommands(cmd *cobra.Command) {
|
||||
kubectl := cmd.Root()
|
||||
streams := genericclioptions.IOStreams{
|
||||
In: &bytes.Buffer{},
|
||||
Out: ioutil.Discard,
|
||||
ErrOut: ioutil.Discard,
|
||||
}
|
||||
|
||||
o := &PluginListOptions{IOStreams: streams}
|
||||
o.Complete(kubectl)
|
||||
plugins, _ := o.ListPlugins()
|
||||
|
||||
for _, plugin := range plugins {
|
||||
plugin = filepath.Base(plugin)
|
||||
args := []string{}
|
||||
|
||||
// Plugins are named "kubectl-<name>" or with more - such as
|
||||
// "kubectl-<name>-<subcmd1>..."
|
||||
for _, arg := range strings.Split(plugin, "-")[1:] {
|
||||
// Underscores (_) in plugin's filename are replaced with dashes(-)
|
||||
// e.g. foo_bar -> foo-bar
|
||||
args = append(args, strings.Replace(arg, "_", "-", -1))
|
||||
}
|
||||
|
||||
// In order to avoid that the same plugin command is added more than once,
|
||||
// find the lowest command given args from the root command
|
||||
parentCmd, remainingArgs, _ := kubectl.Find(args)
|
||||
if parentCmd == nil {
|
||||
parentCmd = kubectl
|
||||
}
|
||||
|
||||
for _, remainingArg := range remainingArgs {
|
||||
cmd := &cobra.Command{
|
||||
Use: remainingArg,
|
||||
// Add a description that will be shown with completion choices.
|
||||
// Make each one different by including the plugin name to avoid
|
||||
// all plugins being grouped in a single line during completion for zsh.
|
||||
Short: fmt.Sprintf("The command %s is a plugin installed by the user", remainingArg),
|
||||
DisableFlagParsing: true,
|
||||
// Allow plugins to provide their own completion choices
|
||||
ValidArgsFunction: pluginCompletion,
|
||||
// A Run is required for it to be a valid command
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
parentCmd.AddCommand(cmd)
|
||||
parentCmd = cmd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluginCompletion deals with shell completion beyond the plugin name, it allows to complete
|
||||
// plugin arguments and flags.
|
||||
// It will look on $PATH for a specific executable file that will provide completions
|
||||
// for the plugin in question.
|
||||
//
|
||||
// When called, this completion executable should print the completion choices to stdout.
|
||||
// The arguments passed to the executable file will be the arguments for the plugin currently
|
||||
// on the command-line. For example, if a user types:
|
||||
//
|
||||
// kubectl myplugin arg1 arg2 a<TAB>
|
||||
//
|
||||
// the completion executable will be called with arguments: "arg1" "arg2" "a".
|
||||
// And if a user types:
|
||||
//
|
||||
// kubectl myplugin arg1 arg2 <TAB>
|
||||
//
|
||||
// the completion executable will be called with arguments: "arg1" "arg2" "". Notice the empty
|
||||
// last argument which indicates that a new word should be completed but that the user has not
|
||||
// typed anything for it yet.
|
||||
//
|
||||
// Kubectl's plugin completion logic supports Cobra's ShellCompDirective system. This means a plugin
|
||||
// can optionally print :<value of a shell completion directive> as its very last line to provide
|
||||
// directives to the shell on how to perform completion. If this directive is not present, the
|
||||
// cobra.ShellCompDirectiveDefault will be used. Please see Cobra's documentation for more details:
|
||||
// https://github.com/spf13/cobra/blob/master/shell_completions.md#dynamic-completion-of-nouns
|
||||
//
|
||||
// The completion executable should be named kubectl_complete-<plugin>. For example, for a plugin
|
||||
// named kubectl-get_all, the completion file should be named kubectl_complete-get_all. The completion
|
||||
// executable must have executable permissions set on it and must be on $PATH.
|
||||
func pluginCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
// Recreate the plugin name from the commandPath
|
||||
pluginName := strings.Replace(strings.Replace(cmd.CommandPath(), "-", "_", -1), " ", "-", -1)
|
||||
|
||||
path, found := lookupCompletionExec(pluginName)
|
||||
if !found {
|
||||
cobra.CompDebugln(fmt.Sprintf("Plugin %s does not provide a matching completion executable", pluginName), true)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
args = append(args, toComplete)
|
||||
cobra.CompDebugln(fmt.Sprintf("About to call: %s %s", path, strings.Join(args, " ")), true)
|
||||
return getPluginCompletions(path, args, os.Environ())
|
||||
}
|
||||
|
||||
// lookupCompletionExec will look for the existence of an executable
|
||||
// that can provide completion for the given plugin name.
|
||||
// The first filepath to match is returned, or a boolean false if
|
||||
// such an executable is not found.
|
||||
func lookupCompletionExec(pluginName string) (string, bool) {
|
||||
// Convert the plugin name into the plugin completion name by inserting "_complete" before the first -.
|
||||
// For example, convert kubectl-get_all to kubectl_complete-get_all
|
||||
pluginCompExec := strings.Replace(pluginName, "-", "_complete-", 1)
|
||||
cobra.CompDebugln(fmt.Sprintf("About to look for: %s", pluginCompExec), true)
|
||||
path, err := exec.LookPath(pluginCompExec)
|
||||
if err != nil || len(path) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return path, true
|
||||
}
|
||||
|
||||
// getPluginCompletions receives an executable's filepath, a slice
|
||||
// of arguments, and a slice of environment variables
|
||||
// to relay to the executable.
|
||||
// The executable is responsible for printing the completions of the
|
||||
// plugin for the current set of arguments.
|
||||
func getPluginCompletions(executablePath string, cmdArgs, environment []string) ([]string, cobra.ShellCompDirective) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
prog := exec.Command(executablePath, cmdArgs...)
|
||||
prog.Stdin = os.Stdin
|
||||
prog.Stdout = buf
|
||||
prog.Stderr = os.Stderr
|
||||
prog.Env = environment
|
||||
|
||||
var comps []string
|
||||
directive := cobra.ShellCompDirectiveDefault
|
||||
if err := prog.Run(); err == nil {
|
||||
for _, comp := range strings.Split(buf.String(), "\n") {
|
||||
// Remove any empty lines
|
||||
if len(comp) > 0 {
|
||||
comps = append(comps, comp)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last line of output is of the form :<integer>, which
|
||||
// indicates a Cobra ShellCompDirective. We do this for plugins
|
||||
// that use Cobra or the ones that wish to use this directive to
|
||||
// communicate a special behavior for the shell.
|
||||
if len(comps) > 0 {
|
||||
lastLine := comps[len(comps)-1]
|
||||
if len(lastLine) > 1 && lastLine[0] == ':' {
|
||||
if strInt, err := strconv.Atoi(lastLine[1:]); err == nil {
|
||||
directive = cobra.ShellCompDirective(strInt)
|
||||
comps = comps[:len(comps)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return comps, directive
|
||||
}
|
|
@ -20,6 +20,8 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -178,6 +180,34 @@ func TestPluginPathsAreValid(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListPlugins(t *testing.T) {
|
||||
pluginPath, _ := filepath.Abs("./testdata")
|
||||
expectPlugins := []string{
|
||||
filepath.Join(pluginPath, "kubectl-foo"),
|
||||
filepath.Join(pluginPath, "kubectl-version"),
|
||||
}
|
||||
|
||||
verifier := newFakePluginPathVerifier()
|
||||
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
|
||||
pluginPaths := []string{pluginPath}
|
||||
|
||||
o := &PluginListOptions{
|
||||
Verifier: verifier,
|
||||
IOStreams: ioStreams,
|
||||
|
||||
PluginPaths: pluginPaths,
|
||||
}
|
||||
|
||||
plugins, errs := o.ListPlugins()
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("unexpected errors: %v", errs)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectPlugins, plugins) {
|
||||
t.Fatalf("saw unexpected plugins. Expecting %v, got %v", expectPlugins, plugins)
|
||||
}
|
||||
}
|
||||
|
||||
type duplicatePathError struct {
|
||||
path string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue