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.
Files changed (61) hide show
  1. package/.github/workflows/publish-npm.yml +19 -0
  2. package/.github/workflows/release.yml +85 -0
  3. package/.goreleaser/linux.yml +68 -0
  4. package/.goreleaser/mac.yml +69 -0
  5. package/.goreleaser/windows.yml +52 -0
  6. package/.tool-versions +1 -0
  7. package/Dockerfile +5 -0
  8. package/LICENSE +202 -0
  9. package/README.md +223 -0
  10. package/docs/cli-demo.gif +0 -0
  11. package/go.mod +58 -0
  12. package/go.sum +444 -0
  13. package/main.go +22 -0
  14. package/package.json +30 -0
  15. package/pkg/ansi/ansi.go +208 -0
  16. package/pkg/ansi/init_windows.go +23 -0
  17. package/pkg/cmd/completion.go +128 -0
  18. package/pkg/cmd/listen.go +114 -0
  19. package/pkg/cmd/login.go +37 -0
  20. package/pkg/cmd/logout.go +35 -0
  21. package/pkg/cmd/root.go +112 -0
  22. package/pkg/cmd/version.go +25 -0
  23. package/pkg/cmd/whoami.go +50 -0
  24. package/pkg/config/config.go +326 -0
  25. package/pkg/config/config_test.go +20 -0
  26. package/pkg/config/profile.go +296 -0
  27. package/pkg/config/profile_test.go +109 -0
  28. package/pkg/hookdeck/client.go +210 -0
  29. package/pkg/hookdeck/client_test.go +203 -0
  30. package/pkg/hookdeck/connections.go +61 -0
  31. package/pkg/hookdeck/destinations.go +14 -0
  32. package/pkg/hookdeck/guest.go +37 -0
  33. package/pkg/hookdeck/session.go +37 -0
  34. package/pkg/hookdeck/sources.go +73 -0
  35. package/pkg/hookdeck/telemetry.go +84 -0
  36. package/pkg/hookdeck/telemetry_test.go +35 -0
  37. package/pkg/hookdeck/verbosetransport.go +82 -0
  38. package/pkg/hookdeck/verbosetransport_test.go +47 -0
  39. package/pkg/listen/connection.go +91 -0
  40. package/pkg/listen/listen.go +140 -0
  41. package/pkg/listen/source.go +84 -0
  42. package/pkg/login/client_login.go +209 -0
  43. package/pkg/login/interactive_login.go +180 -0
  44. package/pkg/login/login_message.go +24 -0
  45. package/pkg/login/poll.go +78 -0
  46. package/pkg/login/validate.go +52 -0
  47. package/pkg/logout/logout.go +48 -0
  48. package/pkg/open/open.go +50 -0
  49. package/pkg/proxy/proxy.go +433 -0
  50. package/pkg/useragent/uname_unix.go +25 -0
  51. package/pkg/useragent/uname_unix_test.go +24 -0
  52. package/pkg/useragent/uname_windows.go +9 -0
  53. package/pkg/useragent/useragent.go +74 -0
  54. package/pkg/validators/cmds.go +71 -0
  55. package/pkg/validators/validate.go +144 -0
  56. package/pkg/version/version.go +58 -0
  57. package/pkg/version/version_test.go +15 -0
  58. package/pkg/websocket/attempt_messages.go +47 -0
  59. package/pkg/websocket/client.go +525 -0
  60. package/pkg/websocket/connection_messages.go +11 -0
  61. 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
+ }