Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds --cli-path and changes behavior to remove connection name and path terminal interactivity #92

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions pkg/cmd/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd

import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
Expand All @@ -26,9 +27,9 @@ import (
)

type listenCmd struct {
cmd *cobra.Command
wsBaseURL string
noWSS bool
cmd *cobra.Command
noWSS bool
cliPath string
}

func newListenCmd() *listenCmd {
Expand All @@ -37,6 +38,12 @@ func newListenCmd() *listenCmd {
lc.cmd = &cobra.Command{
Use: "listen",
Short: "Forward events for a source to your local server",
Long: `Forward events for a source to your local server.

This command will create a new Hookdeck Source if it doesn't exist.

By default the Hookdeck Destination will be named "CLI", and the
Destination CLI path will be "/". To set the CLI path, use the "--cli-path" flag.`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("Requires a port or forwarding URL to forward the events to")
Expand Down Expand Up @@ -75,13 +82,41 @@ func newListenCmd() *listenCmd {
RunE: lc.runListenCmd,
}
lc.cmd.Flags().BoolVar(&lc.noWSS, "no-wss", false, "Force unencrypted ws:// protocol instead of wss://")
lc.cmd.Flags().MarkHidden("no-wss")
lc.cmd.Flags().StringVar(&lc.cliPath, "cli-path", "", "Sets the server path of that locally running web server the events will be forwarded to")

usage := lc.cmd.UsageTemplate()

usage = strings.Replace(
usage,
"{{.UseLine}}",
`hookdeck listen [port or forwarding URL] [source] [connection] [flags]

Arguments:

- [port or forwarding URL]: Required. The port or forwarding URL to forward the events to e.g., "3000" or "http://localhost:3000"
- [source]: Required. The name of source to forward the events from e.g., "shopify", "stripe"
- [connection]: Optional. The name of the connection linking the Source and the Destination
`, 1)

usage += fmt.Sprintf(`

Examples:

Forward events from a Hookdeck Source named "shopify" to a local server running on port %[1]d:

hookdeck listen %[1]d shopify

Forward events to a local server running on "http://myapp.test":

hookdeck listen %[1]d http://myapp.test

Forward events to the path "/webhooks" on local server running on port %[1]d:

hookdeck listen %[1]d --cli-path /webhooks
`, 3000)

lc.cmd.SetUsageTemplate(
strings.Replace(
lc.cmd.UsageTemplate(),
"{{.UseLine}}",
"hookdeck listen [port or forwarding URL] [source] [connection] [flags]", 1),
)
lc.cmd.SetUsageTemplate(usage)

return lc
}
Expand Down Expand Up @@ -113,6 +148,7 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
}

return listen.Listen(url, sourceQuery, connectionQuery, listen.Flags{
NoWSS: lc.noWSS,
NoWSS: lc.noWSS,
CliPath: lc.cliPath,
}, &Config)
}
56 changes: 26 additions & 30 deletions pkg/listen/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package listen

import (
"context"
"errors"
"fmt"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/gosimple/slug"
hookdecksdk "github.com/hookdeck/hookdeck-go-sdk"
hookdeckclient "github.com/hookdeck/hookdeck-go-sdk/client"
log "github.com/sirupsen/logrus"
)

