hookdeck-cli 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-npm.yml +19 -0
- package/.github/workflows/release.yml +85 -0
- package/.goreleaser/linux.yml +68 -0
- package/.goreleaser/mac.yml +69 -0
- package/.goreleaser/windows.yml +52 -0
- package/.tool-versions +1 -0
- package/Dockerfile +5 -0
- package/LICENSE +202 -0
- package/README.md +223 -0
- package/docs/cli-demo.gif +0 -0
- package/go.mod +58 -0
- package/go.sum +444 -0
- package/main.go +22 -0
- package/package.json +30 -0
- package/pkg/ansi/ansi.go +208 -0
- package/pkg/ansi/init_windows.go +23 -0
- package/pkg/cmd/completion.go +128 -0
- package/pkg/cmd/listen.go +114 -0
- package/pkg/cmd/login.go +37 -0
- package/pkg/cmd/logout.go +35 -0
- package/pkg/cmd/root.go +112 -0
- package/pkg/cmd/version.go +25 -0
- package/pkg/cmd/whoami.go +50 -0
- package/pkg/config/config.go +326 -0
- package/pkg/config/config_test.go +20 -0
- package/pkg/config/profile.go +296 -0
- package/pkg/config/profile_test.go +109 -0
- package/pkg/hookdeck/client.go +210 -0
- package/pkg/hookdeck/client_test.go +203 -0
- package/pkg/hookdeck/connections.go +61 -0
- package/pkg/hookdeck/destinations.go +14 -0
- package/pkg/hookdeck/guest.go +37 -0
- package/pkg/hookdeck/session.go +37 -0
- package/pkg/hookdeck/sources.go +73 -0
- package/pkg/hookdeck/telemetry.go +84 -0
- package/pkg/hookdeck/telemetry_test.go +35 -0
- package/pkg/hookdeck/verbosetransport.go +82 -0
- package/pkg/hookdeck/verbosetransport_test.go +47 -0
- package/pkg/listen/connection.go +91 -0
- package/pkg/listen/listen.go +140 -0
- package/pkg/listen/source.go +84 -0
- package/pkg/login/client_login.go +209 -0
- package/pkg/login/interactive_login.go +180 -0
- package/pkg/login/login_message.go +24 -0
- package/pkg/login/poll.go +78 -0
- package/pkg/login/validate.go +52 -0
- package/pkg/logout/logout.go +48 -0
- package/pkg/open/open.go +50 -0
- package/pkg/proxy/proxy.go +433 -0
- package/pkg/useragent/uname_unix.go +25 -0
- package/pkg/useragent/uname_unix_test.go +24 -0
- package/pkg/useragent/uname_windows.go +9 -0
- package/pkg/useragent/useragent.go +74 -0
- package/pkg/validators/cmds.go +71 -0
- package/pkg/validators/validate.go +144 -0
- package/pkg/version/version.go +58 -0
- package/pkg/version/version_test.go +15 -0
- package/pkg/websocket/attempt_messages.go +47 -0
- package/pkg/websocket/client.go +525 -0
- package/pkg/websocket/connection_messages.go +11 -0
- package/pkg/websocket/messages.go +64 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
7
|
+
"github.com/hookdeck/hookdeck-cli/pkg/ansi"
|
|
8
|
+
"github.com/hookdeck/hookdeck-cli/pkg/login"
|
|
9
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type whoamiCmd struct {
|
|
14
|
+
cmd *cobra.Command
|
|
15
|
+
interactive bool
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func newWhoamiCmd() *whoamiCmd {
|
|
19
|
+
lc := &whoamiCmd{}
|
|
20
|
+
|
|
21
|
+
lc.cmd = &cobra.Command{
|
|
22
|
+
Use: "whoami",
|
|
23
|
+
Args: validators.NoArgs,
|
|
24
|
+
Short: "Show the logged-in user",
|
|
25
|
+
RunE: lc.runWhoamiCmd,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return lc
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func (lc *whoamiCmd) runWhoamiCmd(cmd *cobra.Command, args []string) error {
|
|
32
|
+
key, err := Config.Profile.GetAPIKey()
|
|
33
|
+
if err != nil {
|
|
34
|
+
return err
|
|
35
|
+
}
|
|
36
|
+
response, err := login.ValidateKey(Config.APIBaseURL, key)
|
|
37
|
+
if err != nil {
|
|
38
|
+
return err
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
color := ansi.Color(os.Stdout)
|
|
42
|
+
|
|
43
|
+
fmt.Printf(
|
|
44
|
+
"Logged in as %s in workspace %s\n",
|
|
45
|
+
color.Bold(response.UserName),
|
|
46
|
+
color.Bold(response.TeamName),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io/ioutil"
|
|
7
|
+
"os"
|
|
8
|
+
"os/exec"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"runtime"
|
|
11
|
+
"strings"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/BurntSushi/toml"
|
|
15
|
+
"github.com/mitchellh/go-homedir"
|
|
16
|
+
log "github.com/sirupsen/logrus"
|
|
17
|
+
"github.com/spf13/viper"
|
|
18
|
+
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
|
19
|
+
|
|
20
|
+
"github.com/hookdeck/hookdeck-cli/pkg/ansi"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// ColorOn represnets the on-state for colors
|
|
24
|
+
const ColorOn = "on"
|
|
25
|
+
|
|
26
|
+
// ColorOff represents the off-state for colors
|
|
27
|
+
const ColorOff = "off"
|
|
28
|
+
|
|
29
|
+
// ColorAuto represents the auto-state for colors
|
|
30
|
+
const ColorAuto = "auto"
|
|
31
|
+
|
|
32
|
+
// Config handles all overall configuration for the CLI
|
|
33
|
+
type Config struct {
|
|
34
|
+
Color string
|
|
35
|
+
LogLevel string
|
|
36
|
+
Profile Profile
|
|
37
|
+
ProfilesFile string
|
|
38
|
+
APIBaseURL string
|
|
39
|
+
DashboardBaseURL string
|
|
40
|
+
ConsoleBaseURL string
|
|
41
|
+
Insecure bool
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// GetConfigFolder retrieves the folder where the profiles file is stored
|
|
45
|
+
// It searches for the xdg environment path first and will secondarily
|
|
46
|
+
// place it in the home directory
|
|
47
|
+
func (c *Config) GetConfigFolder(xdgPath string) string {
|
|
48
|
+
configPath := xdgPath
|
|
49
|
+
|
|
50
|
+
log.WithFields(log.Fields{
|
|
51
|
+
"prefix": "config.Config.GetProfilesFolder",
|
|
52
|
+
"path": configPath,
|
|
53
|
+
}).Debug("Using profiles file")
|
|
54
|
+
|
|
55
|
+
if configPath == "" {
|
|
56
|
+
home, err := homedir.Dir()
|
|
57
|
+
if err != nil {
|
|
58
|
+
fmt.Println(err)
|
|
59
|
+
os.Exit(1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
configPath = filepath.Join(home, ".config")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return filepath.Join(configPath, "hookdeck")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// InitConfig reads in profiles file and ENV variables if set.
|
|
69
|
+
func (c *Config) InitConfig() {
|
|
70
|
+
logFormatter := &prefixed.TextFormatter{
|
|
71
|
+
FullTimestamp: true,
|
|
72
|
+
TimestampFormat: time.RFC1123,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
c.Profile.Config = c
|
|
76
|
+
|
|
77
|
+
if c.ProfilesFile != "" {
|
|
78
|
+
viper.SetConfigFile(c.ProfilesFile)
|
|
79
|
+
} else {
|
|
80
|
+
configFolder := c.GetConfigFolder(os.Getenv("XDG_CONFIG_HOME"))
|
|
81
|
+
configFile := filepath.Join(configFolder, "config.toml")
|
|
82
|
+
c.ProfilesFile = configFile
|
|
83
|
+
viper.SetConfigType("toml")
|
|
84
|
+
viper.SetConfigFile(configFile)
|
|
85
|
+
viper.SetConfigPermissions(os.FileMode(0600))
|
|
86
|
+
|
|
87
|
+
// Try to change permissions manually, because we used to create files
|
|
88
|
+
// with default permissions (0644)
|
|
89
|
+
err := os.Chmod(configFile, os.FileMode(0600))
|
|
90
|
+
if err != nil && !os.IsNotExist(err) {
|
|
91
|
+
log.Fatalf("%s", err)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If a profiles file is found, read it in.
|
|
96
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
97
|
+
log.WithFields(log.Fields{
|
|
98
|
+
"prefix": "config.Config.InitConfig",
|
|
99
|
+
"path": viper.ConfigFileUsed(),
|
|
100
|
+
}).Debug("Using profiles file")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if c.Profile.DeviceName == "" {
|
|
104
|
+
deviceName, err := os.Hostname()
|
|
105
|
+
if err != nil {
|
|
106
|
+
deviceName = "unknown"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
c.Profile.DeviceName = deviceName
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
color, err := c.Profile.GetColor()
|
|
113
|
+
if err != nil {
|
|
114
|
+
log.Fatalf("%s", err)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
switch color {
|
|
118
|
+
case ColorOn:
|
|
119
|
+
ansi.ForceColors = true
|
|
120
|
+
logFormatter.ForceColors = true
|
|
121
|
+
case ColorOff:
|
|
122
|
+
ansi.DisableColors = true
|
|
123
|
+
logFormatter.DisableColors = true
|
|
124
|
+
case ColorAuto:
|
|
125
|
+
// Nothing to do
|
|
126
|
+
default:
|
|
127
|
+
log.Fatalf("Unrecognized color value: %s. Expected one of on, off, auto.", c.Color)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
log.SetFormatter(logFormatter)
|
|
131
|
+
|
|
132
|
+
// Set log level
|
|
133
|
+
switch c.LogLevel {
|
|
134
|
+
case "debug":
|
|
135
|
+
log.SetLevel(log.DebugLevel)
|
|
136
|
+
case "info":
|
|
137
|
+
log.SetLevel(log.InfoLevel)
|
|
138
|
+
case "warn":
|
|
139
|
+
log.SetLevel(log.WarnLevel)
|
|
140
|
+
case "error":
|
|
141
|
+
log.SetLevel(log.ErrorLevel)
|
|
142
|
+
default:
|
|
143
|
+
log.Fatalf("Unrecognized log level value: %s. Expected one of debug, info, warn, error.", c.LogLevel)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// EditConfig opens the configuration file in the default editor.
|
|
148
|
+
func (c *Config) EditConfig() error {
|
|
149
|
+
var err error
|
|
150
|
+
|
|
151
|
+
fmt.Println("Opening config file:", c.ProfilesFile)
|
|
152
|
+
|
|
153
|
+
switch runtime.GOOS {
|
|
154
|
+
case "darwin", "linux":
|
|
155
|
+
editor := os.Getenv("EDITOR")
|
|
156
|
+
if editor == "" {
|
|
157
|
+
editor = "vi"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
cmd := exec.Command(editor, c.ProfilesFile)
|
|
161
|
+
// Some editors detect whether they have control of stdin/out and will
|
|
162
|
+
// fail if they do not.
|
|
163
|
+
cmd.Stdin = os.Stdin
|
|
164
|
+
cmd.Stdout = os.Stdout
|
|
165
|
+
|
|
166
|
+
return cmd.Run()
|
|
167
|
+
case "windows":
|
|
168
|
+
// As far as I can tell, Windows doesn't have an easily accesible or
|
|
169
|
+
// comparable option to $EDITOR, so default to notepad for now
|
|
170
|
+
err = exec.Command("notepad", c.ProfilesFile).Run()
|
|
171
|
+
default:
|
|
172
|
+
err = fmt.Errorf("unsupported platform")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return err
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// PrintConfig outputs the contents of the configuration file.
|
|
179
|
+
func (c *Config) PrintConfig() error {
|
|
180
|
+
if c.Profile.ProfileName == "default" {
|
|
181
|
+
configFile, err := ioutil.ReadFile(c.ProfilesFile)
|
|
182
|
+
if err != nil {
|
|
183
|
+
return err
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fmt.Print(string(configFile))
|
|
187
|
+
} else {
|
|
188
|
+
configs := viper.GetStringMapString(c.Profile.ProfileName)
|
|
189
|
+
|
|
190
|
+
if len(configs) > 0 {
|
|
191
|
+
fmt.Printf("[%s]\n", c.Profile.ProfileName)
|
|
192
|
+
for field, value := range configs {
|
|
193
|
+
fmt.Printf(" %s=%s\n", field, value)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return nil
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// RemoveProfile removes the profile whose name matches the provided
|
|
202
|
+
// profileName from the config file.
|
|
203
|
+
func (c *Config) RemoveProfile(profileName string) error {
|
|
204
|
+
runtimeViper := viper.GetViper()
|
|
205
|
+
var err error
|
|
206
|
+
|
|
207
|
+
for field, value := range runtimeViper.AllSettings() {
|
|
208
|
+
if isProfile(value) && field == profileName {
|
|
209
|
+
runtimeViper, err = removeKey(runtimeViper, field)
|
|
210
|
+
if err != nil {
|
|
211
|
+
return err
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return syncConfig(runtimeViper)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// RemoveAllProfiles removes all the profiles from the config file.
|
|
220
|
+
func (c *Config) RemoveAllProfiles() error {
|
|
221
|
+
runtimeViper := viper.GetViper()
|
|
222
|
+
var err error
|
|
223
|
+
|
|
224
|
+
for field, value := range runtimeViper.AllSettings() {
|
|
225
|
+
if isProfile(value) {
|
|
226
|
+
runtimeViper, err = removeKey(runtimeViper, field)
|
|
227
|
+
if err != nil {
|
|
228
|
+
return err
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return syncConfig(runtimeViper)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// isProfile identifies whether a value in the config pertains to a profile.
|
|
237
|
+
func isProfile(value interface{}) bool {
|
|
238
|
+
// TODO: ianjabour - ideally find a better way to identify projects in config
|
|
239
|
+
_, ok := value.(map[string]interface{})
|
|
240
|
+
return ok
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// syncConfig merges a runtimeViper instance with the config file being used.
|
|
244
|
+
func syncConfig(runtimeViper *viper.Viper) error {
|
|
245
|
+
runtimeViper.MergeInConfig()
|
|
246
|
+
profilesFile := viper.ConfigFileUsed()
|
|
247
|
+
runtimeViper.SetConfigFile(profilesFile)
|
|
248
|
+
// Ensure we preserve the config file type
|
|
249
|
+
runtimeViper.SetConfigType(filepath.Ext(profilesFile))
|
|
250
|
+
|
|
251
|
+
err := runtimeViper.WriteConfig()
|
|
252
|
+
if err != nil {
|
|
253
|
+
return err
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return nil
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Temporary workaround until https://github.com/spf13/viper/pull/519 can remove a key from viper
|
|
260
|
+
func removeKey(v *viper.Viper, key string) (*viper.Viper, error) {
|
|
261
|
+
configMap := v.AllSettings()
|
|
262
|
+
path := strings.Split(key, ".")
|
|
263
|
+
lastKey := strings.ToLower(path[len(path)-1])
|
|
264
|
+
deepestMap := deepSearch(configMap, path[0:len(path)-1])
|
|
265
|
+
delete(deepestMap, lastKey)
|
|
266
|
+
|
|
267
|
+
buf := new(bytes.Buffer)
|
|
268
|
+
|
|
269
|
+
encodeErr := toml.NewEncoder(buf).Encode(configMap)
|
|
270
|
+
if encodeErr != nil {
|
|
271
|
+
return nil, encodeErr
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
nv := viper.New()
|
|
275
|
+
nv.SetConfigType("toml") // hint to viper that we've encoded the data as toml
|
|
276
|
+
|
|
277
|
+
err := nv.ReadConfig(buf)
|
|
278
|
+
if err != nil {
|
|
279
|
+
return nil, err
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return nv, nil
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func makePath(path string) error {
|
|
286
|
+
dir := filepath.Dir(path)
|
|
287
|
+
|
|
288
|
+
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
289
|
+
err = os.MkdirAll(dir, os.ModePerm)
|
|
290
|
+
if err != nil {
|
|
291
|
+
return err
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return nil
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// taken from https://github.com/spf13/viper/blob/master/util.go#L199,
|
|
299
|
+
// we need this to delete configs, remove when viper supprts unset natively
|
|
300
|
+
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
|
301
|
+
for _, k := range path {
|
|
302
|
+
m2, ok := m[k]
|
|
303
|
+
if !ok {
|
|
304
|
+
// intermediate key does not exist
|
|
305
|
+
// => create it and continue from there
|
|
306
|
+
m3 := make(map[string]interface{})
|
|
307
|
+
m[k] = m3
|
|
308
|
+
m = m3
|
|
309
|
+
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
m3, ok := m2.(map[string]interface{})
|
|
314
|
+
if !ok {
|
|
315
|
+
// intermediate key is a value
|
|
316
|
+
// => replace with a new map
|
|
317
|
+
m3 = make(map[string]interface{})
|
|
318
|
+
m[k] = m3
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// continue search from here
|
|
322
|
+
m = m3
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return m
|
|
326
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/spf13/viper"
|
|
7
|
+
"github.com/stretchr/testify/require"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestRemoveKey(t *testing.T) {
|
|
11
|
+
v := viper.New()
|
|
12
|
+
v.Set("remove", "me")
|
|
13
|
+
v.Set("stay", "here")
|
|
14
|
+
|
|
15
|
+
nv, err := removeKey(v, "remove")
|
|
16
|
+
require.NoError(t, err)
|
|
17
|
+
|
|
18
|
+
require.EqualValues(t, []string{"stay"}, nv.AllKeys())
|
|
19
|
+
require.ElementsMatch(t, []string{"stay", "remove"}, v.AllKeys())
|
|
20
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io/ioutil"
|
|
8
|
+
"net/url"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strings"
|
|
12
|
+
|
|
13
|
+
"github.com/spf13/viper"
|
|
14
|
+
|
|
15
|
+
"github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
|
|
16
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// Profile handles all things related to managing the project specific configurations
|
|
20
|
+
type Profile struct {
|
|
21
|
+
Config *Config
|
|
22
|
+
DeviceName string
|
|
23
|
+
ProfileName string
|
|
24
|
+
TeamName string
|
|
25
|
+
TeamID string
|
|
26
|
+
TeamMode string
|
|
27
|
+
APIKey string
|
|
28
|
+
ClientID string
|
|
29
|
+
DisplayName string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type partialPollResponse struct {
|
|
33
|
+
TeamName string `json:"team_name"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// CreateProfile creates a profile when logging in
|
|
37
|
+
func (p *Profile) CreateProfile() error {
|
|
38
|
+
writeErr := p.writeProfile(viper.GetViper())
|
|
39
|
+
if writeErr != nil {
|
|
40
|
+
return writeErr
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// GetColor gets the color setting for the user based on the flag or the
|
|
47
|
+
// persisted color stored in the config file
|
|
48
|
+
func (p *Profile) GetColor() (string, error) {
|
|
49
|
+
color := viper.GetString("color")
|
|
50
|
+
if color != "" {
|
|
51
|
+
return color, nil
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
color = viper.GetString(p.GetConfigField("color"))
|
|
55
|
+
switch color {
|
|
56
|
+
case "", ColorAuto:
|
|
57
|
+
return ColorAuto, nil
|
|
58
|
+
case ColorOn:
|
|
59
|
+
return ColorOn, nil
|
|
60
|
+
case ColorOff:
|
|
61
|
+
return ColorOff, nil
|
|
62
|
+
default:
|
|
63
|
+
return "", fmt.Errorf("color value not supported: %s", color)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// GetDeviceName returns the configured device name
|
|
68
|
+
func (p *Profile) GetDeviceName() (string, error) {
|
|
69
|
+
if os.Getenv("HOOKDECK_DEVICE_NAME") != "" {
|
|
70
|
+
return os.Getenv("HOOKDECK_DEVICE_NAME"), nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if p.DeviceName != "" {
|
|
74
|
+
return p.DeviceName, nil
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
78
|
+
return viper.GetString(p.GetConfigField("device_name")), nil
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return "", validators.ErrDeviceNameNotConfigured
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// GetAPIKey will return the existing key for the given profile
|
|
85
|
+
func (p *Profile) GetAPIKey() (string, error) {
|
|
86
|
+
envKey := os.Getenv("HOOKDECK_CLI_KEY")
|
|
87
|
+
if envKey != "" {
|
|
88
|
+
err := validators.APIKey(envKey)
|
|
89
|
+
if err != nil {
|
|
90
|
+
return "", err
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return envKey, nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if p.APIKey != "" {
|
|
97
|
+
err := validators.APIKey(p.APIKey)
|
|
98
|
+
if err != nil {
|
|
99
|
+
return "", err
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return p.APIKey, nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Try to fetch the API key from the configuration file
|
|
106
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
107
|
+
key := viper.GetString(p.GetConfigField("api_key"))
|
|
108
|
+
|
|
109
|
+
err := validators.APIKey(key)
|
|
110
|
+
if err != nil {
|
|
111
|
+
return "", err
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return key, nil
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return "", validators.ErrAPIKeyNotConfigured
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// GetDisplayName returns the account display name of the user
|
|
121
|
+
func (p *Profile) GetDisplayName() string {
|
|
122
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
123
|
+
return viper.GetString(p.GetConfigField("display_name"))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return ""
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func (p *Profile) GetTeamMode() string {
|
|
130
|
+
if p.TeamMode != "" {
|
|
131
|
+
return p.TeamMode
|
|
132
|
+
}
|
|
133
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
134
|
+
return viper.GetString(p.GetConfigField("team_mode"))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return ""
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (p *Profile) GetTeamId() string {
|
|
141
|
+
if p.TeamID != "" {
|
|
142
|
+
return p.TeamID
|
|
143
|
+
}
|
|
144
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
145
|
+
return viper.GetString(p.GetConfigField("team_id"))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return ""
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func (p *Profile) refreshTeamName() string {
|
|
152
|
+
apiKey, err := p.GetAPIKey()
|
|
153
|
+
if err != nil {
|
|
154
|
+
panic(err)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
parsedURL, err := url.Parse(p.Config.APIBaseURL + "/cli-auth/poll?key=" + apiKey)
|
|
158
|
+
if err != nil {
|
|
159
|
+
panic(err)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
client := &hookdeck.Client{
|
|
163
|
+
BaseURL: parsedURL,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
res, err := client.Get(context.TODO(), parsedURL.Path, parsedURL.Query().Encode(), nil)
|
|
167
|
+
if err != nil {
|
|
168
|
+
panic(err)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
defer res.Body.Close()
|
|
172
|
+
|
|
173
|
+
body, err := ioutil.ReadAll(res.Body)
|
|
174
|
+
if err != nil {
|
|
175
|
+
panic(err)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
var response partialPollResponse
|
|
179
|
+
|
|
180
|
+
err = json.Unmarshal(body, &response)
|
|
181
|
+
if err != nil {
|
|
182
|
+
panic(err)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
p.TeamName = response.TeamName
|
|
186
|
+
p.CreateProfile()
|
|
187
|
+
|
|
188
|
+
return response.TeamName
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// GetDisplayName returns the account display name of the team
|
|
192
|
+
func (p *Profile) GetTeamName() string {
|
|
193
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
194
|
+
return ""
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
teamName := viper.GetString(p.GetConfigField("team_name"))
|
|
198
|
+
|
|
199
|
+
if teamName == p.GetDisplayName() {
|
|
200
|
+
// Could be a bug where we used to store the display name in the
|
|
201
|
+
// team name field. But it could also be a legit team name that
|
|
202
|
+
// just happens to be the same as the user display name.
|
|
203
|
+
//
|
|
204
|
+
// Call the CLI poll endpoint to make sure.
|
|
205
|
+
teamName = p.refreshTeamName()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return teamName
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// GetTerminalPOSDeviceID returns the device id from the config for Terminal quickstart to use
|
|
212
|
+
func (p *Profile) GetTerminalPOSDeviceID() string {
|
|
213
|
+
if err := viper.ReadInConfig(); err == nil {
|
|
214
|
+
return viper.GetString(p.GetConfigField("terminal_pos_device_id"))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return ""
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// GetConfigField returns the configuration field for the specific profile
|
|
221
|
+
func (p *Profile) GetConfigField(field string) string {
|
|
222
|
+
return p.ProfileName + "." + field
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// RegisterAlias registers an alias for a given key.
|
|
226
|
+
func (p *Profile) RegisterAlias(alias, key string) {
|
|
227
|
+
viper.RegisterAlias(p.GetConfigField(alias), p.GetConfigField(key))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// WriteConfigField updates a configuration field and writes the updated
|
|
231
|
+
// configuration to disk.
|
|
232
|
+
func (p *Profile) WriteConfigField(field, value string) error {
|
|
233
|
+
viper.Set(p.GetConfigField(field), value)
|
|
234
|
+
return viper.WriteConfig()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// DeleteConfigField deletes a configuration field.
|
|
238
|
+
func (p *Profile) DeleteConfigField(field string) error {
|
|
239
|
+
v, err := removeKey(viper.GetViper(), p.GetConfigField(field))
|
|
240
|
+
if err != nil {
|
|
241
|
+
return err
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return p.writeProfile(v)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
func (p *Profile) writeProfile(runtimeViper *viper.Viper) error {
|
|
248
|
+
profilesFile := viper.ConfigFileUsed()
|
|
249
|
+
|
|
250
|
+
err := makePath(profilesFile)
|
|
251
|
+
if err != nil {
|
|
252
|
+
return err
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if p.DeviceName != "" {
|
|
256
|
+
runtimeViper.Set(p.GetConfigField("device_name"), strings.TrimSpace(p.DeviceName))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if p.APIKey != "" {
|
|
260
|
+
runtimeViper.Set(p.GetConfigField("api_key"), strings.TrimSpace(p.APIKey))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if p.ClientID != "" {
|
|
264
|
+
runtimeViper.Set(p.GetConfigField("client_id"), strings.TrimSpace(p.ClientID))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if p.DisplayName != "" {
|
|
268
|
+
runtimeViper.Set(p.GetConfigField("display_name"), strings.TrimSpace(p.DisplayName))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if p.TeamName != "" {
|
|
272
|
+
runtimeViper.Set(p.GetConfigField("team_name"), strings.TrimSpace(p.TeamName))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if p.TeamID != "" {
|
|
276
|
+
runtimeViper.Set(p.GetConfigField("team_id"), strings.TrimSpace(p.TeamID))
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if p.TeamMode != "" {
|
|
280
|
+
runtimeViper.Set(p.GetConfigField("team_mode"), strings.TrimSpace(p.TeamMode))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
runtimeViper.MergeInConfig()
|
|
284
|
+
|
|
285
|
+
runtimeViper.SetConfigFile(profilesFile)
|
|
286
|
+
|
|
287
|
+
// Ensure we preserve the config file type
|
|
288
|
+
runtimeViper.SetConfigType(filepath.Ext(profilesFile))
|
|
289
|
+
|
|
290
|
+
err = runtimeViper.WriteConfig()
|
|
291
|
+
if err != nil {
|
|
292
|
+
return err
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return nil
|
|
296
|
+
}
|