github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/cmd/helm/load_plugins.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 main 17 18 import ( 19 "fmt" 20 "io" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "strings" 25 26 "github.com/spf13/cobra" 27 28 "k8s.io/helm/pkg/helm/helmpath" 29 "k8s.io/helm/pkg/plugin" 30 ) 31 32 const pluginEnvVar = "HELM_PLUGIN" 33 34 func pluginDirs(home helmpath.Home) string { 35 if dirs := os.Getenv(pluginEnvVar); dirs != "" { 36 return dirs 37 } 38 return home.Plugins() 39 } 40 41 // loadPlugins loads plugins into the command list. 42 // 43 // This follows a different pattern than the other commands because it has 44 // to inspect its environment and then add commands to the base command 45 // as it finds them. 46 func loadPlugins(baseCmd *cobra.Command, home helmpath.Home, out io.Writer) { 47 48 // If HELM_NO_PLUGINS is set to 1, do not load plugins. 49 if os.Getenv("HELM_NO_PLUGINS") == "1" { 50 return 51 } 52 53 plugdirs := pluginDirs(home) 54 found, err := findPlugins(plugdirs) 55 if err != nil { 56 fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) 57 return 58 } 59 60 // Now we create commands for all of these. 61 for _, plug := range found { 62 plug := plug 63 md := plug.Metadata 64 if md.Usage == "" { 65 md.Usage = fmt.Sprintf("the %q plugin", md.Name) 66 } 67 68 c := &cobra.Command{ 69 Use: md.Name, 70 Short: md.Usage, 71 Long: md.Description, 72 RunE: func(cmd *cobra.Command, args []string) error { 73 74 k, u := manuallyProcessArgs(args) 75 if err := cmd.Parent().ParseFlags(k); err != nil { 76 return err 77 } 78 79 // Call setupEnv before PrepareCommand because 80 // PrepareCommand uses os.ExpandEnv and expects the 81 // setupEnv vars. 82 setupEnv(md.Name, plug.Dir, plugdirs, home) 83 main, argv := plug.PrepareCommand(u) 84 85 prog := exec.Command(main, argv...) 86 prog.Env = os.Environ() 87 prog.Stdout = out 88 prog.Stderr = os.Stderr 89 if err := prog.Run(); err != nil { 90 if eerr, ok := err.(*exec.ExitError); ok { 91 os.Stderr.Write(eerr.Stderr) 92 return fmt.Errorf("plugin %q exited with error", md.Name) 93 } 94 return err 95 } 96 return nil 97 }, 98 // This passes all the flags to the subcommand. 99 DisableFlagParsing: true, 100 } 101 102 if md.UseTunnel { 103 c.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { 104 // Parse the parent flag, but not the local flags. 105 k, _ := manuallyProcessArgs(args) 106 if err := c.Parent().ParseFlags(k); err != nil { 107 return err 108 } 109 return setupConnection(cmd, args) 110 } 111 } 112 113 // TODO: Make sure a command with this name does not already exist. 114 baseCmd.AddCommand(c) 115 } 116 } 117 118 // manuallyProcessArgs processes an arg array, removing special args. 119 // 120 // Returns two sets of args: known and unknown (in that order) 121 func manuallyProcessArgs(args []string) ([]string, []string) { 122 known := []string{} 123 unknown := []string{} 124 kvargs := []string{"--host", "--kube-context", "--home", "--tiller-namespace"} 125 knownArg := func(a string) bool { 126 for _, pre := range kvargs { 127 if strings.HasPrefix(a, pre+"=") { 128 return true 129 } 130 } 131 return false 132 } 133 for i := 0; i < len(args); i++ { 134 switch a := args[i]; a { 135 case "--debug": 136 known = append(known, a) 137 case "--host", "--kube-context", "--home": 138 known = append(known, a, args[i+1]) 139 i++ 140 default: 141 if knownArg(a) { 142 known = append(known, a) 143 continue 144 } 145 unknown = append(unknown, a) 146 } 147 } 148 return known, unknown 149 } 150 151 // findPlugins returns a list of YAML files that describe plugins. 152 func findPlugins(plugdirs string) ([]*plugin.Plugin, error) { 153 found := []*plugin.Plugin{} 154 // Let's get all UNIXy and allow path separators 155 for _, p := range filepath.SplitList(plugdirs) { 156 matches, err := plugin.LoadAll(p) 157 if err != nil { 158 return matches, err 159 } 160 found = append(found, matches...) 161 } 162 return found, nil 163 } 164 165 // setupEnv prepares os.Env for plugins. It operates on os.Env because 166 // the plugin subsystem itself needs access to the environment variables 167 // created here. 168 func setupEnv(shortname, base, plugdirs string, home helmpath.Home) { 169 // Set extra env vars: 170 for key, val := range map[string]string{ 171 "HELM_PLUGIN_NAME": shortname, 172 "HELM_PLUGIN_DIR": base, 173 "HELM_BIN": os.Args[0], 174 175 // Set vars that may not have been set, and save client the 176 // trouble of re-parsing. 177 pluginEnvVar: plugdirs, 178 homeEnvVar: home.String(), 179 180 // Set vars that convey common information. 181 "HELM_PATH_REPOSITORY": home.Repository(), 182 "HELM_PATH_REPOSITORY_FILE": home.RepositoryFile(), 183 "HELM_PATH_CACHE": home.Cache(), 184 "HELM_PATH_LOCAL_REPOSITORY": home.LocalRepository(), 185 "HELM_PATH_STARTER": home.Starters(), 186 187 "TILLER_HOST": tillerHost, 188 tillerNamespaceEnvVar: tillerNamespace, 189 } { 190 os.Setenv(key, val) 191 } 192 193 if flagDebug { 194 os.Setenv("HELM_DEBUG", "1") 195 } 196 }