Skip to content

Commit

Permalink
Merge pull request #92 from hookdeck/feat/cli-path-non-interactive
Browse files Browse the repository at this point in the history
Adds --cli-path and changes behavior to remove connection name and path terminal interactivity
  • Loading branch information
leggetter authored Jul 25, 2024
2 parents 06cb5a0 + b7e63d5 commit 42fb1e0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 42 deletions.
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

0 comments on commit 42fb1e0

Please sign in to comment.