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 }