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  }