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,144 @@
|
|
|
1
|
+
package validators
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"fmt"
|
|
6
|
+
"net/http"
|
|
7
|
+
"strconv"
|
|
8
|
+
"strings"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// ArgValidator is an argument validator. It accepts a string and returns an
|
|
12
|
+
// error if the string is invalid, or nil otherwise.
|
|
13
|
+
type ArgValidator func(string) error
|
|
14
|
+
|
|
15
|
+
var (
|
|
16
|
+
// ErrAPIKeyNotConfigured is the error returned when the loaded profile is missing the api key property
|
|
17
|
+
ErrAPIKeyNotConfigured = errors.New("you have not configured API keys yet")
|
|
18
|
+
// ErrDeviceNameNotConfigured is the error returned when the loaded profile is missing the device name property
|
|
19
|
+
ErrDeviceNameNotConfigured = errors.New("you have not configured your device name yet")
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// CallNonEmptyArray calls an argument validator on all non-empty elements of
|
|
23
|
+
// a string array.
|
|
24
|
+
func CallNonEmptyArray(validator ArgValidator, values []string) error {
|
|
25
|
+
if len(values) == 0 {
|
|
26
|
+
return nil
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for _, value := range values {
|
|
30
|
+
err := CallNonEmpty(validator, value)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return err
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// CallNonEmpty calls an argument validator on a string if the string is not
|
|
40
|
+
// empty.
|
|
41
|
+
func CallNonEmpty(validator ArgValidator, value string) error {
|
|
42
|
+
if value == "" {
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return validator(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// APIKey validates that a string looks like an API key.
|
|
50
|
+
func APIKey(input string) error {
|
|
51
|
+
if len(input) == 0 {
|
|
52
|
+
return ErrAPIKeyNotConfigured
|
|
53
|
+
} else if len(input) < 12 {
|
|
54
|
+
return errors.New("the API key provided is too short, it must be at least 12 characters long")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// APIKeyNotRestricted validates that a string looks like a secret API key and is not a restricted key.
|
|
61
|
+
func APIKeyNotRestricted(input string) error {
|
|
62
|
+
if len(input) == 0 {
|
|
63
|
+
return ErrAPIKeyNotConfigured
|
|
64
|
+
} else if len(input) < 12 {
|
|
65
|
+
return errors.New("the API key provided is too short, it must be at least 12 characters long")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Account validates that a string is an acceptable account filter.
|
|
72
|
+
func Account(account string) error {
|
|
73
|
+
accountUpper := strings.ToUpper(account)
|
|
74
|
+
|
|
75
|
+
if accountUpper == "CONNECT_IN" || accountUpper == "CONNECT_OUT" || accountUpper == "SELF" {
|
|
76
|
+
return nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return fmt.Errorf("%s is not an acceptable account filter (CONNECT_IN, CONNECT_OUT, SELF)", account)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// HTTPMethod validates that a string is an acceptable HTTP method.
|
|
83
|
+
func HTTPMethod(method string) error {
|
|
84
|
+
methodUpper := strings.ToUpper(method)
|
|
85
|
+
|
|
86
|
+
if methodUpper == http.MethodGet || methodUpper == http.MethodPost || methodUpper == http.MethodDelete {
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return fmt.Errorf("%s is not an acceptable HTTP method (GET, POST, DELETE)", method)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// RequestSource validates that a string is an acceptable request source.
|
|
94
|
+
func RequestSource(source string) error {
|
|
95
|
+
sourceUpper := strings.ToUpper(source)
|
|
96
|
+
|
|
97
|
+
if sourceUpper == "API" || sourceUpper == "DASHBOARD" {
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return fmt.Errorf("%s is not an acceptable source (API, DASHBOARD)", source)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// RequestStatus validates that a string is an acceptable request status.
|
|
105
|
+
func RequestStatus(status string) error {
|
|
106
|
+
statusUpper := strings.ToUpper(status)
|
|
107
|
+
|
|
108
|
+
if statusUpper == "SUCCEEDED" || statusUpper == "FAILED" {
|
|
109
|
+
return nil
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return fmt.Errorf("%s is not an acceptable request status (SUCCEEDED, FAILED)", status)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// StatusCode validates that a provided status code is within the range of
|
|
116
|
+
// those used in the Hookdeck API.
|
|
117
|
+
func StatusCode(code string) error {
|
|
118
|
+
num, err := strconv.Atoi(code)
|
|
119
|
+
if err != nil {
|
|
120
|
+
return err
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if num >= 200 && num < 300 {
|
|
124
|
+
return nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if num >= 400 && num < 600 {
|
|
128
|
+
return nil
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return fmt.Errorf("Provided status code %s is not in the range of acceptable status codes (200's, 400's, 500's)", code)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// StatusCodeType validates that a provided status code type is one of those
|
|
135
|
+
// used in the Hookdeck API.
|
|
136
|
+
func StatusCodeType(code string) error {
|
|
137
|
+
codeUpper := strings.ToUpper(code)
|
|
138
|
+
|
|
139
|
+
if codeUpper != "2XX" && codeUpper != "4XX" && codeUpper != "5XX" {
|
|
140
|
+
return fmt.Errorf("Provided status code type %s is not a valid type (2XX, 4XX, 5XX)", code)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return nil
|
|
144
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package version
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/google/go-github/v28/github"
|
|
10
|
+
log "github.com/sirupsen/logrus"
|
|
11
|
+
|
|
12
|
+
"github.com/hookdeck/hookdeck-cli/pkg/ansi"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// Version of the CLI.
|
|
16
|
+
// This is set to the actual version by GoReleaser, identify by the
|
|
17
|
+
// git tag assigned to the release. Versions built from source will
|
|
18
|
+
// always show master.
|
|
19
|
+
var Version = "master"
|
|
20
|
+
|
|
21
|
+
// Template for the version string.
|
|
22
|
+
var Template = fmt.Sprintf("hookdeck version %s\n", Version)
|
|
23
|
+
|
|
24
|
+
// CheckLatestVersion makes a request to the GitHub API to pull the latest
|
|
25
|
+
// release of the CLI
|
|
26
|
+
func CheckLatestVersion() {
|
|
27
|
+
// master is the dev version, we don't want to check against that every time
|
|
28
|
+
if Version != "master" {
|
|
29
|
+
s := ansi.StartNewSpinner("Checking for new versions...", os.Stdout)
|
|
30
|
+
latest := getLatestVersion()
|
|
31
|
+
|
|
32
|
+
ansi.StopSpinner(s, "", os.Stdout)
|
|
33
|
+
|
|
34
|
+
if needsToUpgrade(Version, latest) {
|
|
35
|
+
fmt.Println(ansi.Italic("A newer version of the Hookdeck CLI is available, please update to:"), ansi.Italic(latest))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func needsToUpgrade(version, latest string) bool {
|
|
41
|
+
return latest != "" && (strings.TrimPrefix(latest, "v") != strings.TrimPrefix(version, "v"))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func getLatestVersion() string {
|
|
45
|
+
client := github.NewClient(nil)
|
|
46
|
+
rep, _, err := client.Repositories.GetLatestRelease(context.Background(), "hookdeck", "hookdeck-cli")
|
|
47
|
+
|
|
48
|
+
l := log.StandardLogger()
|
|
49
|
+
|
|
50
|
+
if err != nil {
|
|
51
|
+
// We don't want to fail any functionality or display errors for this
|
|
52
|
+
// so fail silently and output to debug log
|
|
53
|
+
l.Debug(err)
|
|
54
|
+
return ""
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return *rep.TagName
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package version
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/stretchr/testify/require"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestNeedsToUpgrade(t *testing.T) {
|
|
10
|
+
require.False(t, needsToUpgrade("4.2.4.2", "v4.2.4.2"))
|
|
11
|
+
require.False(t, needsToUpgrade("4.2.4.2", "4.2.4.2"))
|
|
12
|
+
require.True(t, needsToUpgrade("4.2.4.2", "4.2.4.3"))
|
|
13
|
+
require.True(t, needsToUpgrade("4.2.4.2", "v4.2.4.3"))
|
|
14
|
+
require.True(t, needsToUpgrade("v4.2.4.2", "v4.2.4.3"))
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package websocket
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
type AttemptRequest struct {
|
|
8
|
+
Method string `json:"method"`
|
|
9
|
+
Timeout int64 `json:"timeout"`
|
|
10
|
+
DataString string `json:"data_string"`
|
|
11
|
+
Headers json.RawMessage `json:"headers"`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type AttemptBody struct {
|
|
15
|
+
Path string `json:"cli_path"`
|
|
16
|
+
EventID string `json:"event_id"`
|
|
17
|
+
AttemptId string `json:"attempt_id"`
|
|
18
|
+
ConnectionId string `json:"webhook_id"`
|
|
19
|
+
Request AttemptRequest `json:"request"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Attempt struct {
|
|
23
|
+
Event string `json:"type"`
|
|
24
|
+
Body AttemptBody `json:"body"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type AttemptResponseBody struct {
|
|
28
|
+
AttemptId string `json:"attempt_id"`
|
|
29
|
+
CLIPath string `json:"cli_path"`
|
|
30
|
+
Status int `json:"status"`
|
|
31
|
+
Data string `json:"data"`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type AttemptResponse struct {
|
|
35
|
+
Event string `json:"event"`
|
|
36
|
+
Body AttemptResponseBody `json:"body"`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ErrorAttemptBody struct {
|
|
40
|
+
AttemptId string `json:"attempt_id"`
|
|
41
|
+
Error bool `json:"error"`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ErrorAttemptResponse struct {
|
|
45
|
+
Event string `json:"event"`
|
|
46
|
+
Body ErrorAttemptBody `json:"body"`
|
|
47
|
+
}
|