From 9a772f012e8f64bf13ab133804cb063797bd4516 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 20 Dec 2023 23:27:36 +0100 Subject: [PATCH] add support for org replaying Signed-off-by: Chmouel Boudjnah --- README.md | 31 +++++++++++++------ gosmee/hook_list.go | 11 ++++--- gosmee/interface.go | 72 +++++++++++++++++++++++++++++++++++++++++++++ gosmee/replay.go | 24 ++++++++++----- 4 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 gosmee/interface.go diff --git a/README.md b/README.md index 6310653..c94d048 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ If you intend to use , you might want to generate your own smee Once you have it, the basic usage is as follows: ```shell -gosmee client https://smee.io/aBcDeF https://localhost:8080 +gosmee client https://smee.io/aBcDeF https://localhost:8080 ``` This command will relay all payloads received at the smee URL to a service running on . @@ -189,10 +189,10 @@ Here is a `proxy_pass location` to a locally running gosmee server on port local ```nginx location / { proxy_pass http://127.0.0.1:3333; - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding off; - proxy_read_timeout 372h; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + proxy_read_timeout 372h; } ``` @@ -204,9 +204,13 @@ Alternatively if you don't want to use a relay server and use GitHub you can replay the hooks deliveries via the GitHub API. Compared to the relay server method this is more reliable and you don't have to worry about the relay server being down. The downside is that it only works with GitHub and you need to have -a GitHub token with the `repo` scope. +a GitHub token. The scopes needed are: -It currently only supports Repository and not yet organisation hooks. +- For repository webhooks, the token must have the `read:repo_hook` or `repo` scope +- For organizations webhooks, you must have the `admin:org_hook` scope. + +It currently only supports replaying webhook installed on Repositories and +Organizations but not supporting webhooks events from GitHub apps. You will need to know the Hook ID of the webhook you want to replay, you can get it with the `--hook-id` command: @@ -215,8 +219,17 @@ get it with the `--hook-id` command: goplay replay --github-token=$GITHUB_TOKEN --list-hooks org/repo ``` -This will list all the hooks for the repository and their ID. When you grab the -appropriate you can start to listen to the events and replay them on a local +This will list all the hooks for the repository and their ID. + +If you want to list the hooks on an organization you can just specify the org +name with no slash (same goes for the rest of this documentation, it behaves the +same between org and repo): + +```shell +goplay replay --github-token=$GITHUB_TOKEN --list-hooks org +``` + +When you grab the appropriate you can start to listen to the events and replay them on a local server: ```shell diff --git a/gosmee/hook_list.go b/gosmee/hook_list.go index 89962e0..ce721e1 100644 --- a/gosmee/hook_list.go +++ b/gosmee/hook_list.go @@ -10,8 +10,9 @@ import ( ) func (r *replayOpts) listHooks(ctx context.Context) error { - hooks, _, err := r.client.Repositories.ListHooks(ctx, r.org, r.repo, nil) - if err != nil { + var hooks []*github.Hook + var err error + if hooks, _, err = r.ghop.ListHooks(ctx, r.org, r.repo, nil); err != nil { return fmt.Errorf("cannot list hooks: %w", err) } @@ -30,8 +31,10 @@ func (r *replayOpts) listHooks(ctx context.Context) error { } func (r *replayOpts) listDeliveries(ctx context.Context, hookID int64) error { - deliveries, _, err := r.client.Repositories.ListHookDeliveries(ctx, r.org, r.repo, hookID, &github.ListCursorOptions{PerPage: 50}) - if err != nil { + var deliveries []*github.HookDelivery + var err error + opt := &github.ListCursorOptions{PerPage: 50} + if deliveries, _, err = r.ghop.ListHookDeliveries(ctx, r.org, r.repo, hookID, opt); err != nil { return fmt.Errorf("cannot list deliveries: %w", err) } fmt.Fprintf(os.Stdout, ansi.Color(fmt.Sprintf("%-12s %-12s %s\n", "ID", "Event", "Delivered At"), "cyan+b")) diff --git a/gosmee/interface.go b/gosmee/interface.go new file mode 100644 index 0000000..7ee8357 --- /dev/null +++ b/gosmee/interface.go @@ -0,0 +1,72 @@ +package gosmee + +import ( + "context" + + "github.com/google/go-github/v57/github" + "golang.org/x/exp/slog" +) + +type GHOp interface { + ListHooks(ctx context.Context, org, repo string, opt *github.ListOptions) ([]*github.Hook, *github.Response, error) + ListHookDeliveries(ctx context.Context, org, repo string, hookID int64, opt *github.ListCursorOptions) ([]*github.HookDelivery, *github.Response, error) + GetHookDelivery(ctx context.Context, org, repo string, hookID, deliveryID int64) (*github.HookDelivery, *github.Response, error) + Starting() +} + +var _ GHOp = (*RepoOP)(nil) +var _ GHOp = (*OrgOP)(nil) + +type RepoOP struct { + client *github.Client + logger *slog.Logger + org, repo string +} + +func NewRepoLister(client *github.Client, logger *slog.Logger, org, repo string) *RepoOP { + return &RepoOP{client: client, logger: logger, org: repo, repo: repo} +} + +func (r *RepoOP) Starting() { + r.logger.Info("watching deliveries on", "org", r.org, "repo", r.repo) +} + +func (r *RepoOP) ListHooks(ctx context.Context, org, repo string, opt *github.ListOptions) ([]*github.Hook, *github.Response, error) { + return r.client.Repositories.ListHooks(ctx, org, repo, opt) +} + +func (r *RepoOP) ListHookDeliveries(ctx context.Context, org, repo string, hookID int64, opt *github.ListCursorOptions) ([]*github.HookDelivery, *github.Response, error) { + return r.client.Repositories.ListHookDeliveries(ctx, org, repo, hookID, opt) +} + +func (r *RepoOP) GetHookDelivery(ctx context.Context, org, repo string, hookID, deliveryID int64) (*github.HookDelivery, *github.Response, error) { + return r.client.Repositories.GetHookDelivery(ctx, org, repo, hookID, deliveryID) +} + +var _ GHOp = (*RepoOP)(nil) + +type OrgOP struct { + client *github.Client + logger *slog.Logger + org, repo string +} + +func NewOrgLister(client *github.Client, logger *slog.Logger, org, repo string) *OrgOP { + return &OrgOP{client: client, logger: logger, org: org, repo: repo} +} + +func (r *OrgOP) ListHooks(ctx context.Context, org, _ string, opt *github.ListOptions) ([]*github.Hook, *github.Response, error) { + return r.client.Organizations.ListHooks(ctx, org, opt) +} + +func (r *OrgOP) ListHookDeliveries(ctx context.Context, org, _ string, hookID int64, opt *github.ListCursorOptions) ([]*github.HookDelivery, *github.Response, error) { + return r.client.Organizations.ListHookDeliveries(ctx, org, hookID, opt) +} + +func (r *OrgOP) GetHookDelivery(ctx context.Context, org, _ string, hookID, deliveryID int64) (*github.HookDelivery, *github.Response, error) { + return r.client.Organizations.GetHookDelivery(ctx, org, hookID, deliveryID) +} + +func (r *OrgOP) Starting() { + r.logger.Info("watching deliveries on", "org", r.org) +} diff --git a/gosmee/replay.go b/gosmee/replay.go index 5a835a5..68fb584 100644 --- a/gosmee/replay.go +++ b/gosmee/replay.go @@ -27,6 +27,7 @@ type replayOpts struct { org string logger *slog.Logger sinceTime time.Time + ghop GHOp } // chooseDeliveries reverses the deliveries slice and only show the deliveries since the last date we parsed. @@ -47,10 +48,10 @@ func (r *replayOpts) chooseDeliveries(dlvs []*github.HookDelivery) []*github.Hoo } func (r *replayOpts) replayHooks(ctx context.Context, hookid int64) error { - r.logger.Info(fmt.Sprintf("starting watching deliveries for %s/%s", r.org, r.repo)) + r.ghop.Starting() for { opt := &github.ListCursorOptions{PerPage: 100} - deliveries, _, err := r.client.Repositories.ListHookDeliveries(ctx, r.org, r.repo, hookid, opt) + deliveries, _, err := r.ghop.ListHookDeliveries(ctx, r.org, r.repo, hookid, opt) if err != nil { return fmt.Errorf("cannot list deliveries: %w", err) } @@ -63,7 +64,7 @@ func (r *replayOpts) replayHooks(ctx context.Context, hookid int64) error { for range []int{1, 2, 3} { var resp *github.Response var err error - delivery, resp, err = r.client.Repositories.GetHookDelivery(ctx, r.org, r.repo, hookid, hd.GetID()) + delivery, resp, err = r.ghop.GetHookDelivery(ctx, r.org, r.repo, hookid, hd.GetID()) if resp.StatusCode == http.StatusNotFound { time.Sleep(1 * time.Second) continue @@ -141,11 +142,15 @@ func replay(c *cli.Context) error { orgRepo := c.Args().Get(0) if strings.Contains(orgRepo, "/") { - ropt.org = strings.Split(orgRepo, "/")[0] - ropt.repo = strings.Split(orgRepo, "/")[1] + spt := strings.Split(orgRepo, "/") + ropt.org = spt[0] + ropt.repo = spt[1] + } else { + ropt.org = orgRepo } - if ropt.org == "" || ropt.repo == "" { - return fmt.Errorf("org and repo are required, example: org/repo") + + if ropt.org == "" { + return fmt.Errorf("at least an org is required or an org/repo") } if c.IsSet("list-hooks") { @@ -205,6 +210,11 @@ func replay(c *cli.Context) error { } ropt.sinceTime = since } + if ropt.repo == "" { + ropt.ghop = NewOrgLister(client, logger, ropt.org, ropt.repo) + } else { + ropt.ghop = NewRepoLister(client, logger, ropt.org, ropt.repo) + } ropt.replayDataOpts = &replayDataOpts{ targetURL: targetURL,