github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/plugin.go (about) 1 package main 2 3 import ( 4 "net/url" 5 "os" 6 "os/exec" 7 "strings" 8 ) 9 10 var ( 11 hkPath string 12 ) 13 14 var helpPlugins = &Command{ 15 Usage: "plugins", 16 Category: "hk", 17 Short: "interface to plugin commands", 18 Long: ` 19 Plugin commands extend hk's functionality. 20 21 Plugins are located in one of the directories in hk's search path, 22 HKPATH, and executed for unrecognized hk commands. If a plugin named 23 "default" exists, it will be run when no suitably-named plugin can 24 be found. (Run 'hk help environ' for details on HKPATH.) 25 26 The arguments to the plugin are the arguments to hk, not including 27 "hk" itself. 28 29 Several environment variables will also be set: 30 31 HEROKU_API_URL 32 33 This follows the same format as the variable read by hk. For a 34 plugin, this variable is always set and it always includes a 35 username and password. 36 37 (Run 'hk help environ' for details of the format.) 38 39 HKUSER 40 41 The username from HEROKU_API_URL, for convenience. 42 43 HKPASS 44 45 The password from HEROKU_API_URL, for convenience. 46 47 HKHOST 48 49 The hostname (and port, if any) from HEROKU_API_URL, for 50 convenience. 51 52 HKAPP 53 54 The name of the heroku app in the current directory, if there is a 55 git remote named "heroku" with the proper URL format. 56 57 HKVERSION 58 59 The version string of hk that executed the plugin. 60 61 HKPLUGINMODE 62 63 Either unset or it takes the value "info". If set to info, the 64 plugin should print out a summary of itself in the following 65 format: 66 67 name version: short help 68 69 long help 70 71 Where name is the plugin's file name, version is the plugin's 72 version string, short help is a one-line help message at most 50 chars, 73 and long help is a complete help text including usage line, prose 74 description, and list of options. Plugins are encouraged to follow the 75 example set by built-in hk commands for the style of this documentation. 76 `, 77 } 78 79 func init() { 80 const defaultPluginPath = "/usr/local/lib/hk/plugin" 81 hkPath = os.Getenv("HKPATH") 82 if hkPath == "" { 83 hkPath = defaultPluginPath 84 } 85 86 } 87 88 func execPlugin(path string, args []string) error { 89 u, err := url.Parse(apiURL) 90 if err != nil { 91 printFatal(err.Error()) 92 } 93 94 hkuser, hkpass := getCreds(apiURL) 95 u.User = url.UserPassword("", hkpass) 96 hkapp, _ := app() 97 env := []string{ 98 "HEROKU_API_URL=" + u.String(), 99 "HKAPP=" + hkapp, 100 "HKUSER=" + hkuser, 101 "HKPASS=" + hkpass, 102 "HKHOST=" + u.Host, 103 "HKVERSION=" + Version, 104 } 105 106 return sysExec(path, args, append(env, os.Environ()...)) 107 } 108 109 func findPlugin(name string) (path string) { 110 path = lookupPlugin(name) 111 if path == "" { 112 path = lookupPlugin("default") 113 } 114 return path 115 } 116 117 // NOTE: lookupPlugin is not threadsafe for anything needing the PATH env var. 118 func lookupPlugin(name string) string { 119 opath := os.Getenv("PATH") 120 defer os.Setenv("PATH", opath) 121 os.Setenv("PATH", hkPath) 122 123 path, err := exec.LookPath(name) 124 if err != nil { 125 if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { 126 return "" 127 } 128 printFatal(err.Error()) 129 } 130 return path 131 } 132 133 type plugin string 134 135 func (p plugin) Name() string { 136 return string(p) 137 } 138 139 func (p plugin) Short() string { 140 _, short, _ := pluginInfo(string(p)) 141 return short 142 } 143 144 func pluginInfo(name string) (ver, short, long string) { 145 if os.Getenv("HKPLUGINMODE") == "info" { 146 return "", "plugin exec loop", "plugin exec loop" 147 } 148 short = "no description" 149 long = name + ": unknown description" 150 var cmd exec.Cmd 151 cmd.Args = []string{name} 152 cmd.Path = lookupPlugin(name) 153 cmd.Env = append([]string{"HKPLUGINMODE=info"}, os.Environ()...) 154 buf, err := cmd.Output() 155 if err != nil { 156 return 157 } 158 info := string(buf) 159 if !strings.HasPrefix(info, name+" ") { 160 return 161 } 162 info = info[len(name)+1:] 163 i := strings.Index(info, ": ") 164 if i < 0 { 165 return 166 } 167 ver, info = info[:i], info[i+2:] 168 i = strings.Index(info, "\n\n") 169 if i < 0 || 50 < i || strings.Contains(info[:i], "\n") { 170 return 171 } 172 short, long = info[:i], info[i+2:] 173 return ver, strings.TrimSpace(short), strings.TrimSpace(long) 174 }