Skip to content

Commit c51251b

Browse files
authored
Merge pull request #84 from hookdeck/listen-multiple
v0.2.0
2 parents 29564b2 + 6dd2063 commit c51251b

File tree

12 files changed

+499
-259
lines changed

12 files changed

+499
-259
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/fsnotify/fsnotify v1.4.9 // indirect
2929
github.com/google/go-querystring v1.0.0 // indirect
3030
github.com/hashicorp/hcl v1.0.0 // indirect
31-
github.com/hookdeck/hookdeck-go-sdk v0.0.37 // indirect
31+
github.com/hookdeck/hookdeck-go-sdk v0.4.1 // indirect
3232
github.com/inconshreveable/mousetrap v1.0.0 // indirect
3333
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
3434
github.com/kr/pty v1.1.8 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG
128128
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
129129
github.com/hookdeck/hookdeck-go-sdk v0.0.37 h1:Y+QnwsWuJ6KMkpY2qJZDeGzcKc4GkzBrRaEnIb8zimc=
130130
github.com/hookdeck/hookdeck-go-sdk v0.0.37/go.mod h1:kfFn3/WEGcxuPkaaf8lAq9L+3nYg45GwGy4utH/Tnmg=
131+
github.com/hookdeck/hookdeck-go-sdk v0.4.1 h1:r/rZJeBuDq31amTIB1LDHkA5lTAG2jAmZGqhgHRYKy8=
132+
github.com/hookdeck/hookdeck-go-sdk v0.4.1/go.mod h1:kfFn3/WEGcxuPkaaf8lAq9L+3nYg45GwGy4utH/Tnmg=
131133
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
132134
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
133135
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=

pkg/cmd/completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func selectShell(shell string) error {
110110
}
111111
return err
112112
default:
113-
return fmt.Errorf("Could not automatically detect your shell. Please run the command with the `--shell` flag for either bash or zsh")
113+
return fmt.Errorf("could not automatically detect your shell. Please run the command with the `--shell` flag for either bash or zsh")
114114
}
115115
}
116116

pkg/cmd/listen.go

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package cmd
1717

