From 4b303c3d26a2d2075ee189bb0238d93ef1612cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 26 May 2023 16:15:38 +0200 Subject: [PATCH] :nail_care: --- main.go | 2 +- webhook/create_webhook.go | 29 ++++++----- webhook/forward.go | 104 ++++++++++++++++++-------------------- 3 files changed, 67 insertions(+), 68 deletions(-) diff --git a/main.go b/main.go index e002fc9..c3ec128 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( ) func main() { - if err := webhook.NewCmdForward(nil).Execute(); err != nil { + if err := webhook.NewCmdForward().Execute(); err != nil { os.Exit(1) } } diff --git a/webhook/create_webhook.go b/webhook/create_webhook.go index 0b73138..794b8ce 100644 --- a/webhook/create_webhook.go +++ b/webhook/create_webhook.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "strconv" "strings" "github.com/cli/go-gh/v2/pkg/api" @@ -37,28 +36,37 @@ type createHookResponse struct { WsURL string `json:"ws_url"` } +type hookOptions struct { + gitHubHost string + authToken string + eventTypes []string + repo string + org string + secret string +} + // createHook issues a request against the GitHub API to create a dev webhook func createHook(o *hookOptions) (string, func() error, error) { apiClient, err := api.NewRESTClient(api.ClientOptions{ - Host: o.GitHubHost, + Host: o.gitHubHost, AuthToken: o.authToken, }) if err != nil { - return "", nil, fmt.Errorf("error creating rest client: %w", err) + return "", nil, fmt.Errorf("error creating REST client: %w", err) } - path := fmt.Sprintf("repos/%s/hooks", o.Repo) - if o.Org != "" { - path = fmt.Sprintf("orgs/%s/hooks", o.Org) + path := fmt.Sprintf("repos/%s/hooks", o.repo) + if o.org != "" { + path = fmt.Sprintf("orgs/%s/hooks", o.org) } req := createHookRequest{ Name: "cli", - Events: o.EventTypes, + Events: o.eventTypes, Active: false, Config: hookConfig{ ContentType: "json", InsecureSSL: "0", - Secret: o.Secret, + Secret: o.secret, }, } @@ -76,11 +84,8 @@ func createHook(o *hookOptions) (string, func() error, error) { return "", nil, fmt.Errorf("error creating webhook: %w", err) } - // reset path for activation. - path += "/" + strconv.Itoa(res.ID) - return res.WsURL, func() error { - err = apiClient.Patch(path, strings.NewReader(`{"active": true}`), nil) + err := apiClient.Patch(res.URL, strings.NewReader(`{"active": true}`), nil) if err != nil { return fmt.Errorf("error activating webhook: %w", err) } diff --git a/webhook/forward.go b/webhook/forward.go index d7fd2a4..b64d527 100644 --- a/webhook/forward.go +++ b/webhook/forward.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "os" "strings" @@ -16,24 +15,17 @@ import ( "github.com/spf13/cobra" ) -type hookOptions struct { - Out io.Writer - ErrOut io.Writer - GitHubHost string - authToken string - EventTypes []string - Repo string - Org string - Secret string -} - // NewCmdForward returns a forward command. -func NewCmdForward(runF func(*hookOptions) error) *cobra.Command { - var localURL string - opts := &hookOptions{ - Out: os.Stdout, - ErrOut: os.Stderr, - } +func NewCmdForward() *cobra.Command { + var ( + localURL string + eventTypes []string + targetRepo string + targetOrg string + githubHost string + webhookSecret string + ) + cmd := &cobra.Command{ Use: "forward --events= [--url=]", Short: "Receive test events locally", @@ -45,47 +37,39 @@ func NewCmdForward(runF func(*hookOptions) error) *cobra.Command { $ gh webhook forward --events=issues --org=github --url="http://localhost:9999/webhooks" `), RunE: func(*cobra.Command, []string) error { - if opts.Repo == "" && opts.Org == "" { + if targetRepo == "" && targetOrg == "" { return errors.New("`--repo` or `--org` flag required") } - if localURL == "" { - fmt.Fprintln(opts.ErrOut, "note: no `--url` specified; printing webhook payloads to stdout") - } - - if runF != nil { - return runF(opts) - } - - var err error - opts.authToken, err = authTokenForHost(opts.GitHubHost) + authToken, err := authTokenForHost(githubHost) if err != nil { return fmt.Errorf("fatal: error fetching gh token: %w", err) - } else if opts.authToken == "" { - return errors.New("fatal: you must be authenticated with gh to run this command") } - wsURL, activate, err := createHook(opts) + wsURL, activate, err := createHook(&hookOptions{ + gitHubHost: githubHost, + eventTypes: eventTypes, + authToken: authToken, + repo: targetRepo, + org: targetOrg, + secret: webhookSecret, + }) if err != nil { return err } - for i := 0; i < 3; i++ { - if err = runFwd(opts.Out, localURL, opts.authToken, wsURL, activate); err != nil { - if websocket.IsCloseError(err, websocket.CloseNormalClosure) { - return nil - } - } - } - return err + + return runFwd(os.Stdout, localURL, authToken, wsURL, activate) }, } - cmd.Flags().StringSliceVarP(&opts.EventTypes, "events", "E", nil, "Names of the event `types` to forward. Use `*` to forward all events.") - cmd.MarkFlagRequired("events") - cmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Name of the repo where the webhook is installed") - cmd.Flags().StringVarP(&opts.GitHubHost, "github-host", "H", "github.com", "GitHub host name") + + cmd.Flags().StringSliceVarP(&eventTypes, "events", "E", nil, "Names of the event `types` to forward. Use `*` to forward all events.") + _ = cmd.MarkFlagRequired("events") + cmd.Flags().StringVarP(&targetRepo, "repo", "R", "", "Name of the repo where the webhook is installed") + cmd.Flags().StringVarP(&githubHost, "github-host", "H", "github.com", "GitHub host name") cmd.Flags().StringVarP(&localURL, "url", "U", "", "Address of the local server to receive events. If omitted, events will be printed to stdout.") - cmd.Flags().StringVarP(&opts.Org, "org", "O", "", "Name of the org where the webhook is installed") - cmd.Flags().StringVarP(&opts.Secret, "secret", "S", "", "Webhook secret for incoming events") + cmd.Flags().StringVarP(&targetOrg, "org", "O", "", "Name of the org where the webhook is installed") + cmd.Flags().StringVarP(&webhookSecret, "secret", "S", "", "Webhook secret for incoming events") + return cmd } @@ -95,6 +79,9 @@ type wsEventReceived struct { } func runFwd(out io.Writer, url, token, wsURL string, activateHook func() error) error { + if url == "" { + fmt.Fprintln(os.Stderr, "notice: no `--url` specified; printing webhook payloads to stdout") + } for i := 0; i < 3; i++ { err := handleWebsocket(out, url, token, wsURL, activateHook) if err != nil { @@ -102,6 +89,8 @@ func runFwd(out io.Writer, url, token, wsURL string, activateHook func() error) if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) { time.Sleep(5 * time.Second) continue + } else if websocket.IsCloseError(err, websocket.CloseNormalClosure) { + return nil } return err } @@ -117,7 +106,7 @@ func handleWebsocket(out io.Writer, url, token, wsURL string, activateHook func( } defer c.Close() - fmt.Fprintf(out, "Forwarding Webhook events from GitHub...\n") + fmt.Fprintln(os.Stderr, "Forwarding Webhook events from GitHub...") if err := activateHook(); err != nil { return fmt.Errorf("error activating hook: %w", err) } @@ -129,9 +118,9 @@ func handleWebsocket(out io.Writer, url, token, wsURL string, activateHook func( return fmt.Errorf("error receiving json event: %w", err) } - resp, err := forwardEvent(url, ev) + resp, err := forwardEvent(out, url, ev) if err != nil { - fmt.Fprintf(out, "Error forwarding event: %v\n", err) + fmt.Fprintf(os.Stderr, "warning: error forwarding event: %v\n", err) continue } @@ -164,13 +153,18 @@ type httpEventForward struct { } // forwardEvent forwards events to the server running on the local port specified by the user -func forwardEvent(url string, ev wsEventReceived) (*httpEventForward, error) { - event := ev.Header.Get("X-GitHub-Event") - event = strings.ReplaceAll(event, "\n", "") - event = strings.ReplaceAll(event, "\r", "") - log.Printf("[LOG] received the following event: %v \n", event) +func forwardEvent(w io.Writer, url string, ev wsEventReceived) (*httpEventForward, error) { if url == "" { - fmt.Printf("%s\n", ev.Body) + event := ev.Header.Get("X-GitHub-Event") + event = strings.ReplaceAll(event, "\n", "") + event = strings.ReplaceAll(event, "\r", "") + fmt.Fprintf(os.Stderr, "[LOG] received event %q\n", event) + if _, err := w.Write(ev.Body); err != nil { + return nil, err + } + if _, err := w.Write([]byte("\n")); err != nil { + return nil, err + } return &httpEventForward{Status: 200, Header: make(http.Header), Body: []byte("OK")}, nil }