diff --git a/cmd/root.go b/cmd/root.go index 8bf404ded..6a7d8ff85 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" ) @@ -73,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 new file mode 100644 index 000000000..ece487db6 --- /dev/null +++ b/cmd/util/plugins.go @@ -0,0 +1,84 @@ +/* +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 ( + "context" + "fmt" + "os" + "os/exec" + "strings" + + k3d "github.com/rancher/k3d/v3/pkg/types" +) + +// 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 { + 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 false, nil + } + + return true, ExecPlugin(ctx, 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(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() +}