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  }