pkg/sdk/cmdutil/plugin.go (75 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 cmdutil import ( "fmt" "os" "runtime" "strings" "syscall" exec "golang.org/x/sys/execabs" ) // PluginHandler is capable of parsing command line arguments // and performing executable filename lookups to search // for valid plugin files, and execute found plugins. type PluginHandler interface { // exists at the given filename, or a boolean false. // Lookup will iterate over a list of given prefixes // in order to recognize valid plugin filenames. // The first filepath to match a prefix is returned. Lookup(filename string) (string, bool) // Execute receives an executable's filepath, a slice // of arguments, and a slice of environment variables // to relay to the executable. Execute(executablePath string, cmdArgs, environment []string) error } // 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 func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) { for _, prefix := range h.ValidPrefixes { path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) if err != nil || path == "" { continue } return path, true } return "", false } // Execute implements PluginHandler func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error { // Windows does not support exec syscall. if runtime.GOOS == "windows" { cmd := exec.Command(executablePath, cmdArgs...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin cmd.Env = environment err := cmd.Run() if err == nil { os.Exit(0) } if err != nil { return fmt.Errorf("unable to execute command: %w", err) } // No error return nil } // invoke cmd binary relaying the environment and args given // append executablePath to cmdArgs, as execve will make first argument the "binary name". //nolint:gosec // controlled input return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment) } // 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 for idx := range cmdArgs { if strings.HasPrefix(cmdArgs[idx], "-") { break } remainingArgs = append(remainingArgs, strings.ReplaceAll(cmdArgs[idx], "-", "_")) } foundBinaryPath := "" // attempt to find binary, starting at longest possible name with given cmdArgs for len(remainingArgs) > 0 { path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-")) if !found { remainingArgs = remainingArgs[:len(remainingArgs)-1] continue } foundBinaryPath = path break } if foundBinaryPath == "" { return nil } // invoke cmd binary relaying the current environment and args given if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil { return fmt.Errorf("unable to execute '%s' plugin: %w", foundBinaryPath, err) } return nil }