github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/cmdutil/plugin.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package cmdutil 19 20 import ( 21 "fmt" 22 "os" 23 "runtime" 24 "strings" 25 "syscall" 26 27 exec "golang.org/x/sys/execabs" 28 ) 29 30 // PluginHandler is capable of parsing command line arguments 31 // and performing executable filename lookups to search 32 // for valid plugin files, and execute found plugins. 33 type PluginHandler interface { 34 // exists at the given filename, or a boolean false. 35 // Lookup will iterate over a list of given prefixes 36 // in order to recognize valid plugin filenames. 37 // The first filepath to match a prefix is returned. 38 Lookup(filename string) (string, bool) 39 // Execute receives an executable's filepath, a slice 40 // of arguments, and a slice of environment variables 41 // to relay to the executable. 42 Execute(executablePath string, cmdArgs, environment []string) error 43 } 44 45 // DefaultPluginHandler implements PluginHandler. 46 type DefaultPluginHandler struct { 47 ValidPrefixes []string 48 } 49 50 // NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of 51 // given filename prefixes used to identify valid plugin filenames. 52 func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler { 53 return &DefaultPluginHandler{ 54 ValidPrefixes: validPrefixes, 55 } 56 } 57 58 // Lookup implements PluginHandler. 59 func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) { 60 for _, prefix := range h.ValidPrefixes { 61 path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) 62 if err != nil || path == "" { 63 continue 64 } 65 return path, true 66 } 67 68 return "", false 69 } 70 71 // Execute implements PluginHandler. 72 func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error { 73 // Windows does not support exec syscall. 74 if runtime.GOOS == "windows" { 75 cmd := exec.Command(executablePath, cmdArgs...) 76 cmd.Stdout = os.Stdout 77 cmd.Stderr = os.Stderr 78 cmd.Stdin = os.Stdin 79 cmd.Env = environment 80 err := cmd.Run() 81 if err == nil { 82 os.Exit(0) 83 } 84 if err != nil { 85 return fmt.Errorf("unable to execute command: %w", err) 86 } 87 88 // No error 89 return nil 90 } 91 92 // invoke cmd binary relaying the environment and args given 93 // append executablePath to cmdArgs, as execve will make first argument the "binary name". 94 //nolint:gosec // controlled input 95 return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment) 96 } 97 98 // HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find 99 // a plugin executable on the PATH that satisfies the given arguments. 100 func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error { 101 remainingArgs := []string{} // all "non-flag" arguments 102 103 for idx := range cmdArgs { 104 if strings.HasPrefix(cmdArgs[idx], "-") { 105 break 106 } 107 remainingArgs = append(remainingArgs, strings.ReplaceAll(cmdArgs[idx], "-", "_")) 108 } 109 110 foundBinaryPath := "" 111 112 // attempt to find binary, starting at longest possible name with given cmdArgs 113 for len(remainingArgs) > 0 { 114 path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-")) 115 if !found { 116 remainingArgs = remainingArgs[:len(remainingArgs)-1] 117 continue 118 } 119 120 foundBinaryPath = path 121 break 122 } 123 124 if foundBinaryPath == "" { 125 return nil 126 } 127 128 // invoke cmd binary relaying the current environment and args given 129 if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil { 130 return fmt.Errorf("unable to execute %q plugin: %w", foundBinaryPath, err) 131 } 132 133 return nil 134 }