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