Skip to content

Commit

Permalink
💅
Browse files Browse the repository at this point in the history
  • Loading branch information
mislav committed May 26, 2023
1 parent 5ddddde commit 4b303c3
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 68 deletions.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
29 changes: 17 additions & 12 deletions webhook/create_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/cli/go-gh/v2/pkg/api"
Expand Down Expand Up @@ -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,
},
}

Expand All @@ -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)
}
Expand Down
104 changes: 49 additions & 55 deletions webhook/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
Expand All @@ -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=<types> [--url=<url>]",
Short: "Receive test events locally",
Expand All @@ -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
}

Expand All @@ -95,13 +79,18 @@ 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 {
// If the error is a server disconnect (1006), retry connecting
if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
time.Sleep(5 * time.Second)
continue
} else if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
return nil
}
return err
}
Expand All @@ -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)
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 4b303c3

Please sign in to comment.