github.com/koderover/helm@v2.17.0+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 "k8s.io/helm/pkg/plugin" 17 18 import ( 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/ghodss/yaml" 26 yaml2 "gopkg.in/yaml.v2" 27 28 helm_env "k8s.io/helm/pkg/helm/environment" 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" yaml:"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" yaml:"command"` 41 } 42 43 // Metadata describes a plugin. 44 // 45 // This is the plugin equivalent of a chart.Metadata. 46 type Metadata struct { 47 // Name is the name of the plugin 48 Name string `json:"name" yaml:"name"` 49 50 // Version is a SemVer 2 version of the plugin. 51 Version string `json:"version" yaml:"version"` 52 53 // Usage is the single-line usage text shown in help 54 Usage string `json:"usage" yaml:"usage"` 55 56 // Description is a long description shown in places like `helm help` 57 Description string `json:"description" yaml:"description"` 58 59 // Command is the command, as a single string. 60 // 61 // The command will be passed through environment expansion, so env vars can 62 // be present in this command. Unless IgnoreFlags is set, this will 63 // also merge the flags passed from Helm. 64 // 65 // Note that command is not executed in a shell. To do so, we suggest 66 // pointing the command to a shell script. 67 Command string `json:"command" yaml:"command"` 68 69 // IgnoreFlags ignores any flags passed in from Helm 70 // 71 // For example, if the plugin is invoked as `helm --debug myplugin`, if this 72 // is false, `--debug` will be appended to `--command`. If this is true, 73 // the `--debug` flag will be discarded. 74 IgnoreFlags bool `json:"ignoreFlags" yaml:"ignoreFlags,omitempty"` 75 76 // UseTunnel indicates that this command needs a tunnel. 77 // Setting this will cause a number of side effects, such as the 78 // automatic setting of HELM_HOST. 79 UseTunnel bool `json:"useTunnel" yaml:"useTunnel,omitempty"` 80 81 // Hooks are commands that will run on events. 82 Hooks Hooks 83 84 // Downloaders field is used if the plugin supply downloader mechanism 85 // for special protocols. 86 Downloaders []Downloaders `json:"downloaders" yaml:"downloaders"` 87 } 88 89 // Plugin represents a plugin. 90 type Plugin struct { 91 // Metadata is a parsed representation of a plugin.yaml 92 Metadata *Metadata 93 // Dir is the string path to the directory that holds the plugin. 94 Dir string 95 } 96 97 // PrepareCommand takes a Plugin.Command and prepares it for execution. 98 // 99 // It merges extraArgs into any arguments supplied in the plugin. It 100 // returns the name of the command and an args array. 101 // 102 // The result is suitable to pass to exec.Command. 103 func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string) { 104 parts := strings.Split(os.ExpandEnv(p.Metadata.Command), " ") 105 main := parts[0] 106 baseArgs := []string{} 107 if len(parts) > 1 { 108 baseArgs = parts[1:] 109 } 110 if !p.Metadata.IgnoreFlags { 111 baseArgs = append(baseArgs, extraArgs...) 112 } 113 return main, baseArgs 114 } 115 116 // LoadDir loads a plugin from the given directory. 117 func LoadDir(dirname string) (*Plugin, error) { 118 data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName)) 119 if err != nil { 120 return nil, err 121 } 122 123 plug := &Plugin{Dir: dirname} 124 if err := validateMeta(data); err != nil { 125 return nil, err 126 } 127 if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { 128 return nil, err 129 } 130 return plug, nil 131 } 132 133 func validateMeta(data []byte) error { 134 // This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing. 135 return yaml2.UnmarshalStrict(data, &Metadata{}) 136 } 137 138 // LoadAll loads all plugins found beneath the base directory. 139 // 140 // This scans only one directory level. 141 func LoadAll(basedir string) ([]*Plugin, error) { 142 plugins := []*Plugin{} 143 // We want basedir/*/plugin.yaml 144 scanpath := filepath.Join(basedir, "*", pluginFileName) 145 matches, err := filepath.Glob(scanpath) 146 if err != nil { 147 return plugins, err 148 } 149 150 if matches == nil { 151 return plugins, nil 152 } 153 154 loaded := map[string]bool{} 155 for _, yaml := range matches { 156 dir := filepath.Dir(yaml) 157 p, err := LoadDir(dir) 158 pname := p.Metadata.Name 159 if err != nil { 160 return plugins, err 161 } 162 163 if _, ok := loaded[pname]; ok { 164 fmt.Fprintf(os.Stderr, "A plugin named %q already exists. Skipping.", pname) 165 continue 166 } 167 168 plugins = append(plugins, p) 169 loaded[pname] = true 170 } 171 return plugins, nil 172 } 173 174 // FindPlugins returns a list of YAML files that describe plugins. 175 func FindPlugins(plugdirs string) ([]*Plugin, error) { 176 found := []*Plugin{} 177 // Let's get all UNIXy and allow path separators 178 for _, p := range filepath.SplitList(plugdirs) { 179 matches, err := LoadAll(p) 180 if err != nil { 181 return matches, err 182 } 183 found = append(found, matches...) 184 } 185 return found, nil 186 } 187 188 // SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because 189 // the plugin subsystem itself needs access to the environment variables 190 // created here. 191 func SetupPluginEnv(settings helm_env.EnvSettings, 192 shortName, base string) { 193 for key, val := range map[string]string{ 194 "HELM_PLUGIN_NAME": shortName, 195 "HELM_PLUGIN_DIR": base, 196 "HELM_BIN": os.Args[0], 197 198 // Set vars that may not have been set, and save client the 199 // trouble of re-parsing. 200 "HELM_PLUGIN": settings.PluginDirs(), 201 "HELM_HOME": settings.Home.String(), 202 203 // Set vars that convey common information. 204 "HELM_PATH_REPOSITORY": settings.Home.Repository(), 205 "HELM_PATH_REPOSITORY_FILE": settings.Home.RepositoryFile(), 206 "HELM_PATH_CACHE": settings.Home.Cache(), 207 "HELM_PATH_LOCAL_REPOSITORY": settings.Home.LocalRepository(), 208 "HELM_PATH_STARTER": settings.Home.Starters(), 209 210 "TILLER_HOST": settings.TillerHost, 211 "TILLER_NAMESPACE": settings.TillerNamespace, 212 } { 213 os.Setenv(key, val) 214 } 215 216 if settings.Debug { 217 os.Setenv("HELM_DEBUG", "1") 218 } 219 }