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,109 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io/ioutil"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
"github.com/spf13/viper"
|
|
11
|
+
"github.com/stretchr/testify/require"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func TestWriteProfile(t *testing.T) {
|
|
15
|
+
profilesFile := filepath.Join(os.TempDir(), "hookdeck", "config.toml")
|
|
16
|
+
p := Profile{
|
|
17
|
+
DeviceName: "st-testing",
|
|
18
|
+
ProfileName: "tests",
|
|
19
|
+
APIKey: "sk_test_123",
|
|
20
|
+
DisplayName: "test-account-display-name",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
c := &Config{
|
|
24
|
+
Color: "auto",
|
|
25
|
+
LogLevel: "info",
|
|
26
|
+
Profile: p,
|
|
27
|
+
ProfilesFile: profilesFile,
|
|
28
|
+
}
|
|
29
|
+
c.InitConfig()
|
|
30
|
+
|
|
31
|
+
v := viper.New()
|
|
32
|
+
|
|
33
|
+
fmt.Println(profilesFile)
|
|
34
|
+
|
|
35
|
+
err := p.writeProfile(v)
|
|
36
|
+
require.NoError(t, err)
|
|
37
|
+
|
|
38
|
+
require.FileExists(t, c.ProfilesFile)
|
|
39
|
+
|
|
40
|
+
configValues := helperLoadBytes(t, c.ProfilesFile)
|
|
41
|
+
expectedConfig := `
|
|
42
|
+
[tests]
|
|
43
|
+
api_key = "sk_test_123"
|
|
44
|
+
device_name = "st-testing"
|
|
45
|
+
display_name = "test-account-display-name"
|
|
46
|
+
`
|
|
47
|
+
require.EqualValues(t, expectedConfig, string(configValues))
|
|
48
|
+
|
|
49
|
+
cleanUp(c.ProfilesFile)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func TestWriteProfilesMerge(t *testing.T) {
|
|
53
|
+
profilesFile := filepath.Join(os.TempDir(), "hookdeck", "config.toml")
|
|
54
|
+
p := Profile{
|
|
55
|
+
ProfileName: "tests",
|
|
56
|
+
DeviceName: "st-testing",
|
|
57
|
+
APIKey: "sk_test_123",
|
|
58
|
+
DisplayName: "test-account-display-name",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
c := &Config{
|
|
62
|
+
Color: "auto",
|
|
63
|
+
LogLevel: "info",
|
|
64
|
+
Profile: p,
|
|
65
|
+
ProfilesFile: profilesFile,
|
|
66
|
+
}
|
|
67
|
+
c.InitConfig()
|
|
68
|
+
|
|
69
|
+
v := viper.New()
|
|
70
|
+
writeErr := p.writeProfile(v)
|
|
71
|
+
|
|
72
|
+
require.NoError(t, writeErr)
|
|
73
|
+
require.FileExists(t, c.ProfilesFile)
|
|
74
|
+
|
|
75
|
+
p.ProfileName = "tests-merge"
|
|
76
|
+
writeErrTwo := p.writeProfile(v)
|
|
77
|
+
require.NoError(t, writeErrTwo)
|
|
78
|
+
require.FileExists(t, c.ProfilesFile)
|
|
79
|
+
|
|
80
|
+
configValues := helperLoadBytes(t, c.ProfilesFile)
|
|
81
|
+
expectedConfig := `
|
|
82
|
+
[tests]
|
|
83
|
+
api_key = "sk_test_123"
|
|
84
|
+
device_name = "st-testing"
|
|
85
|
+
display_name = "test-account-display-name"
|
|
86
|
+
|
|
87
|
+
[tests-merge]
|
|
88
|
+
api_key = "sk_test_123"
|
|
89
|
+
device_name = "st-testing"
|
|
90
|
+
display_name = "test-account-display-name"
|
|
91
|
+
`
|
|
92
|
+
|
|
93
|
+
require.EqualValues(t, expectedConfig, string(configValues))
|
|
94
|
+
|
|
95
|
+
cleanUp(c.ProfilesFile)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func helperLoadBytes(t *testing.T, name string) []byte {
|
|
99
|
+
bytes, err := ioutil.ReadFile(name)
|
|
100
|
+
if err != nil {
|
|
101
|
+
t.Fatal(err)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return bytes
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func cleanUp(file string) {
|
|
108
|
+
os.Remove(file)
|
|
109
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"io/ioutil"
|
|
9
|
+
"net"
|
|
10
|
+
"net/http"
|
|
11
|
+
"net/url"
|
|
12
|
+
"os"
|
|
13
|
+
"time"
|
|
14
|
+
|
|
15
|
+
"github.com/hookdeck/hookdeck-cli/pkg/useragent"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// DefaultAPIBaseURL is the default base URL for API requests
|
|
19
|
+
const DefaultAPIBaseURL = "https://api.hookdeck.com"
|
|
20
|
+
|
|
21
|
+
// DefaultDashboardURL is the default base URL for web links
|
|
22
|
+
const DefaultDashboardURL = "https://dashboard.hookdeck.com"
|
|
23
|
+
|
|
24
|
+
// DefaultDashboardBaseURL is the default base URL for dashboard requests
|
|
25
|
+
const DefaultDashboardBaseURL = "http://dashboard.hookdeck.com"
|
|
26
|
+
|
|
27
|
+
const DefaultConsoleBaseURL = "http://console.hookdeck.com"
|
|
28
|
+
|
|
29
|
+
const DefaultWebsocektURL = "wss://ws.hookdeck.com"
|
|
30
|
+
|
|
31
|
+
// Client is the API client used to sent requests to Hookdeck.
|
|
32
|
+
type Client struct {
|
|
33
|
+
// The base URL (protocol + hostname) used for all requests sent by this
|
|
34
|
+
// client.
|
|
35
|
+
BaseURL *url.URL
|
|
36
|
+
|
|
37
|
+
// API key used to authenticate requests sent by this client. If left
|
|
38
|
+
// empty, the `Authorization` header will be omitted.
|
|
39
|
+
APIKey string
|
|
40
|
+
|
|
41
|
+
// When this is enabled, request and response headers will be printed to
|
|
42
|
+
// stdout.
|
|
43
|
+
Verbose bool
|
|
44
|
+
|
|
45
|
+
// Cached HTTP client, lazily created the first time the Client is used to
|
|
46
|
+
// send a request.
|
|
47
|
+
httpClient *http.Client
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type ErrorResponse struct {
|
|
51
|
+
Handled bool `json:"Handled"`
|
|
52
|
+
Message string `json:"message"`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// PerformRequest sends a request to Hookdeck and returns the response.
|
|
56
|
+
func (c *Client) PerformRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
|
57
|
+
if req.Header == nil {
|
|
58
|
+
req.Header = http.Header{}
|
|
59
|
+
}
|
|
60
|
+
req.Header.Set("Content-Type", "application/json")
|
|
61
|
+
req.Header.Set("User-Agent", useragent.GetEncodedUserAgent())
|
|
62
|
+
req.Header.Set("X-Hookdeck-Client-User-Agent", useragent.GetEncodedHookdeckUserAgent())
|
|
63
|
+
|
|
64
|
+
if !telemetryOptedOut(os.Getenv("HOOKDECK_CLI_TELEMETRY_OPTOUT")) {
|
|
65
|
+
telemetryHdr, err := getTelemetryHeader()
|
|
66
|
+
if err == nil {
|
|
67
|
+
req.Header.Set("Hookdeck-CLI-Telemetry", telemetryHdr)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if c.APIKey != "" {
|
|
72
|
+
req.SetBasicAuth(c.APIKey, "")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if c.httpClient == nil {
|
|
76
|
+
c.httpClient = newHTTPClient(c.Verbose, os.Getenv("HOOKDECK_CLI_UNIX_SOCKET"))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ctx != nil {
|
|
80
|
+
req = req.WithContext(ctx)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
resp, err := c.httpClient.Do(req)
|
|
84
|
+
if err != nil {
|
|
85
|
+
return nil, err
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
err = checkAndPrintError(resp)
|
|
89
|
+
if err != nil {
|
|
90
|
+
return nil, err
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return resp, nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func (c *Client) Get(ctx context.Context, path string, params string, configure func(*http.Request)) (*http.Response, error) {
|
|
97
|
+
url, err := url.Parse(path)
|
|
98
|
+
if err != nil {
|
|
99
|
+
return nil, err
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
url = c.BaseURL.ResolveReference(url)
|
|
103
|
+
|
|
104
|
+
url.RawQuery = params
|
|
105
|
+
|
|
106
|
+
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
|
|
107
|
+
if err != nil {
|
|
108
|
+
return nil, err
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return c.PerformRequest(ctx, req)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (c *Client) Post(ctx context.Context, path string, data []byte, configure func(*http.Request)) (*http.Response, error) {
|
|
115
|
+
url, err := url.Parse(path)
|
|
116
|
+
if err != nil {
|
|
117
|
+
return nil, err
|
|
118
|
+
}
|
|
119
|
+
url = c.BaseURL.ResolveReference(url)
|
|
120
|
+
req, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewBuffer(data))
|
|
121
|
+
if err != nil {
|
|
122
|
+
return nil, err
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return c.PerformRequest(ctx, req)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (c *Client) Put(ctx context.Context, path string, data []byte, configure func(*http.Request)) (*http.Response, error) {
|
|
129
|
+
url, err := url.Parse(path)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return nil, err
|
|
132
|
+
}
|
|
133
|
+
url = c.BaseURL.ResolveReference(url)
|
|
134
|
+
req, err := http.NewRequest(http.MethodPut, url.String(), bytes.NewBuffer(data))
|
|
135
|
+
if err != nil {
|
|
136
|
+
return nil, err
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return c.PerformRequest(ctx, req)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func checkAndPrintError(res *http.Response) error {
|
|
143
|
+
if res.StatusCode != http.StatusOK {
|
|
144
|
+
defer res.Body.Close()
|
|
145
|
+
body, err := ioutil.ReadAll(res.Body)
|
|
146
|
+
if err != nil {
|
|
147
|
+
return err
|
|
148
|
+
}
|
|
149
|
+
response := &ErrorResponse{}
|
|
150
|
+
err = json.Unmarshal(body, &response)
|
|
151
|
+
if err != nil {
|
|
152
|
+
// Not a valid JSON response, just use body
|
|
153
|
+
return fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, body)
|
|
154
|
+
}
|
|
155
|
+
if response.Message != "" {
|
|
156
|
+
return fmt.Errorf("error: %s", response.Message)
|
|
157
|
+
}
|
|
158
|
+
return fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, body)
|
|
159
|
+
}
|
|
160
|
+
return nil
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
func postprocessJsonResponse(res *http.Response, target interface{}) (interface{}, error) {
|
|
164
|
+
defer res.Body.Close()
|
|
165
|
+
body, err := ioutil.ReadAll(res.Body)
|
|
166
|
+
if err != nil {
|
|
167
|
+
return nil, err
|
|
168
|
+
}
|
|
169
|
+
err = json.Unmarshal(body, target)
|
|
170
|
+
return target, err
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func newHTTPClient(verbose bool, unixSocket string) *http.Client {
|
|
174
|
+
var httpTransport *http.Transport
|
|
175
|
+
|
|
176
|
+
if unixSocket != "" {
|
|
177
|
+
dialFunc := func(network, addr string) (net.Conn, error) {
|
|
178
|
+
return net.Dial("unix", unixSocket)
|
|
179
|
+
}
|
|
180
|
+
dialContext := func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
181
|
+
return net.Dial("unix", unixSocket)
|
|
182
|
+
}
|
|
183
|
+
httpTransport = &http.Transport{
|
|
184
|
+
DialContext: dialContext,
|
|
185
|
+
DialTLS: dialFunc,
|
|
186
|
+
ResponseHeaderTimeout: 30 * time.Second,
|
|
187
|
+
ExpectContinueTimeout: 10 * time.Second,
|
|
188
|
+
TLSHandshakeTimeout: 10 * time.Second,
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
httpTransport = &http.Transport{
|
|
192
|
+
Proxy: http.ProxyFromEnvironment,
|
|
193
|
+
DialContext: (&net.Dialer{
|
|
194
|
+
Timeout: 30 * time.Second,
|
|
195
|
+
KeepAlive: 30 * time.Second,
|
|
196
|
+
}).DialContext,
|
|
197
|
+
TLSHandshakeTimeout: 10 * time.Second,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
tr := &verboseTransport{
|
|
202
|
+
Transport: httpTransport,
|
|
203
|
+
Verbose: verbose,
|
|
204
|
+
Out: os.Stderr,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return &http.Client{
|
|
208
|
+
Transport: tr,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
"io/ioutil"
|
|
7
|
+
"net/http"
|
|
8
|
+
"net/http/httptest"
|
|
9
|
+
"net/url"
|
|
10
|
+
"strings"
|
|
11
|
+
"testing"
|
|
12
|
+
|
|
13
|
+
"github.com/stretchr/testify/require"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
func TestPerformRequest_ParamsEncoding_Delete(t *testing.T) {
|
|
17
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
18
|
+
require.Equal(t, "/delete", r.URL.Path)
|
|
19
|
+
require.Equal(t, "key_a=value_a&key_b=value_b", r.URL.RawQuery)
|
|
20
|
+
|
|
21
|
+
body, err := ioutil.ReadAll(r.Body)
|
|
22
|
+
require.NoError(t, err)
|
|
23
|
+
require.Equal(t, "", string(body))
|
|
24
|
+
}))
|
|
25
|
+
defer ts.Close()
|
|
26
|
+
|
|
27
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
28
|
+
client := Client{
|
|
29
|
+
BaseURL: baseURL,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
params := url.Values{}
|
|
33
|
+
params.Add("key_a", "value_a")
|
|
34
|
+
params.Add("key_b", "value_b")
|
|
35
|
+
|
|
36
|
+
req := &http.Request{
|
|
37
|
+
Method: http.MethodDelete,
|
|
38
|
+
URL: &url.URL{
|
|
39
|
+
Scheme: baseURL.Scheme,
|
|
40
|
+
Host: baseURL.Host,
|
|
41
|
+
Path: "/delete",
|
|
42
|
+
RawQuery: params.Encode(),
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
46
|
+
require.NoError(t, err)
|
|
47
|
+
|
|
48
|
+
defer resp.Body.Close()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func TestPerformRequest_ParamsEncoding_Get(t *testing.T) {
|
|
52
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
53
|
+
require.Equal(t, "/get", r.URL.Path)
|
|
54
|
+
require.Equal(t, "key_a=value_a&key_b=value_b", r.URL.RawQuery)
|
|
55
|
+
|
|
56
|
+
body, err := ioutil.ReadAll(r.Body)
|
|
57
|
+
require.NoError(t, err)
|
|
58
|
+
require.Equal(t, "", string(body))
|
|
59
|
+
}))
|
|
60
|
+
defer ts.Close()
|
|
61
|
+
|
|
62
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
63
|
+
client := Client{
|
|
64
|
+
BaseURL: baseURL,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
params := url.Values{}
|
|
68
|
+
params.Add("key_a", "value_a")
|
|
69
|
+
params.Add("key_b", "value_b")
|
|
70
|
+
|
|
71
|
+
req := &http.Request{
|
|
72
|
+
Method: http.MethodGet,
|
|
73
|
+
URL: &url.URL{
|
|
74
|
+
Scheme: baseURL.Scheme,
|
|
75
|
+
Host: baseURL.Host,
|
|
76
|
+
Path: "/get",
|
|
77
|
+
RawQuery: params.Encode(),
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
82
|
+
require.NoError(t, err)
|
|
83
|
+
|
|
84
|
+
defer resp.Body.Close()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func TestPerformRequest_ParamsEncoding_Post(t *testing.T) {
|
|
88
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
89
|
+
require.Equal(t, "/post", r.URL.Path)
|
|
90
|
+
require.Equal(t, "", r.URL.RawQuery)
|
|
91
|
+
|
|
92
|
+
body, err := ioutil.ReadAll(r.Body)
|
|
93
|
+
require.NoError(t, err)
|
|
94
|
+
require.Equal(t, "key_a=value_a&key_b=value_b", string(body))
|
|
95
|
+
}))
|
|
96
|
+
defer ts.Close()
|
|
97
|
+
|
|
98
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
99
|
+
client := Client{
|
|
100
|
+
BaseURL: baseURL,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
params := url.Values{}
|
|
104
|
+
params.Add("key_a", "value_a")
|
|
105
|
+
params.Add("key_b", "value_b")
|
|
106
|
+
|
|
107
|
+
req := &http.Request{
|
|
108
|
+
Method: http.MethodPost,
|
|
109
|
+
URL: &url.URL{
|
|
110
|
+
Scheme: baseURL.Scheme,
|
|
111
|
+
Host: baseURL.Host,
|
|
112
|
+
Path: "/post",
|
|
113
|
+
},
|
|
114
|
+
Body: io.NopCloser(strings.NewReader(params.Encode())),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
118
|
+
require.NoError(t, err)
|
|
119
|
+
|
|
120
|
+
defer resp.Body.Close()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func TestPerformRequest_ApiKey_Provided(t *testing.T) {
|
|
124
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
125
|
+
require.Equal(t, "Basic c2tfdGVzdF8xMjM0Og==", r.Header.Get("Authorization"))
|
|
126
|
+
}))
|
|
127
|
+
defer ts.Close()
|
|
128
|
+
|
|
129
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
130
|
+
client := Client{
|
|
131
|
+
BaseURL: baseURL,
|
|
132
|
+
APIKey: "sk_test_1234",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
req := &http.Request{
|
|
136
|
+
Method: http.MethodGet,
|
|
137
|
+
URL: &url.URL{
|
|
138
|
+
Scheme: baseURL.Scheme,
|
|
139
|
+
Host: baseURL.Host,
|
|
140
|
+
Path: "/get",
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
145
|
+
require.NoError(t, err)
|
|
146
|
+
|
|
147
|
+
defer resp.Body.Close()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func TestPerformRequest_ApiKey_Omitted(t *testing.T) {
|
|
151
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
152
|
+
require.Equal(t, "", r.Header.Get("Authorization"))
|
|
153
|
+
}))
|
|
154
|
+
defer ts.Close()
|
|
155
|
+
|
|
156
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
157
|
+
client := Client{
|
|
158
|
+
BaseURL: baseURL,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
req := &http.Request{
|
|
162
|
+
Method: http.MethodGet,
|
|
163
|
+
URL: &url.URL{
|
|
164
|
+
Scheme: baseURL.Scheme,
|
|
165
|
+
Host: baseURL.Host,
|
|
166
|
+
Path: "/get",
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
171
|
+
require.NoError(t, err)
|
|
172
|
+
|
|
173
|
+
defer resp.Body.Close()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func TestPerformRequest_ConfigureFunc(t *testing.T) {
|
|
177
|
+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
178
|
+
require.Equal(t, "2019-07-10", r.Header.Get("Hookdeck-Version"))
|
|
179
|
+
}))
|
|
180
|
+
defer ts.Close()
|
|
181
|
+
|
|
182
|
+
baseURL, _ := url.Parse(ts.URL)
|
|
183
|
+
client := Client{
|
|
184
|
+
BaseURL: baseURL,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
req := &http.Request{
|
|
188
|
+
Method: http.MethodGet,
|
|
189
|
+
Header: http.Header{
|
|
190
|
+
"Hookdeck-Version": []string{"2019-07-10"},
|
|
191
|
+
},
|
|
192
|
+
URL: &url.URL{
|
|
193
|
+
Scheme: baseURL.Scheme,
|
|
194
|
+
Host: baseURL.Host,
|
|
195
|
+
Path: "/get",
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
resp, err := client.PerformRequest(context.TODO(), req)
|
|
200
|
+
require.NoError(t, err)
|
|
201
|
+
|
|
202
|
+
defer resp.Body.Close()
|
|
203
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io/ioutil"
|
|
8
|
+
"net/http"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
type Connection struct {
|
|
12
|
+
Id string
|
|
13
|
+
Alias string
|
|
14
|
+
Label string
|
|
15
|
+
Destination Destination
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CreateConnectionInput struct {
|
|
19
|
+
Alias string `json:"alias"`
|
|
20
|
+
Label string `json:"label"`
|
|
21
|
+
SourceId string `json:"source_id"`
|
|
22
|
+
Destination CreateDestinationInput `json:"destination"`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ConnectionList struct {
|
|
26
|
+
Count int
|
|
27
|
+
Models []Connection
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func (c *Client) ListConnectionsBySource(source_id string) ([]Connection, error) {
|
|
31
|
+
res, err := c.Get(context.Background(), "/connections", "source_id="+source_id, nil)
|
|
32
|
+
if err != nil {
|
|
33
|
+
return []Connection{}, err
|
|
34
|
+
}
|
|
35
|
+
if res.StatusCode != http.StatusOK {
|
|
36
|
+
return []Connection{}, fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, err)
|
|
37
|
+
}
|
|
38
|
+
sources := ConnectionList{}
|
|
39
|
+
postprocessJsonResponse(res, &sources)
|
|
40
|
+
|
|
41
|
+
return sources.Models, nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func (c *Client) CreateConnection(input CreateConnectionInput) (Connection, error) {
|
|
45
|
+
input_bytes, err := json.Marshal(input)
|
|
46
|
+
if err != nil {
|
|
47
|
+
return Connection{}, err
|
|
48
|
+
}
|
|
49
|
+
res, err := c.Post(context.Background(), "/connections", input_bytes, nil)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return Connection{}, err
|
|
52
|
+
}
|
|
53
|
+
if res.StatusCode != http.StatusOK {
|
|
54
|
+
defer res.Body.Close()
|
|
55
|
+
body, _ := ioutil.ReadAll(res.Body)
|
|
56
|
+
return Connection{}, fmt.Errorf("Unexpected http status code: %d %s", res.StatusCode, string(body))
|
|
57
|
+
}
|
|
58
|
+
source := Connection{}
|
|
59
|
+
postprocessJsonResponse(res, &source)
|
|
60
|
+
return source, nil
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
type Destination struct {
|
|
4
|
+
Id string `json:"id"`
|
|
5
|
+
Alias string `json:"alias"`
|
|
6
|
+
Label string `json:"label"`
|
|
7
|
+
CliPath string `json:"cli_path"`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type CreateDestinationInput struct {
|
|
11
|
+
Alias string `json:"alias"`
|
|
12
|
+
Label string `json:"label"`
|
|
13
|
+
CliPath string `json:"cli_path"`
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net/http"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type GuestUser struct {
|
|
11
|
+
Id string `json:"id"`
|
|
12
|
+
APIKey string `json:"key"`
|
|
13
|
+
Url string `json:"link"`
|
|
14
|
+
BrowserURL string `json:"browser_url"`
|
|
15
|
+
PollURL string `json:"poll_url"`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CreateGuestUserInput struct {
|
|
19
|
+
DeviceName string `json:"device_name"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func (c *Client) CreateGuestUser(input CreateGuestUserInput) (GuestUser, error) {
|
|
23
|
+
input_bytes, err := json.Marshal(input)
|
|
24
|
+
if err != nil {
|
|
25
|
+
return GuestUser{}, err
|
|
26
|
+
}
|
|
27
|
+
res, err := c.Post(context.Background(), "/cli/guest", input_bytes, nil)
|
|
28
|
+
if err != nil {
|
|
29
|
+
return GuestUser{}, err
|
|
30
|
+
}
|
|
31
|
+
if res.StatusCode != http.StatusOK {
|
|
32
|
+
return GuestUser{}, fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, err)
|
|
33
|
+
}
|
|
34
|
+
guest_user := GuestUser{}
|
|
35
|
+
postprocessJsonResponse(res, &guest_user)
|
|
36
|
+
return guest_user, nil
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package hookdeck
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io/ioutil"
|
|
8
|
+
"net/http"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
type Session struct {
|
|
12
|
+
Id string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type CreateSessionInput struct {
|
|
16
|
+
SourceId string `json:"source_id"`
|
|
17
|
+
ConnectionIds []string `json:"webhook_ids"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func (c *Client) CreateSession(input CreateSessionInput) (Session, error) {
|
|
21
|
+
input_bytes, err := json.Marshal(input)
|
|
22
|
+
if err != nil {
|
|
23
|
+
return Session{}, err
|
|
24
|
+
}
|
|
25
|
+
res, err := c.Post(context.Background(), "/cli-sessions", input_bytes, nil)
|
|
26
|
+
if err != nil {
|
|
27
|
+
return Session{}, err
|
|
28
|
+
}
|
|
29
|
+
if res.StatusCode != http.StatusOK {
|
|
30
|
+
defer res.Body.Close()
|
|
31
|
+
body, _ := ioutil.ReadAll(res.Body)
|
|
32
|
+
return Session{}, fmt.Errorf("Unexpected http status code: %d %s", res.StatusCode, string(body))
|
|
33
|
+
}
|
|
34
|
+
session := Session{}
|
|
35
|
+
postprocessJsonResponse(res, &session)
|
|
36
|
+
return session, nil
|
|
37
|
+
}
|