github.com/Azure/draft-classic@v0.16.0/cmd/draft/plugin.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/Azure/draft/pkg/draft/draftpath"
    15  	"github.com/Azure/draft/pkg/plugin"
    16  )
    17  
    18  const (
    19  	pluginHelp   = `Manage client-side Draft plugins.`
    20  	pluginEnvVar = `DRAFT_PLUGIN`
    21  )
    22  
    23  func newPluginCmd(out io.Writer) *cobra.Command {
    24  	cmd := &cobra.Command{
    25  		Use:   "plugin",
    26  		Short: "add Draft plugins",
    27  		Long:  pluginHelp,
    28  	}
    29  	cmd.AddCommand(
    30  		newPluginInstallCmd(out),
    31  		newPluginListCmd(out),
    32  		newPluginRemoveCmd(out),
    33  		newPluginUpdateCmd(out),
    34  	)
    35  	return cmd
    36  }
    37  
    38  // findPlugins returns a list of YAML files that describe plugins.
    39  func findPlugins(plugdirs string) ([]*plugin.Plugin, error) {
    40  	found := []*plugin.Plugin{}
    41  	// Let's get all UNIXy and allow path separators
    42  	for _, p := range filepath.SplitList(plugdirs) {
    43  		matches, err := plugin.LoadAll(p)
    44  		if err != nil {
    45  			return matches, err
    46  		}
    47  		found = append(found, matches...)
    48  	}
    49  	return found, nil
    50  }
    51  
    52  func pluginDirPath(home draftpath.Home) string {
    53  	plugdirs := os.Getenv(pluginEnvVar)
    54  
    55  	if plugdirs == "" {
    56  		plugdirs = home.Plugins()
    57  	}
    58  
    59  	return plugdirs
    60  }
    61  
    62  // loadPlugins loads plugins into the command list.
    63  //
    64  // This follows a different pattern than the other commands because it has
    65  // to inspect its environment and then add commands to the base command
    66  // as it finds them.
    67  func loadPlugins(baseCmd *cobra.Command, home draftpath.Home, out io.Writer, in io.Reader) {
    68  	plugdirs := pluginDirPath(home)
    69  
    70  	found, err := findPlugins(plugdirs)
    71  	if err != nil {
    72  		fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
    73  		return
    74  	}
    75  
    76  	// Now we create commands for all of these.
    77  	for _, plug := range found {
    78  		var commandExists bool
    79  		for _, command := range baseCmd.Commands() {
    80  			if strings.Compare(command.Use, plug.Metadata.Usage) == 0 {
    81  				commandExists = true
    82  			}
    83  		}
    84  		if commandExists {
    85  			log.Debugf("command %s exists", plug.Metadata.Usage)
    86  			continue
    87  		}
    88  		plug := plug
    89  		md := plug.Metadata
    90  		if md.Usage == "" {
    91  			md.Usage = fmt.Sprintf("the %q plugin", md.Name)
    92  		}
    93  
    94  		c := &cobra.Command{
    95  			Use:   md.Name,
    96  			Short: md.Usage,
    97  			Long:  md.Description,
    98  			RunE: func(cmd *cobra.Command, args []string) error {
    99  
   100  				k, u := manuallyProcessArgs(args)
   101  				if err := cmd.Parent().ParseFlags(k); err != nil {
   102  					return err
   103  				}
   104  
   105  				// Call setupEnv before PrepareCommand because
   106  				// PrepareCommand uses os.ExpandEnv and expects the
   107  				// setupEnv vars.
   108  				setupPluginEnv(md.Name, plug.Metadata.Version, plug.Dir, plugdirs, draftpath.Home(homePath()))
   109  				main, argv := plug.PrepareCommand(u)
   110  
   111  				prog := exec.Command(main, argv...)
   112  				prog.Env = os.Environ()
   113  				prog.Stdout = out
   114  				prog.Stderr = os.Stderr
   115  				prog.Stdin = in
   116  				return prog.Run()
   117  			},
   118  			// This passes all the flags to the subcommand.
   119  			DisableFlagParsing: true,
   120  		}
   121  		if md.UseTunnel {
   122  			c.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
   123  				// Parse the parent flag, but not the local flags.
   124  				k, _ := manuallyProcessArgs(args)
   125  				if err := c.Parent().ParseFlags(k); err != nil {
   126  					return err
   127  				}
   128  				client, config, err := getKubeClient(kubeContext)
   129  				if err != nil {
   130  					return fmt.Errorf("Could not get a kube client: %s", err)
   131  				}
   132  
   133  				tillerTunnel, err := setupTillerConnection(client, config, tillerNamespace)
   134  				if err != nil {
   135  					return err
   136  				}
   137  				tillerHost = fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local)
   138  				return nil
   139  			}
   140  		}
   141  
   142  		baseCmd.AddCommand(c)
   143  	}
   144  }
   145  
   146  // manuallyProcessArgs processes an arg array, removing special args.
   147  //
   148  // Returns two sets of args: known and unknown (in that order)
   149  func manuallyProcessArgs(args []string) ([]string, []string) {
   150  	known := []string{}
   151  	unknown := []string{}
   152  	kvargs := []string{"--host", "--kube-context", "--home"}
   153  	knownArg := func(a string) bool {
   154  		for _, pre := range kvargs {
   155  			if strings.HasPrefix(a, pre+"=") {
   156  				return true
   157  			}
   158  		}
   159  		return false
   160  	}
   161  	for i := 0; i < len(args); i++ {
   162  		switch a := args[i]; a {
   163  		case "--debug":
   164  			known = append(known, a)
   165  		case "--host", "--kube-context", "--home":
   166  			known = append(known, a, args[i+1])
   167  			i++
   168  		default:
   169  			if knownArg(a) {
   170  				known = append(known, a)
   171  				continue
   172  			}
   173  			unknown = append(unknown, a)
   174  		}
   175  	}
   176  	return known, unknown
   177  }
   178  
   179  // setupPluginEnv prepares os.Env for plugins. It operates on os.Env because
   180  // the plugin subsystem itself needs access to the environment variables
   181  // created here.
   182  func setupPluginEnv(shortname, ver, base, plugdirs string, home draftpath.Home) {
   183  	// Set extra env vars:
   184  	for key, val := range map[string]string{
   185  		"DRAFT_PLUGIN_NAME":    shortname,
   186  		"DRAFT_PLUGIN_VERSION": ver,
   187  		"DRAFT_PLUGIN_DIR":     base,
   188  		"DRAFT_BIN":            os.Args[0],
   189  
   190  		// Set vars that may not have been set, and save client the
   191  		// trouble of re-parsing.
   192  		pluginEnvVar: pluginDirPath(home),
   193  		homeEnvVar:   home.String(),
   194  		hostEnvVar:   tillerHost,
   195  		// Set vars that convey common information.
   196  		"DRAFT_PACKS_HOME": home.Packs(),
   197  	} {
   198  		os.Setenv(key, val)
   199  	}
   200  
   201  	if flagDebug {
   202  		os.Setenv("DRAFT_DEBUG", "1")
   203  	}
   204  }