From a746e4f2db7bad5ce9bd7c2292ccdf85ba164f22 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 13 Aug 2020 07:55:45 +0200 Subject: [PATCH 1/3] init plugin system --- cmd/root.go | 11 +++++++ cmd/util/plugins.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 cmd/util/plugins.go diff --git a/cmd/root.go b/cmd/root.go index 8bf404de..e80156a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -60,6 +60,17 @@ k3d is a wrapper CLI that helps you to easily create k3s clusters inside docker. Nodes of a k3d cluster are docker containers running a k3s image. All Nodes of a k3d cluster are part of the same docker network.`, Run: func(cmd *cobra.Command, args []string) { + if len(args) > 1 { + parts := args[1:] + + // Check if it's a built-in command, else try to execute it as a plugin + if _, _, err := cmd.Find(parts); err != nil { + if err := util.HandlePlugin(parts); err != nil { + log.Errorf("Failed to execute plugin '%+v'", parts) + log.Fatalln(err) + } + } + } if flags.version { printVersion() } else { diff --git a/cmd/util/plugins.go b/cmd/util/plugins.go new file mode 100644 index 00000000..b32bab9c --- /dev/null +++ b/cmd/util/plugins.go @@ -0,0 +1,76 @@ +/* +Copyright © 2020 The k3d Author(s) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package util + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +func HandlePlugin(args []string) error { + argsPrefix := []string{} + + for _, arg := range args { + if strings.HasPrefix(arg, "-") { + continue // drop flags + } + argsPrefix = append(argsPrefix, strings.ReplaceAll(arg, "-", "_")) // plugin executables assumed to have underscores + } + + execPath := "" + + for len(argsPrefix) > 0 { + path, found := FindPlugin(strings.Join(argsPrefix, "-")) + + if !found { + argsPrefix = argsPrefix[:len(argsPrefix)-1] // drop last element + continue + } + + execPath = path + break + } + + if execPath == "" { + return nil + } + + return ExecPlugin(execPath, args[len(argsPrefix):], os.Environ()) + +} + +// FindPlugin tries to find the plugin executable on the filesystem +func FindPlugin(name string) (string, bool) { + path, err := exec.LookPath(fmt.Sprintf("%s-%s", k3d.DefaultObjectNamePrefix, name)) + if err == nil && len(path) > 0 { + return path, true + } + return "", false +} + +// ExecPlugin executes a found plugin +func ExecPlugin(path string, args []string, env []string) error { + + return nil +} From fb0d09545bfbe2839e1b8ef2b446ae23f3a12581 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 13 Aug 2020 09:05:26 +0200 Subject: [PATCH 2/3] plugin: finish handling and executing plugins --- cmd/root.go | 27 +++++++++++++++------------ cmd/util/plugins.go | 20 ++++++++++++++------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index e80156a0..6a7d8ff8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,6 +22,7 @@ THE SOFTWARE. package cmd import ( + "context" "fmt" "io" "io/ioutil" @@ -34,9 +35,9 @@ import ( "github.com/rancher/k3d/v3/cmd/image" "github.com/rancher/k3d/v3/cmd/kubeconfig" "github.com/rancher/k3d/v3/cmd/node" + cliutil "github.com/rancher/k3d/v3/cmd/util" "github.com/rancher/k3d/v3/pkg/runtimes" "github.com/rancher/k3d/v3/version" - log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/writer" ) @@ -60,17 +61,6 @@ k3d is a wrapper CLI that helps you to easily create k3s clusters inside docker. Nodes of a k3d cluster are docker containers running a k3s image. All Nodes of a k3d cluster are part of the same docker network.`, Run: func(cmd *cobra.Command, args []string) { - if len(args) > 1 { - parts := args[1:] - - // Check if it's a built-in command, else try to execute it as a plugin - if _, _, err := cmd.Find(parts); err != nil { - if err := util.HandlePlugin(parts); err != nil { - log.Errorf("Failed to execute plugin '%+v'", parts) - log.Fatalln(err) - } - } - } if flags.version { printVersion() } else { @@ -84,6 +74,19 @@ All Nodes of a k3d cluster are part of the same docker network.`, // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { + if len(os.Args) > 1 { + parts := os.Args[1:] + // Check if it's a built-in command, else try to execute it as a plugin + if _, _, err := rootCmd.Find(parts); err != nil { + pluginFound, err := cliutil.HandlePlugin(context.Background(), parts) + if err != nil { + log.Errorf("Failed to execute plugin '%+v'", parts) + log.Fatalln(err) + } else if pluginFound { + os.Exit(0) + } + } + } if err := rootCmd.Execute(); err != nil { log.Fatalln(err) } diff --git a/cmd/util/plugins.go b/cmd/util/plugins.go index b32bab9c..ece487db 100644 --- a/cmd/util/plugins.go +++ b/cmd/util/plugins.go @@ -22,13 +22,17 @@ THE SOFTWARE. package util import ( + "context" "fmt" "os" "os/exec" "strings" + + k3d "github.com/rancher/k3d/v3/pkg/types" ) -func HandlePlugin(args []string) error { +// HandlePlugin takes care of finding and executing a plugin based on the longest prefix +func HandlePlugin(ctx context.Context, args []string) (bool, error) { argsPrefix := []string{} for _, arg := range args { @@ -53,10 +57,10 @@ func HandlePlugin(args []string) error { } if execPath == "" { - return nil + return false, nil } - return ExecPlugin(execPath, args[len(argsPrefix):], os.Environ()) + return true, ExecPlugin(ctx, execPath, args[len(argsPrefix):], os.Environ()) } @@ -70,7 +74,11 @@ func FindPlugin(name string) (string, bool) { } // ExecPlugin executes a found plugin -func ExecPlugin(path string, args []string, env []string) error { - - return nil +func ExecPlugin(ctx context.Context, path string, args []string, env []string) error { + cmd := exec.CommandContext(ctx, path, args...) + cmd.Env = env + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + return cmd.Run() } From cc222ef67a96d85540b5a4eccc260b57a2f7f3e4 Mon Sep 17 00:00:00 2001 From: Tadeu Andrade Date: Fri, 28 Aug 2020 11:00:35 -0300 Subject: [PATCH 3/3] (feature) Add fish shell completions --- cmd/root.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 8bf404de..3e155b03 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -157,12 +157,17 @@ func printVersion() { fmt.Printf("k3s version %s (default)\n", version.K3sVersion) } +func generateFishCompletion(writer io.Writer) error { + return rootCmd.GenFishCompletion(writer, true) +} + // Completion var completionFunctions = map[string]func(io.Writer) error{ "bash": rootCmd.GenBashCompletion, "zsh": rootCmd.GenZshCompletion, "psh": rootCmd.GenPowerShellCompletion, "powershell": rootCmd.GenPowerShellCompletion, + "fish": generateFishCompletion, } // NewCmdCompletion creates a new completion command @@ -170,8 +175,8 @@ func NewCmdCompletion() *cobra.Command { // create new cobra command cmd := &cobra.Command{ Use: "completion SHELL", - Short: "Generate completion scripts for [bash, zsh, powershell | psh]", - Long: `Generate completion scripts for [bash, zsh, powershell | psh]`, + Short: "Generate completion scripts for [bash, zsh, fish, powershell | psh]", + Long: `Generate completion scripts for [bash, zsh, fish, powershell | psh]`, Args: cobra.ExactArgs(1), // TODO: NewCmdCompletion: add support for 0 args = auto detection Run: func(cmd *cobra.Command, args []string) { if f, ok := completionFunctions[args[0]]; ok {