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