1818
import (
1919
"errors"
20+
"fmt"
2021
"net/url"
2122
"strconv"
2223
"strings"
@@ -26,9 +27,9 @@ import (
2627
)
2728

2829
type listenCmd struct {
29-
cmd *cobra.Command
30-
wsBaseURL string
31-
noWSS bool
30+
cmd *cobra.Command
31+
noWSS bool
32+
cliPath string
3233
}
3334

3435
func newListenCmd() *listenCmd {
@@ -37,9 +38,15 @@ func newListenCmd() *listenCmd {
3738
lc.cmd = &cobra.Command{
3839
Use: "listen",
3940
Short: "Forward events for a source to your local server",
41+
Long: `Forward events for a source to your local server.
42+
43+
This command will create a new Hookdeck Source if it doesn't exist.
44+
45+
By default the Hookdeck Destination will be named "CLI", and the
46+
Destination CLI path will be "/". To set the CLI path, use the "--cli-path" flag.`,
4047
Args: func(cmd *cobra.Command, args []string) error {
4148
if len(args) < 1 {
42-
return errors.New("Requires a port or forwarding URL to forward the events to")
49+
return errors.New("requires a port or forwarding URL to forward the events to")
4350
}
4451

4552
_, err_port := strconv.ParseInt(args[0], 10, 64)
@@ -53,44 +60,72 @@ func newListenCmd() *listenCmd {
5360
}
5461

5562
if err_port != nil && err_url != nil {
56-
return errors.New("Argument is not a valid port or forwading URL")
63+
return errors.New("argument is not a valid port or forwading URL")
5764
}
5865

5966
if err_port != nil {
6067
if parsed_url.Host == "" {
61-
return errors.New("Forwarding URL must contain a host.")
68+
return errors.New("forwarding URL must contain a host")
6269
}
6370

6471
if parsed_url.RawQuery != "" {
65-
return errors.New("Forwarding URL cannot contain query params.")
72+
return errors.New("forwarding URL cannot contain query params")
6673
}
6774
}
6875

6976
if len(args) > 3 {
70-
return errors.New("Invalid extra argument provided")
77+
return errors.New("invalid extra argument provided")
7178
}
7279

7380
return nil
7481
},
7582
RunE: lc.runListenCmd,
7683
}
7784
lc.cmd.Flags().BoolVar(&lc.noWSS, "no-wss", false, "Force unencrypted ws:// protocol instead of wss://")
85+
lc.cmd.Flags().MarkHidden("no-wss")
86+
lc.cmd.Flags().StringVar(&lc.cliPath, "cli-path", "", "Sets the server path of that locally running web server the events will be forwarded to")
87+
88+
usage := lc.cmd.UsageTemplate()
89+
90+
usage = strings.Replace(
91+
usage,
92+
"{{.UseLine}}",
93+
`hookdeck listen [port or forwarding URL] [source] [connection] [flags]
94+
95+
Arguments:
96+
97+
- [port or forwarding URL]: Required. The port or forwarding URL to forward the events to e.g., "3000" or "http://localhost:3000"
98+
- [source]: Required. The name of source to forward the events from e.g., "shopify", "stripe"
99+
- [connection]: Optional. The name of the connection linking the Source and the Destination
100+
`, 1)
101+
102+
usage += fmt.Sprintf(`
103+
104+
Examples:
105+
106+
Forward events from a Hookdeck Source named "shopify" to a local server running on port %[1]d:
107+
108+
hookdeck listen %[1]d shopify
109+
110+
Forward events to a local server running on "http://myapp.test":
111+
112+
hookdeck listen %[1]d http://myapp.test
113+
114+
Forward events to the path "/webhooks" on local server running on port %[1]d:
115+
116+
hookdeck listen %[1]d --cli-path /webhooks
117+
`, 3000)
78118

79-
lc.cmd.SetUsageTemplate(
80-
strings.Replace(
81-
lc.cmd.UsageTemplate(),
82-
"{{.UseLine}}",
83-
"hookdeck listen [port or forwarding URL] [source] [connection] [flags]", 1),
84-
)
119+
lc.cmd.SetUsageTemplate(usage)
85120

86121
return lc
87122
}
88123

89124
// listenCmd represents the listen command
90125
func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
91-
var sourceAlias, connectionQuery string
126+
var sourceQuery, connectionQuery string
92127
if len(args) > 1 {
93-
sourceAlias = args[1]
128+
sourceQuery = args[1]
94129
}
95130
if len(args) > 2 {
96131
connectionQuery = args[2]
@@ -112,7 +147,8 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
112147
url.Scheme = "http"
113148
}
114149

115-
return listen.Listen(url, sourceAlias, connectionQuery, listen.Flags{
116-
NoWSS: lc.noWSS,
150+
return listen.Listen(url, sourceQuery, connectionQuery, listen.Flags{
151+
NoWSS: lc.noWSS,
152+
CliPath: lc.cliPath,
117153
}, &Config)
118154
}

pkg/cmd/root.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import (
2828
"github.com/spf13/cobra"
2929
)
3030

31-
var cfgFile string
32-
3331
var Config config.Config
3432

3533
var rootCmd = &cobra.Command{

pkg/cmd/whoami.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import (
1111
)
1212

1313
type whoamiCmd struct {
14-
cmd *cobra.Command
15-
interactive bool
14+
cmd *cobra.Command
1615
}
1716

1817
func newWhoamiCmd() *whoamiCmd {

pkg/hookdeck/session.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ type Session struct {
1313
}
1414

1515
type CreateSessionInput struct {
16-
SourceId string `json:"source_id"`
1716
ConnectionIds []string `json:"webhook_ids"`
1817
}
1918

@@ -29,7 +28,7 @@ func (c *Client) CreateSession(input CreateSessionInput) (Session, error) {
2928
if res.StatusCode != http.StatusOK {
3029
defer res.Body.Close()
3130
body, _ := ioutil.ReadAll(res.Body)
32-
return Session{}, fmt.Errorf("Unexpected http status code: %d %s", res.StatusCode, string(body))
31+
return Session{}, fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, string(body))
3332
}
3433
session := Session{}
3534
postprocessJsonResponse(res, &session)

pkg/listen/connection.go

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,114 @@ package listen
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"strings"
87

9-
"github.com/AlecAivazis/survey/v2"
108
"github.com/gosimple/slug"
119
hookdecksdk "github.com/hookdeck/hookdeck-go-sdk"
1210
hookdeckclient "github.com/hookdeck/hookdeck-go-sdk/client"
11+
log "github.com/sirupsen/logrus"
1312
)
1413

15-
func getConnections(client *hookdeckclient.Client, source *hookdecksdk.Source, connectionQuery string) ([]*hookdecksdk.Connection, error) {
16-
// TODO: Filter connections using connectionQuery
17-
var connections []*hookdecksdk.Connection
18-
connectionList, err := client.Connection.List(context.Background(), &hookdecksdk.ConnectionListRequest{
19-
SourceId: &source.Id,
14+
func getConnections(client *hookdeckclient.Client, sources []*hookdecksdk.Source, connectionFilterString string, isMultiSource bool, cliPath string) ([]*hookdecksdk.Connection, error) {
15+
sourceIDs := []*string{}
16+
17+
for _, source := range sources {
18+
sourceIDs = append(sourceIDs, &source.Id)
19+
}
20+
21+
connectionQuery, err := client.Connection.List(context.Background(), &hookdecksdk.ConnectionListRequest{
22+
SourceId: sourceIDs,
2023
})
2124
if err != nil {
22-
return nil, err
25+
return []*hookdecksdk.Connection{}, err
2326
}
24-
connections = connectionList.Models
2527

26-
var filteredConnections []*hookdecksdk.Connection
28+
connections, err := filterConnections(connectionQuery.Models, connectionFilterString)
29+
if err != nil {
30+
return []*hookdecksdk.Connection{}, err
31+
}
32+
33+
connections, err = ensureConnections(client, connections, sources, isMultiSource, connectionFilterString, cliPath)
34+
if err != nil {
35+
return []*hookdecksdk.Connection{}, err
36+
}
37+
38+
return connections, nil
39+
}
40+
41+
// 1. Filter to only include CLI destination
42+
// 2. Apply connectionFilterString
43+
func filterConnections(connections []*hookdecksdk.Connection, connectionFilterString string) ([]*hookdecksdk.Connection, error) {
44+
// 1. Filter to only include CLI destination
45+
var cliDestinationConnections []*hookdecksdk.Connection
2746
for _, connection := range connections {
2847
if connection.Destination.CliPath != nil && *connection.Destination.CliPath != "" {
29-
filteredConnections = append(filteredConnections, connection)
48+
cliDestinationConnections = append(cliDestinationConnections, connection)
3049
}
3150
}
32-
connections = filteredConnections
3351

34-
if connectionQuery != "" {
35-
is_path, err := isPath(connectionQuery)
36-
if err != nil {
37-
return connections, err
38-
}
39-
var filteredConnections []*hookdecksdk.Connection
40-
for _, connection := range connections {
41-
if (is_path && connection.Destination.CliPath != nil && strings.Contains(*connection.Destination.CliPath, connectionQuery)) || (connection.Name != nil && *connection.Name == connectionQuery) {
42-
filteredConnections = append(filteredConnections, connection)
43-
}
44-
}
45-
connections = filteredConnections
52+
if connectionFilterString == "" {
53+
return cliDestinationConnections, nil
4654
}
4755

48-
if len(connections) == 0 {
49-
answers := struct {
50-
Label string `survey:"label"`
51-
Path string `survey:"path"`
52-
}{}
53-
var qs = []*survey.Question{
54-
{
55-
Name: "path",
56-
Prompt: &survey.Input{Message: "What path should the events be forwarded to (ie: /webhooks)?"},
57-
Validate: func(val interface{}) error {
58-
str, ok := val.(string)
59-
is_path, err := isPath(str)
60-
if !ok || !is_path || err != nil {
61-
return errors.New("invalid path")
62-
}
63-
return nil
64-
},
65-
},
66-
{
67-
Name: "label",
68-
Prompt: &survey.Input{Message: "What's your connection label (ie: My API)?"},
69-
Validate: survey.Required,
70-
},
56+
// 2. Apply connectionFilterString
57+
isPath, err := isPath(connectionFilterString)
58+
if err != nil {
59+
return connections, err
60+
}
61+
var filteredConnections []*hookdecksdk.Connection
62+
for _, connection := range cliDestinationConnections {
63+
if (isPath && connection.Destination.CliPath != nil && strings.Contains(*connection.Destination.CliPath, connectionFilterString)) || (connection.Name != nil && *connection.Name == connectionFilterString) {
64+
filteredConnections = append(filteredConnections, connection)
7165
}
66+
}
7267

73-
err := survey.Ask(qs, &answers)
74-
if err != nil {
75-
fmt.Println(err.Error())
76-
return connections, err
77-
}
78-
alias := slug.Make(answers.Label)
79-
connection, err := client.Connection.Create(context.Background(), &hookdecksdk.ConnectionCreateRequest{
80-
Name: hookdecksdk.OptionalOrNull(&alias),
81-
SourceId: hookdecksdk.OptionalOrNull(&source.Id),
82-
Destination: hookdecksdk.OptionalOrNull(&hookdecksdk.ConnectionCreateRequestDestination{
83-
Name: alias,
84-
CliPath: &answers.Path,
85-
}),
86-
})
87-
if err != nil {
88-
return connections, err
89-
}
90-
connections = append(connections, connection)
68+
return filteredConnections, nil
69+
}
70+
71+
// When users want to listen to a single source but there is no connection for that source,
72+
// we can help user set up a new connection for it.
73+
func ensureConnections(client *hookdeckclient.Client, connections []*hookdecksdk.Connection, sources []*hookdecksdk.Source, isMultiSource bool, connectionFilterString string, cliPath string) ([]*hookdecksdk.Connection, error) {
74+
if len(connections) > 0 || isMultiSource {
75+
log.Debug(fmt.Sprintf("Connection exists for Source \"%s\", Connection \"%s\", and CLI path \"%s\"", sources[0].Name, connectionFilterString, cliPath))
76+
77+
return connections, nil
78+
}
79+
80+
log.Debug(fmt.Sprintf("No connection found. Creating a connection for Source \"%s\", Connection \"%s\", and CLI path \"%s\"", sources[0].Name, connectionFilterString, cliPath))
81+
82+
connectionDetails := struct {
83+
Label string `survey:"label"`
84+
Path string `survey:"path"`
85+
}{}
86+
87+
if len(connectionFilterString) == 0 {
88+
connectionDetails.Label = "cli"
89+
} else {
90+
connectionDetails.Label = connectionFilterString
91+
}
92+
93+
if len(cliPath) == 0 {
94+
connectionDetails.Path = "/"
95+
} else {
96+
connectionDetails.Path = cliPath
97+
}
98+
99+
alias := slug.Make(connectionDetails.Label)
100+
101+
connection, err := client.Connection.Create(context.Background(), &hookdecksdk.ConnectionCreateRequest{
102+
Name: hookdecksdk.OptionalOrNull(&alias),
103+
SourceId: hookdecksdk.OptionalOrNull(&sources[0].Id),
104+
Destination: hookdecksdk.OptionalOrNull(&hookdecksdk.ConnectionCreateRequestDestination{
105+
Name: alias,
106+
CliPath: &connectionDetails.Path,
107+
}),
108+
})
109+
if err != nil {
110+
return connections, err
91111
}
112+
connections = append(connections, connection)
92113

93114
return connections, nil
94115
}

0 commit comments

Comments
 (0)