func getConnections(client *hookdeckclient.Client, sources []*hookdecksdk.Source, connectionFilterString string, isMultiSource bool) ([]*hookdecksdk.Connection, error) {
func getConnections(client *hookdeckclient.Client, sources []*hookdecksdk.Source, connectionFilterString string, isMultiSource bool, cliPath string) ([]*hookdecksdk.Connection, error) {
sourceIDs := []*string{}

for _, source := range sources {
Expand All @@ -31,7 +30,7 @@ func getConnections(client *hookdeckclient.Client, sources []*hookdecksdk.Source
return []*hookdecksdk.Connection{}, err
}

connections, err = ensureConnections(client, connections, sources, isMultiSource)
connections, err = ensureConnections(client, connections, sources, isMultiSource, connectionFilterString, cliPath)
if err != nil {
return []*hookdecksdk.Connection{}, err
}
Expand Down Expand Up @@ -71,47 +70,44 @@ func filterConnections(connections []*hookdecksdk.Connection, connectionFilterSt

// When users want to listen to a single source but there is no connection for that source,
// we can help user set up a new connection for it.
func ensureConnections(client *hookdeckclient.Client, connections []*hookdecksdk.Connection, sources []*hookdecksdk.Source, isMultiSource bool) ([]*hookdecksdk.Connection, error) {
func ensureConnections(client *hookdeckclient.Client, connections []*hookdecksdk.Connection, sources []*hookdecksdk.Source, isMultiSource bool, connectionFilterString string, cliPath string) ([]*hookdecksdk.Connection, error) {
l := log.StandardLogger()

if len(connections) > 0 || isMultiSource {
msg := fmt.Sprintf("Connection exists for Source \"%s\", Connection \"%s\", and CLI path \"%s\"", sources[0].Name, connectionFilterString, cliPath)
l.Debug(msg)

return connections, nil
}

answers := struct {
msg := fmt.Sprintf("No connection found. Creating a connection for Source \"%s\", Connection \"%s\", and CLI path \"%s\"", sources[0].Name, connectionFilterString, cliPath)
l.Debug(msg)

connectionDetails := struct {
Label string `survey:"label"`
Path string `survey:"path"`
}{}
var qs = []*survey.Question{
{
Name: "path",
Prompt: &survey.Input{Message: "What path should the events be forwarded to (ie: /webhooks)?"},
Validate: func(val interface{}) error {
str, ok := val.(string)
isPath, err := isPath(str)
if !ok || !isPath || err != nil {
return errors.New("invalid path")
}
return nil
},
},
{
Name: "label",
Prompt: &survey.Input{Message: "What's your connection label (ie: My API)?"},
Validate: survey.Required,
},

if len(connectionFilterString) == 0 {
connectionDetails.Label = "cli"
} else {
connectionDetails.Label = connectionFilterString
}

err := survey.Ask(qs, &answers)
if err != nil {
fmt.Println(err.Error())
return connections, err
if len(cliPath) == 0 {
connectionDetails.Path = "/"
} else {
connectionDetails.Path = cliPath
}
alias := slug.Make(answers.Label)

alias := slug.Make(connectionDetails.Label)

connection, err := client.Connection.Create(context.Background(), &hookdecksdk.ConnectionCreateRequest{
Name: hookdecksdk.OptionalOrNull(&alias),
SourceId: hookdecksdk.OptionalOrNull(&sources[0].Id),
Destination: hookdecksdk.OptionalOrNull(&hookdecksdk.ConnectionCreateRequestDestination{
Name: alias,
CliPath: &answers.Path,
CliPath: &connectionDetails.Path,
}),
})
if err != nil {
Expand Down
45 changes: 43 additions & 2 deletions pkg/listen/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import (
)

type Flags struct {
NoWSS bool
NoWSS bool
CliPath string
}

// listenCmd represents the listen command
Expand All @@ -46,6 +47,17 @@ func Listen(URL *url.URL, sourceQuery string, connectionFilterString string, fla

isMultiSource := len(sourceAliases) > 1 || (len(sourceAliases) == 1 && sourceAliases[0] == "*")

if flags.CliPath != "" {
if isMultiSource {
return errors.New("Can only set a CLI path when listening to a single source")
}

_, err = isPath(flags.CliPath)
if err != nil {
return errors.New("The CLI path must be in a valid format")
}
}

if config.Profile.APIKey == "" {
guestURL, err = login.GuestLogin(config)
if guestURL == "" {
Expand All @@ -62,11 +74,40 @@ func Listen(URL *url.URL, sourceQuery string, connectionFilterString string, fla
return err
}

connections, err := getConnections(sdkClient, sources, connectionFilterString, isMultiSource)
connections, err := getConnections(sdkClient, sources, connectionFilterString, isMultiSource, flags.CliPath)
if err != nil {
return err
}

if len(flags.CliPath) != 0 && len(connections) > 1 {
return errors.New(fmt.Errorf(`Multiple CLI destinations found. Cannot set the CLI path on multiple destinations.
Specify a single destination to update the CLI path. For example, pass a connection name:

hookdeck listen %s %s %s --cli-path %s`, URL.String(), sourceQuery, "connection-name", flags.CliPath).Error())
}

// If the "cli-path" flag has been passed and the destination has a current cli path value but it's different, update destination path
if len(flags.CliPath) != 0 &&
len(connections) == 1 &&
*connections[0].Destination.CliPath != "" &&
*connections[0].Destination.CliPath != flags.CliPath {

l := log.StandardLogger()
updateMsg := fmt.Sprintf("Updating destination CLI path from \"%s\" to \"%s\"", *connections[0].Destination.CliPath, flags.CliPath)
l.Debug(updateMsg)

path := flags.CliPath
_, err := sdkClient.Destination.Update(context.Background(), connections[0].Destination.Id, &hookdecksdk.DestinationUpdateRequest{
CliPath: hookdecksdk.Optional[string](path),
})

if err != nil {
return err
}

connections[0].Destination.CliPath = &path
}

sources = getRelevantSources(sources, connections)

if err := validateData(sources, connections); err != nil {
Expand Down