123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- /*
- Copyright 2017 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"
- "path/filepath"
- "runtime"
- "strings"
- "github.com/spf13/cobra"
- "k8s.io/cli-runtime/pkg/genericclioptions"
- cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
- "k8s.io/kubernetes/pkg/kubectl/util/i18n"
- "k8s.io/kubernetes/pkg/kubectl/util/templates"
- )
- var (
- pluginLong = templates.LongDesc(`
- Provides utilities for interacting with plugins.
- Plugins provide extended functionality that is not part of the major command-line distribution.
- Please refer to the documentation and examples for more information about how write your own plugins.`)
- pluginListLong = templates.LongDesc(`
- List all available plugin files on a user's PATH.
- Available plugin files are those that are:
- - executable
- - anywhere on the user's PATH
- - begin with "kubectl-"
- `)
- ValidPluginFilenamePrefixes = []string{"kubectl"}
- )
- func NewCmdPlugin(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
- cmd := &cobra.Command{
- Use: "plugin [flags]",
- DisableFlagsInUseLine: true,
- Short: i18n.T("Provides utilities for interacting with plugins."),
- Long: pluginLong,
- Run: func(cmd *cobra.Command, args []string) {
- cmdutil.DefaultSubCommandRun(streams.ErrOut)(cmd, args)
- },
- }
- cmd.AddCommand(NewCmdPluginList(f, streams))
- return cmd
- }
- type PluginListOptions struct {
- Verifier PathVerifier
- NameOnly bool
- PluginPaths []string
- genericclioptions.IOStreams
- }
- // NewCmdPluginList provides a way to list all plugin executables visible to kubectl
- func NewCmdPluginList(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
- o := &PluginListOptions{
- IOStreams: streams,
- }
- cmd := &cobra.Command{
- Use: "list",
- Short: "list all visible plugin executables on a user's PATH",
- Long: pluginListLong,
- Run: func(cmd *cobra.Command, args []string) {
- cmdutil.CheckErr(o.Complete(cmd))
- cmdutil.CheckErr(o.Run())
- },
- }
- cmd.Flags().BoolVar(&o.NameOnly, "name-only", o.NameOnly, "If true, display only the binary name of each plugin, rather than its full path")
- return cmd
- }
- func (o *PluginListOptions) Complete(cmd *cobra.Command) error {
- o.Verifier = &CommandOverrideVerifier{
- root: cmd.Root(),
- seenPlugins: make(map[string]string, 0),
- }
- o.PluginPaths = filepath.SplitList(os.Getenv("PATH"))
- return nil
- }
- func (o *PluginListOptions) Run() error {
- pluginsFound := false
- isFirstFile := true
- pluginErrors := []error{}
- pluginWarnings := 0
- for _, dir := range uniquePathsList(o.PluginPaths) {
- files, err := ioutil.ReadDir(dir)
- if err != nil {
- if _, ok := err.(*os.PathError); ok {
- fmt.Fprintf(o.ErrOut, "Unable read directory %q from your PATH: %v. Skipping...", 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.ErrOut, "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 !pluginsFound {
- pluginErrors = append(pluginErrors, fmt.Errorf("error: unable to find any kubectl plugins in your PATH"))
- }
- if pluginWarnings > 0 {
- if pluginWarnings == 1 {
- pluginErrors = append(pluginErrors, fmt.Errorf("error: one plugin warning was found"))
- } else {
- pluginErrors = append(pluginErrors, fmt.Errorf("error: %v plugin warnings were found", pluginWarnings))
- }
- }
- if len(pluginErrors) > 0 {
- fmt.Fprintln(o.ErrOut)
- errs := bytes.NewBuffer(nil)
- for _, e := range pluginErrors {
- fmt.Fprintln(errs, e)
- }
- return fmt.Errorf("%s", errs.String())
- }
- return nil
- }
- // pathVerifier receives a path and determines if it is valid or not
- type PathVerifier interface {
- // Verify determines if a given path is valid
- Verify(path string) []error
- }
- type CommandOverrideVerifier struct {
- root *cobra.Command
- seenPlugins map[string]string
- }
- // Verify implements PathVerifier and determines if a given path
- // is valid depending on whether or not it overwrites an existing
- // kubectl command path, or a previously seen plugin.
- func (v *CommandOverrideVerifier) Verify(path string) []error {
- if v.root == nil {
- return []error{fmt.Errorf("unable to verify path with nil root")}
- }
- // extract the plugin binary name
- segs := strings.Split(path, "/")
- binName := segs[len(segs)-1]
- cmdPath := strings.Split(binName, "-")
- if len(cmdPath) > 1 {
- // the first argument is always "kubectl" for a plugin binary
- cmdPath = cmdPath[1:]
- }
- errors := []error{}
- if isExec, err := isExecutable(path); err == nil && !isExec {
- errors = append(errors, fmt.Errorf("warning: %s identified as a kubectl plugin, but it is not executable", path))
- } else if err != nil {
- errors = append(errors, fmt.Errorf("error: unable to identify %s as an executable file: %v", path, err))
- }
- if existingPath, ok := v.seenPlugins[binName]; ok {
- errors = append(errors, fmt.Errorf("warning: %s is overshadowed by a similarly named plugin: %s", path, existingPath))
- } else {
- v.seenPlugins[binName] = path
- }
- if cmd, _, err := v.root.Find(cmdPath); err == nil {
- errors = append(errors, fmt.Errorf("warning: %s overwrites existing command: %q", binName, cmd.CommandPath()))
- }
- return errors
- }
- func isExecutable(fullPath string) (bool, error) {
- info, err := os.Stat(fullPath)
- if err != nil {
- return false, err
- }
- if runtime.GOOS == "windows" {
- fileExt := strings.ToLower(filepath.Ext(fullPath))
- switch fileExt {
- case ".bat", ".cmd", ".com", ".exe", ".ps1":
- return true, nil
- }
- return false, nil
- }
- if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
- return true, nil
- }
- return false, nil
- }
- // uniquePathsList deduplicates a given slice of strings without
- // sorting or otherwise altering its order in any way.
- func uniquePathsList(paths []string) []string {
- seen := map[string]bool{}
- newPaths := []string{}
- for _, p := range paths {
- if seen[p] {
- continue
- }
- seen[p] = true
- newPaths = append(newPaths, p)
- }
- return newPaths
- }
- func hasValidPrefix(filepath string, validPrefixes []string) bool {
- for _, prefix := range validPrefixes {
- if !strings.HasPrefix(filepath, prefix+"-") {
- continue
- }
- return true
- }
- return false
- }
|