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
package/pkg/ansi/ansi.go
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
package ansi
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"os"
|
|
7
|
+
"runtime"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/briandowns/spinner"
|
|
11
|
+
"github.com/logrusorgru/aurora"
|
|
12
|
+
"github.com/tidwall/pretty"
|
|
13
|
+
"golang.org/x/term"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
var darkTerminalStyle = &pretty.Style{
|
|
17
|
+
Key: [2]string{"\x1B[34m", "\x1B[0m"},
|
|
18
|
+
String: [2]string{"\x1B[30m", "\x1B[0m"},
|
|
19
|
+
Number: [2]string{"\x1B[94m", "\x1B[0m"},
|
|
20
|
+
True: [2]string{"\x1B[35m", "\x1B[0m"},
|
|
21
|
+
False: [2]string{"\x1B[35m", "\x1B[0m"},
|
|
22
|
+
Null: [2]string{"\x1B[31m", "\x1B[0m"},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//
|
|
26
|
+
// Public variables
|
|
27
|
+
//
|
|
28
|
+
|
|
29
|
+
// ForceColors forces the use of colors and other ANSI sequences.
|
|
30
|
+
var ForceColors = false
|
|
31
|
+
|
|
32
|
+
// DisableColors disables all colors and other ANSI sequences.
|
|
33
|
+
var DisableColors = false
|
|
34
|
+
|
|
35
|
+
// EnvironmentOverrideColors overs coloring based on `CLICOLOR` and
|
|
36
|
+
// `CLICOLOR_FORCE`. Cf. https://bixense.com/clicolors/
|
|
37
|
+
var EnvironmentOverrideColors = true
|
|
38
|
+
|
|
39
|
+
//
|
|
40
|
+
// Public functions
|
|
41
|
+
//
|
|
42
|
+
|
|
43
|
+
// Bold returns bolded text if the writer supports colors
|
|
44
|
+
func Bold(text string) string {
|
|
45
|
+
color := Color(os.Stdout)
|
|
46
|
+
return color.Sprintf(color.Bold(text))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Color returns an aurora.Aurora instance with colors enabled or disabled
|
|
50
|
+
// depending on whether the writer supports colors.
|
|
51
|
+
func Color(w io.Writer) aurora.Aurora {
|
|
52
|
+
return aurora.NewAurora(shouldUseColors(w))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ColorizeJSON returns a colorized version of the input JSON, if the writer
|
|
56
|
+
// supports colors.
|
|
57
|
+
func ColorizeJSON(json string, darkStyle bool, w io.Writer) string {
|
|
58
|
+
if !shouldUseColors(w) {
|
|
59
|
+
return json
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
style := (*pretty.Style)(nil)
|
|
63
|
+
if darkStyle {
|
|
64
|
+
style = darkTerminalStyle
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return string(pretty.Color([]byte(json), style))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ColorizeStatus returns a colorized number for HTTP status code
|
|
71
|
+
func ColorizeStatus(status int) aurora.Value {
|
|
72
|
+
color := Color(os.Stdout)
|
|
73
|
+
|
|
74
|
+
switch {
|
|
75
|
+
case status >= 500:
|
|
76
|
+
return color.Red(status).Bold()
|
|
77
|
+
case status >= 300:
|
|
78
|
+
return color.Yellow(status).Bold()
|
|
79
|
+
default:
|
|
80
|
+
return color.Green(status).Bold()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Faint returns slightly offset color text if the writer supports it
|
|
85
|
+
func Faint(text string) string {
|
|
86
|
+
color := Color(os.Stdout)
|
|
87
|
+
return color.Sprintf(color.Faint(text))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Italic returns italicized text if the writer supports it.
|
|
91
|
+
func Italic(text string) string {
|
|
92
|
+
color := Color(os.Stdout)
|
|
93
|
+
return color.Sprintf(color.Italic(text))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Linkify returns an ANSI escape sequence with an hyperlink, if the writer
|
|
97
|
+
// supports colors.
|
|
98
|
+
func Linkify(text, url string, w io.Writer) string {
|
|
99
|
+
if !shouldUseColors(w) {
|
|
100
|
+
return text
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
104
|
+
// for more information about this escape sequence.
|
|
105
|
+
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type charset = []string
|
|
109
|
+
|
|
110
|
+
func getCharset() charset {
|
|
111
|
+
// See https://github.com/briandowns/spinner#available-character-sets for
|
|
112
|
+
// list of available charsets
|
|
113
|
+
if runtime.GOOS == "windows" {
|
|
114
|
+
// Less fancy, but uses ASCII characters so works with Windows default
|
|
115
|
+
// console.
|
|
116
|
+
return spinner.CharSets[8]
|
|
117
|
+
}
|
|
118
|
+
return spinner.CharSets[11]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const duration = time.Duration(100) * time.Millisecond
|
|
122
|
+
|
|
123
|
+
// StartNewSpinner starts a new spinner with the given message. If the writer is not
|
|
124
|
+
// a terminal or doesn't support colors, it simply prints the message.
|
|
125
|
+
func StartNewSpinner(msg string, w io.Writer) *spinner.Spinner {
|
|
126
|
+
if !isTerminal(w) || !shouldUseColors(w) {
|
|
127
|
+
fmt.Fprintln(w, msg)
|
|
128
|
+
return nil
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
s := spinner.New(getCharset(), duration)
|
|
132
|
+
s.Writer = w
|
|
133
|
+
|
|
134
|
+
if msg != "" {
|
|
135
|
+
s.Suffix = " " + msg
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
s.Start()
|
|
139
|
+
|
|
140
|
+
return s
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// StartSpinner updates an existing spinner's message, and starts it if it was stopped
|
|
144
|
+
func StartSpinner(s *spinner.Spinner, msg string, w io.Writer) {
|
|
145
|
+
if s == nil {
|
|
146
|
+
fmt.Fprintln(w, msg)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
if msg != "" {
|
|
150
|
+
s.Suffix = " " + msg
|
|
151
|
+
}
|
|
152
|
+
if !s.Active() {
|
|
153
|
+
s.Start()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// StopSpinner stops a spinner with the given message. If the writer is not
|
|
158
|
+
// a terminal or doesn't support colors, it simply prints the message.
|
|
159
|
+
func StopSpinner(s *spinner.Spinner, msg string, w io.Writer) {
|
|
160
|
+
if !isTerminal(w) || !shouldUseColors(w) {
|
|
161
|
+
fmt.Fprintln(w, msg)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if msg != "" {
|
|
166
|
+
s.FinalMSG = "> " + msg + "\n"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
s.Stop()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// StrikeThrough returns struck though text if the writer supports colors
|
|
173
|
+
func StrikeThrough(text string) string {
|
|
174
|
+
color := Color(os.Stdout)
|
|
175
|
+
return color.Sprintf(color.StrikeThrough(text))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//
|
|
179
|
+
// Private functions
|
|
180
|
+
//
|
|
181
|
+
|
|
182
|
+
func isTerminal(w io.Writer) bool {
|
|
183
|
+
switch v := w.(type) {
|
|
184
|
+
case *os.File:
|
|
185
|
+
return term.IsTerminal(int(v.Fd()))
|
|
186
|
+
default:
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func shouldUseColors(w io.Writer) bool {
|
|
192
|
+
useColors := ForceColors || isTerminal(w)
|
|
193
|
+
|
|
194
|
+
if EnvironmentOverrideColors {
|
|
195
|
+
force, ok := os.LookupEnv("CLICOLOR_FORCE")
|
|
196
|
+
|
|
197
|
+
switch {
|
|
198
|
+
case ok && force != "0":
|
|
199
|
+
useColors = true
|
|
200
|
+
case ok && force == "0":
|
|
201
|
+
useColors = false
|
|
202
|
+
case os.Getenv("CLICOLOR") == "0":
|
|
203
|
+
useColors = false
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return useColors && !DisableColors
|
|
208
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// +build windows
|
|
2
|
+
|
|
3
|
+
package ansi
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"os"
|
|
7
|
+
|
|
8
|
+
"golang.org/x/sys/windows"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// enableAnsiColors enables support for ANSI color sequences in Windows
|
|
12
|
+
// default console. Note that this only works with Windows 10.
|
|
13
|
+
func enableAnsiColors() {
|
|
14
|
+
stdout := windows.Handle(os.Stdout.Fd())
|
|
15
|
+
var originalMode uint32
|
|
16
|
+
|
|
17
|
+
windows.GetConsoleMode(stdout, &originalMode)
|
|
18
|
+
windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func init() {
|
|
22
|
+
enableAnsiColors()
|
|
23
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
|
|
10
|
+
"runtime"
|
|
11
|
+
|
|
12
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type completionCmd struct {
|
|
16
|
+
cmd *cobra.Command
|
|
17
|
+
|
|
18
|
+
shell string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func newCompletionCmd() *completionCmd {
|
|
22
|
+
cc := &completionCmd{}
|
|
23
|
+
|
|
24
|
+
cc.cmd = &cobra.Command{
|
|
25
|
+
Use: "completion",
|
|
26
|
+
Short: "Generate bash and zsh completion scripts",
|
|
27
|
+
Args: validators.NoArgs,
|
|
28
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
29
|
+
return selectShell(cc.shell)
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
cc.cmd.Flags().StringVar(&cc.shell, "shell", "", "The shell to generate completion commands for. Supports \"bash\" or \"zsh\"")
|
|
34
|
+
|
|
35
|
+
return cc
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const (
|
|
39
|
+
instructionsHeader = `
|
|
40
|
+
Suggested next steps:
|
|
41
|
+
---------------------`
|
|
42
|
+
|
|
43
|
+
zshCompletionInstructions = `
|
|
44
|
+
1. Move ` + "`hookdeck-completion.zsh`" + ` to the correct location:
|
|
45
|
+
mkdir -p ~/.hookdeck
|
|
46
|
+
mv hookdeck-completion.zsh ~/.hookdeck
|
|
47
|
+
|
|
48
|
+
2. Add the following lines to your ` + "`.zshrc`" + ` enabling shell completion for Hookdeck:
|
|
49
|
+
fpath=(~/.hookdeck $fpath)
|
|
50
|
+
autoload -Uz compinit && compinit -i
|
|
51
|
+
|
|
52
|
+
3. Source your ` + "`.zshrc`" + ` or open a new terminal session:
|
|
53
|
+
source ~/.zshrc`
|
|
54
|
+
|
|
55
|
+
bashCompletionInstructionsMac = `
|
|
56
|
+
Set up bash autocompletion on your system:
|
|
57
|
+
1. Install the bash autocompletion package:
|
|
58
|
+
brew install bash-completion
|
|
59
|
+
2. Follow the post-install instructions displayed by Homebrew; add a line like the following to your bash profile:
|
|
60
|
+
[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
|
|
61
|
+
|
|
62
|
+
Set up Hookdeck autocompletion:
|
|
63
|
+
3. Move ` + "`hookdeck-completion.bash`" + ` to the correct location:
|
|
64
|
+
mkdir -p ~/.hookdeck
|
|
65
|
+
mv hookdeck-completion.bash ~/.hookdeck
|
|
66
|
+
|
|
67
|
+
4. Add the following line to your bash profile, so that Hookdeck autocompletion will be enabled every time you start a new terminal session:
|
|
68
|
+
source ~/.hookdeck/hookdeck-completion.bash
|
|
69
|
+
|
|
70
|
+
5. Either restart your terminal, or run the following command in your current session to enable immediately:
|
|
71
|
+
source ~/.hookdeck/hookdeck-completion.bash`
|
|
72
|
+
|
|
73
|
+
bashCompletionInstructionsLinux = `
|
|
74
|
+
1. Ensure bash autocompletion is installed on your system. Often, this means verifying that ` + "`/etc/profile.d/bash_completion.sh`" + ` exists, and is sourced by your bash profile; the location of this file varies across distributions of Linux.
|
|
75
|
+
|
|
76
|
+
2. Move ` + "`hookdeck-completion.bash`" + ` to the correct location:
|
|
77
|
+
mkdir -p ~/.hookdeck
|
|
78
|
+
mv hookdeck-completion.bash ~/.hookdeck
|
|
79
|
+
|
|
80
|
+
3. Add the following line to your bash profile, so that Hookdeck autocompletion will be enabled every time you start a new terminal session:
|
|
81
|
+
source ~/.hookdeck/hookdeck-completion.bash
|
|
82
|
+
|
|
83
|
+
4. Either restart your terminal, or run the following command in your current session to enable immediately:
|
|
84
|
+
source ~/.hookdeck/hookdeck-completion.bash`
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
func selectShell(shell string) error {
|
|
88
|
+
selected := shell
|
|
89
|
+
if selected == "" {
|
|
90
|
+
selected = detectShell()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
switch {
|
|
94
|
+
case selected == "zsh":
|
|
95
|
+
fmt.Println("Detected `zsh`, generating zsh completion file: hookdeck-completion.zsh")
|
|
96
|
+
err := rootCmd.GenZshCompletionFile("hookdeck-completion.zsh")
|
|
97
|
+
if err == nil {
|
|
98
|
+
fmt.Printf("%s%s\n", instructionsHeader, zshCompletionInstructions)
|
|
99
|
+
}
|
|
100
|
+
return err
|
|
101
|
+
case selected == "bash":
|
|
102
|
+
fmt.Println("Detected `bash`, generating bash completion file: hookdeck-completion.bash")
|
|
103
|
+
err := rootCmd.GenBashCompletionFile("hookdeck-completion.bash")
|
|
104
|
+
if err == nil {
|
|
105
|
+
if runtime.GOOS == "darwin" {
|
|
106
|
+
fmt.Printf("%s%s\n", instructionsHeader, bashCompletionInstructionsMac)
|
|
107
|
+
} else if runtime.GOOS == "linux" {
|
|
108
|
+
fmt.Printf("%s%s\n", instructionsHeader, bashCompletionInstructionsLinux)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return err
|
|
112
|
+
default:
|
|
113
|
+
return fmt.Errorf("Could not automatically detect your shell. Please run the command with the `--shell` flag for either bash or zsh")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func detectShell() string {
|
|
118
|
+
shell := os.Getenv("SHELL")
|
|
119
|
+
|
|
120
|
+
switch {
|
|
121
|
+
case strings.Contains(shell, "zsh"):
|
|
122
|
+
return "zsh"
|
|
123
|
+
case strings.Contains(shell, "bash"):
|
|
124
|
+
return "bash"
|
|
125
|
+
default:
|
|
126
|
+
return ""
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
package cmd
|
|
17
|
+
|
|
18
|
+
import (
|
|
19
|
+
"errors"
|
|
20
|
+
"net/url"
|
|
21
|
+
"strconv"
|
|
22
|
+
"strings"
|
|
23
|
+
|
|
24
|
+
"github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
|
|
25
|
+
"github.com/hookdeck/hookdeck-cli/pkg/listen"
|
|
26
|
+
"github.com/spf13/cobra"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
type listenCmd struct {
|
|
30
|
+
cmd *cobra.Command
|
|
31
|
+
wsBaseURL string
|
|
32
|
+
noWSS bool
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func newListenCmd() *listenCmd {
|
|
36
|
+
lc := &listenCmd{}
|
|
37
|
+
|
|
38
|
+
lc.cmd = &cobra.Command{
|
|
39
|
+
Use: "listen",
|
|
40
|
+
Short: "Forward webhooks for a source to your local server",
|
|
41
|
+
Args: func(cmd *cobra.Command, args []string) error {
|
|
42
|
+
if len(args) < 1 {
|
|
43
|
+
return errors.New("Requires a port or forwarding URL to foward the webhooks to")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_, err_port := strconv.ParseInt(args[0], 10, 64)
|
|
47
|
+
|
|
48
|
+
var parsed_url *url.URL
|
|
49
|
+
var err_url error
|
|
50
|
+
if strings.HasPrefix(args[0], "http") {
|
|
51
|
+
parsed_url, err_url = url.Parse(args[0])
|
|
52
|
+
} else {
|
|
53
|
+
parsed_url, err_url = url.Parse("http://" + args[0])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if err_port != nil && err_url != nil {
|
|
57
|
+
return errors.New("Argument is not a valid port or forwading URL")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if err_port != nil {
|
|
61
|
+
if parsed_url.Host == "" {
|
|
62
|
+
return errors.New("Forwarding URL must contain a host.")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if parsed_url.RawQuery != "" {
|
|
66
|
+
return errors.New("Forwarding URL cannot contain query params.")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if len(args) > 3 {
|
|
71
|
+
return errors.New("Invalid extra argument provided")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return nil
|
|
75
|
+
},
|
|
76
|
+
RunE: lc.runListenCmd,
|
|
77
|
+
}
|
|
78
|
+
lc.cmd.Flags().StringVar(&lc.wsBaseURL, "ws-base", hookdeck.DefaultWebsocektURL, "Sets the Websocket base URL")
|
|
79
|
+
lc.cmd.Flags().BoolVar(&lc.noWSS, "no-wss", false, "Force unencrypted ws:// protocol instead of wss://")
|
|
80
|
+
|
|
81
|
+
return lc
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// listenCmd represents the listen command
|
|
85
|
+
func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
|
|
86
|
+
var source_alias, connection_query string
|
|
87
|
+
if len(args) > 1 {
|
|
88
|
+
source_alias = args[1]
|
|
89
|
+
}
|
|
90
|
+
if len(args) > 2 {
|
|
91
|
+
connection_query = args[2]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_, err_port := strconv.ParseInt(args[0], 10, 64)
|
|
95
|
+
var url *url.URL
|
|
96
|
+
if err_port != nil {
|
|
97
|
+
if strings.HasPrefix(args[0], "http") {
|
|
98
|
+
url, _ = url.Parse(args[0])
|
|
99
|
+
} else {
|
|
100
|
+
url, _ = url.Parse("http://" + args[0])
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
url, _ = url.Parse("http://localhost:" + args[0])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if url.Scheme == "" {
|
|
107
|
+
url.Scheme = "http"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return listen.Listen(url, source_alias, connection_query, listen.Flags{
|
|
111
|
+
WSBaseURL: lc.wsBaseURL,
|
|
112
|
+
NoWSS: lc.noWSS,
|
|
113
|
+
}, &Config)
|
|
114
|
+
}
|
package/pkg/cmd/login.go
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
|
|
6
|
+
"github.com/spf13/cobra"
|
|
7
|
+
|
|
8
|
+
"github.com/hookdeck/hookdeck-cli/pkg/login"
|
|
9
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type loginCmd struct {
|
|
13
|
+
cmd *cobra.Command
|
|
14
|
+
interactive bool
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func newLoginCmd() *loginCmd {
|
|
18
|
+
lc := &loginCmd{}
|
|
19
|
+
|
|
20
|
+
lc.cmd = &cobra.Command{
|
|
21
|
+
Use: "login",
|
|
22
|
+
Args: validators.NoArgs,
|
|
23
|
+
Short: "Login to your Hookdeck account",
|
|
24
|
+
Long: `Login to your Hookdeck account to setup the CLI`,
|
|
25
|
+
RunE: lc.runLoginCmd,
|
|
26
|
+
}
|
|
27
|
+
lc.cmd.Flags().BoolVarP(&lc.interactive, "interactive", "i", false, "Run interactive configuration mode if you cannot open a browser")
|
|
28
|
+
|
|
29
|
+
return lc
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func (lc *loginCmd) runLoginCmd(cmd *cobra.Command, args []string) error {
|
|
33
|
+
if lc.interactive {
|
|
34
|
+
return login.InteractiveLogin(&Config)
|
|
35
|
+
}
|
|
36
|
+
return login.Login(&Config, os.Stdin)
|
|
37
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/spf13/cobra"
|
|
5
|
+
|
|
6
|
+
"github.com/hookdeck/hookdeck-cli/pkg/logout"
|
|
7
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type logoutCmd struct {
|
|
11
|
+
cmd *cobra.Command
|
|
12
|
+
all bool
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func newLogoutCmd() *logoutCmd {
|
|
16
|
+
lc := &logoutCmd{}
|
|
17
|
+
|
|
18
|
+
lc.cmd = &cobra.Command{
|
|
19
|
+
Use: "logout",
|
|
20
|
+
Args: validators.NoArgs,
|
|
21
|
+
Short: "Logout of your Hookdeck account",
|
|
22
|
+
Long: `Logout of your Hookdeck account to setup the CLI`,
|
|
23
|
+
RunE: lc.runLogoutCmd,
|
|
24
|
+
}
|
|
25
|
+
lc.cmd.Flags().BoolVarP(&lc.all, "all", "a", false, "Clear credentials for all projects you are currently logged into.")
|
|
26
|
+
|
|
27
|
+
return lc
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func (lc *logoutCmd) runLogoutCmd(cmd *cobra.Command, args []string) error {
|
|
31
|
+
if lc.all {
|
|
32
|
+
return logout.All(&Config)
|
|
33
|
+
}
|
|
34
|
+
return logout.Logout(&Config)
|
|
35
|
+
}
|
package/pkg/cmd/root.go
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
package cmd
|
|
17
|
+
|
|
18
|
+
import (
|
|
19
|
+
"fmt"
|
|
20
|
+
"os"
|
|
21
|
+
"strings"
|
|
22
|
+
"unicode"
|
|
23
|
+
|
|
24
|
+
"github.com/hookdeck/hookdeck-cli/pkg/config"
|
|
25
|
+
"github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
|
|
26
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
27
|
+
"github.com/hookdeck/hookdeck-cli/pkg/version"
|
|
28
|
+
"github.com/spf13/cobra"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
var cfgFile string
|
|
32
|
+
|
|
33
|
+
var Config config.Config
|
|
34
|
+
|
|
35
|
+
var rootCmd = &cobra.Command{
|
|
36
|
+
Use: "hookdeck",
|
|
37
|
+
SilenceUsage: true,
|
|
38
|
+
SilenceErrors: true,
|
|
39
|
+
Version: version.Version,
|
|
40
|
+
Short: "A CLI to forward webhooks received on Hookdeck to your local server.",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
44
|
+
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
45
|
+
func Execute() {
|
|
46
|
+
if err := rootCmd.Execute(); err != nil {
|
|
47
|
+
errString := err.Error()
|
|
48
|
+
isLoginRequiredError := errString == validators.ErrAPIKeyNotConfigured.Error() || errString == validators.ErrDeviceNameNotConfigured.Error()
|
|
49
|
+
|
|
50
|
+
switch {
|
|
51
|
+
case isLoginRequiredError:
|
|
52
|
+
// capitalize first letter of error because linter
|
|
53
|
+
errRunes := []rune(errString)
|
|
54
|
+
errRunes[0] = unicode.ToUpper(errRunes[0])
|
|
55
|
+
|
|
56
|
+
fmt.Printf("%s. Running `hookdeck login`...\n", string(errRunes))
|
|
57
|
+
loginCommand, _, err := rootCmd.Find([]string{"login"})
|
|
58
|
+
|
|
59
|
+
if err != nil {
|
|
60
|
+
fmt.Println(err)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
err = loginCommand.RunE(&cobra.Command{}, []string{})
|
|
64
|
+
|
|
65
|
+
if err != nil {
|
|
66
|
+
fmt.Println(err)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case strings.Contains(errString, "unknown command"):
|
|
70
|
+
suggStr := "\nS"
|
|
71
|
+
|
|
72
|
+
suggestions := rootCmd.SuggestionsFor(os.Args[1])
|
|
73
|
+
if len(suggestions) > 0 {
|
|
74
|
+
suggStr = fmt.Sprintf(" Did you mean \"%s\"?\nIf not, s", suggestions[0])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fmt.Println(fmt.Sprintf("Unknown command \"%s\" for \"%s\".%s"+
|
|
78
|
+
"ee \"hookdeck --help\" for a list of available commands.",
|
|
79
|
+
os.Args[1], rootCmd.CommandPath(), suggStr))
|
|
80
|
+
|
|
81
|
+
default:
|
|
82
|
+
fmt.Println(err)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
os.Exit(1)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func init() {
|
|
90
|
+
cobra.OnInitialize(Config.InitConfig)
|
|
91
|
+
|
|
92
|
+
rootCmd.PersistentFlags().StringVar(&Config.Profile.APIKey, "cli-key", "", "Your CLI key to use for the command")
|
|
93
|
+
rootCmd.PersistentFlags().StringVar(&Config.Color, "color", "", "turn on/off color output (on, off, auto)")
|
|
94
|
+
rootCmd.PersistentFlags().StringVar(&Config.ProfilesFile, "config", "", "config file (default is $HOME/.config/hookdeck/config.toml)")
|
|
95
|
+
rootCmd.PersistentFlags().StringVar(&Config.Profile.DeviceName, "device-name", "", "device name")
|
|
96
|
+
rootCmd.PersistentFlags().StringVar(&Config.LogLevel, "log-level", "info", "log level (debug, info, warn, error)")
|
|
97
|
+
rootCmd.PersistentFlags().BoolVar(&Config.Insecure, "insecure", false, "Allow invalid TLS certificates")
|
|
98
|
+
rootCmd.PersistentFlags().StringVarP(&Config.Profile.ProfileName, "project-name", "p", "default", "the project name to read from for config")
|
|
99
|
+
|
|
100
|
+
// Hidden configuration flags, useful for dev/debugging
|
|
101
|
+
rootCmd.PersistentFlags().StringVar(&Config.APIBaseURL, "api-base", hookdeck.DefaultAPIBaseURL, "Sets the API base URL")
|
|
102
|
+
rootCmd.PersistentFlags().StringVar(&Config.DashboardBaseURL, "dashboard-base", hookdeck.DefaultDashboardBaseURL, "Sets the web dashboard base URL")
|
|
103
|
+
rootCmd.PersistentFlags().StringVar(&Config.ConsoleBaseURL, "console-base", hookdeck.DefaultConsoleBaseURL, "Sets the web console base URL")
|
|
104
|
+
|
|
105
|
+
rootCmd.Flags().BoolP("version", "v", false, "Get the version of the Hookdeck CLI")
|
|
106
|
+
|
|
107
|
+
rootCmd.AddCommand(newLoginCmd().cmd)
|
|
108
|
+
rootCmd.AddCommand(newLogoutCmd().cmd)
|
|
109
|
+
rootCmd.AddCommand(newListenCmd().cmd)
|
|
110
|
+
rootCmd.AddCommand(newCompletionCmd().cmd)
|
|
111
|
+
rootCmd.AddCommand(newWhoamiCmd().cmd)
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/spf13/cobra"
|
|
7
|
+
|
|
8
|
+
"github.com/hookdeck/hookdeck-cli/pkg/validators"
|
|
9
|
+
"github.com/hookdeck/hookdeck-cli/pkg/version"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
var versionCmd = &cobra.Command{
|
|
13
|
+
Use: "version",
|
|
14
|
+
Args: validators.NoArgs,
|
|
15
|
+
Short: "Get the version of the Hookdeck CLI",
|
|
16
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
17
|
+
fmt.Print(version.Template)
|
|
18
|
+
|
|
19
|
+
version.CheckLatestVersion()
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func init() {
|
|
24
|
+
rootCmd.AddCommand(versionCmd)
|
|
25
|
+
}
|