github.com/umeshredd/helm@v3.0.0-alpha.1+incompatible/pkg/plugin/plugin.go (about) 1 /* 2 Copyright The Helm Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package plugin // import "helm.sh/helm/pkg/plugin" 17 18 import ( 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "runtime" 24 "strings" 25 26 "github.com/ghodss/yaml" 27 28 helm_env "helm.sh/helm/pkg/cli" 29 ) 30 31 const pluginFileName = "plugin.yaml" 32 33 // Downloaders represents the plugins capability if it can retrieve 34 // charts from special sources 35 type Downloaders struct { 36 // Protocols are the list of schemes from the charts URL. 37 Protocols []string `json:"protocols"` 38 // Command is the executable path with which the plugin performs 39 // the actual download for the corresponding Protocols 40 Command string `json:"command"` 41 } 42 43 // PlatformCommand represents a command for a particular operating system and architecture 44 type PlatformCommand struct { 45 OperatingSystem string `json:"os"` 46 Architecture string `json:"arch"` 47 Command string `json:"command"` 48 } 49 50 // Metadata describes a plugin. 51 // 52 // This is the plugin equivalent of a chart.Metadata. 53 type Metadata struct { 54 // Name is the name of the plugin 55 Name string `json:"name"` 56 57 // Version is a SemVer 2 version of the plugin. 58 Version string `json:"version"` 59 60 // Usage is the single-line usage text shown in help 61 Usage string `json:"usage"` 62 63 // Description is a long description shown in places like `helm help` 64 Description string `json:"description"` 65 66 // Command is the command, as a single string. 67 // 68 // The command will be passed through environment expansion, so env vars can 69 // be present in this command. Unless IgnoreFlags is set, this will 70 // also merge the flags passed from Helm. 71 // 72 // Note that command is not executed in a shell. To do so, we suggest 73 // pointing the command to a shell script. 74 // 75 // The following rules will apply to processing commands: 76 // - If platformCommand is present, it will be searched first 77 // - If both OS and Arch match the current platform, search will stop and the command will be executed 78 // - If OS matches and there is no more specific match, the command will be executed 79 // - If no OS/Arch match is found, the default command will be executed 80 // - If no command is present and no matches are found in platformCommand, Helm will exit with an error 81 PlatformCommand []PlatformCommand `json:"platformCommand"` 82 Command string `json:"command"` 83 84 // IgnoreFlags ignores any flags passed in from Helm 85 // 86 // For example, if the plugin is invoked as `helm --debug myplugin`, if this 87 // is false, `--debug` will be appended to `--command`. If this is true, 88 // the `--debug` flag will be discarded. 89 IgnoreFlags bool `json:"ignoreFlags"` 90 91 // Hooks are commands that will run on events. 92 Hooks Hooks 93 94 // Downloaders field is used if the plugin supply downloader mechanism 95 // for special protocols. 96 Downloaders []Downloaders `json:"downloaders"` 97 } 98 99 // Plugin represents a plugin. 100 type Plugin struct { 101 // Metadata is a parsed representation of a plugin.yaml 102 Metadata *Metadata 103 // Dir is the string path to the directory that holds the plugin. 104 Dir string 105 } 106 107 // The following rules will apply to processing the Plugin.PlatformCommand.Command: 108 // - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution 109 // - If OS matches and there is no more specific match, the command will be prepared for execution 110 // - If no OS/Arch match is found, return nil 111 func getPlatformCommand(platformCommands []PlatformCommand) []string { 112 var command []string 113 for _, platformCommand := range platformCommands { 114 if strings.EqualFold(platformCommand.OperatingSystem, runtime.GOOS) { 115 command = strings.Split(os.ExpandEnv(platformCommand.Command), " ") 116 } 117 if strings.EqualFold(platformCommand.OperatingSystem, runtime.GOOS) && strings.EqualFold(platformCommand.Architecture, runtime.GOARCH) { 118 return strings.Split(os.ExpandEnv(platformCommand.Command), " ") 119 } 120 } 121 return command 122 } 123 124 // PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing: 125 // - If platformCommand is present, it will be searched first 126 // - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution 127 // - If OS matches and there is no more specific match, the command will be prepared for execution 128 // - If no OS/Arch match is found, the default command will be prepared for execution 129 // - If no command is present and no matches are found in platformCommand, will exit with an error 130 // 131 // It merges extraArgs into any arguments supplied in the plugin. It 132 // returns the name of the command and an args array. 133 // 134 // The result is suitable to pass to exec.Command. 135 func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { 136 var parts []string 137 platCmdLen := len(p.Metadata.PlatformCommand) 138 if platCmdLen > 0 { 139 parts = getPlatformCommand(p.Metadata.PlatformCommand) 140 } 141 if platCmdLen == 0 || parts == nil { 142 parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ") 143 } 144 if len(parts) == 0 || parts[0] == "" { 145 return "", nil, fmt.Errorf("No plugin command is applicable") 146 } 147 148 main := parts[0] 149 baseArgs := []string{} 150 if len(parts) > 1 { 151 baseArgs = parts[1:] 152 } 153 if !p.Metadata.IgnoreFlags { 154 baseArgs = append(baseArgs, extraArgs...) 155 } 156 return main, baseArgs, nil 157 } 158 159 // LoadDir loads a plugin from the given directory. 160 func LoadDir(dirname string) (*Plugin, error) { 161 data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName)) 162 if err != nil { 163 return nil, err 164 } 165 166 plug := &Plugin{Dir: dirname} 167 if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { 168 return nil, err 169 } 170 return plug, nil 171 } 172 173 // LoadAll loads all plugins found beneath the base directory. 174 // 175 // This scans only one directory level. 176 func LoadAll(basedir string) ([]*Plugin, error) { 177 plugins := []*Plugin{} 178 // We want basedir/*/plugin.yaml 179 scanpath := filepath.Join(basedir, "*", pluginFileName) 180 matches, err := filepath.Glob(scanpath) 181 if err != nil { 182 return plugins, err 183 } 184 185 if matches == nil { 186 return plugins, nil 187 } 188 189 for _, yaml := range matches { 190 dir := filepath.Dir(yaml) 191 p, err := LoadDir(dir) 192 if err != nil { 193 return plugins, err 194 } 195 plugins = append(plugins, p) 196 } 197 return plugins, nil 198 } 199 200 // FindPlugins returns a list of YAML files that describe plugins. 201 func FindPlugins(plugdirs string) ([]*Plugin, error) { 202 found := []*Plugin{} 203 // Let's get all UNIXy and allow path separators 204 for _, p := range filepath.SplitList(plugdirs) { 205 matches, err := LoadAll(p) 206 if err != nil { 207 return matches, err 208 } 209 found = append(found, matches...) 210 } 211 return found, nil 212 } 213 214 // SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because 215 // the plugin subsystem itself needs access to the environment variables 216 // created here. 217 func SetupPluginEnv(settings helm_env.EnvSettings, 218 shortName, base string) { 219 for key, val := range map[string]string{ 220 "HELM_PLUGIN_NAME": shortName, 221 "HELM_PLUGIN_DIR": base, 222 "HELM_BIN": os.Args[0], 223 224 // Set vars that may not have been set, and save client the 225 // trouble of re-parsing. 226 "HELM_PLUGIN": settings.PluginDirs(), 227 "HELM_HOME": settings.Home.String(), 228 229 // Set vars that convey common information. 230 "HELM_PATH_REPOSITORY": settings.Home.Repository(), 231 "HELM_PATH_REPOSITORY_FILE": settings.Home.RepositoryFile(), 232 "HELM_PATH_CACHE": settings.Home.Cache(), 233 "HELM_PATH_STARTER": settings.Home.Starters(), 234 } { 235 os.Setenv(key, val) 236 } 237 238 if settings.Debug { 239 os.Setenv("HELM_DEBUG", "1") 240 } 241 }