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

Make merge gatekeeper more resilient #67

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something very peculiar to our setup, but we are using Importer https://github.com/upsidr/importer for our documentation. This means we are only pulling in pieces of documentation from docs directory.

You can see some markdown comments such as

<!-- == imptr: inputs / begin from: ./docs/action-usage.md#[inputs] == -->

It means it's checking the file ./docs/action-usage.md and a marker inputs, and when you run importer update README.md, it would replace the documentation with that given content. This is our own way of ensuring documentation does not get drifted between places, and that's the reason why the CI is failing. If you could make sure to update the ./docs directory content as well as README.md, that would be appreciated! 🥰

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ At UPSIDER, we have a few internal repositories set up with a monorepo structure

We are looking to add a few more features, such as extra signoff from non-coder, label based check, etc.

<sup><sub>NOTE:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take out the blank spaces here, it would change the line break layout slightly. While it seems to read just fine with this change, I'd like to check if this was an intentional update?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my ide removed it

<sup><sub>NOTE:
<sup>(\*1)</sup> There are some other hacks, such as using an empty job with the same name to override the status, but those solutions do not provide the flexible control we are after.</sub></sup>

<!-- == imptr: background / end == -->
Expand Down Expand Up @@ -85,6 +85,7 @@ There are some customisation available for Merge Gatekeeper.
| `token` | `GITHUB_TOKEN` or Personal Access Token with `repo` scope | Yes |
| `self` | The name of Merge Gatekeeper job, and defaults to `merge-gatekeeper`. This is used to check other job status, and do not check Merge Gatekeeper itself. If you updated the GitHub Action job name from `merge-gatekeeper` to something else, you would need to specify the new name with this value. | |
| `interval` | Check interval to recheck the job status. Default is set to 5 (sec). | |
| `github-client-retry` | Retry the request if the GitHub client returns 5xx error. Default is set to 0. | |
| `timeout` | Timeout setup to give up further check. Default is set to 600 (sec). | |
| `ignored` | Jobs to ignore regardless of their statuses. Defined as a comma-separated list. | |
| `ref` | Git ref to check out. This falls back to the HEAD for given PR, but can be set to any ref. | |
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ inputs:
description: "set validate interval second (default 5)"
required: false
default: "5"
github-client-retry:
description: "set retry count for GitHub client (default 0)"
required: false
default: "0"
timeout:
description: "set validate timeout second (default 600)"
required: false
Expand All @@ -35,6 +39,7 @@ runs:
- "--token=${{ inputs.token }}"
- "--self=${{ inputs.self }}"
- "--interval=${{ inputs.interval }}"
- "--github-client-retry=${{ inputs.github-client-retry }}"
- "--ref=${{ inputs.ref }}"
- "--timeout=${{ inputs.timeout }}"
- "--ignored=${{ inputs.ignored }}"
4 changes: 1 addition & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v38 v38.1.0 h1:C6h1FkaITcBFK7gAmq4eFzt6gbhEhk7L5z6R3Uva+po=
github.com/google/go-github/v38 v38.1.0/go.mod h1:cStvrz/7nFr0FoENgG6GLbp53WaelXucT+BBz/3VKx4=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
Expand Down
11 changes: 10 additions & 1 deletion internal/cli/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"time"
Expand All @@ -25,6 +26,7 @@ var (
validateInvalSecond uint
selfJobName string
ignoredJobs string
githubClientRetry int
)

func validateCmd() *cobra.Command {
Expand All @@ -45,7 +47,12 @@ func validateCmd() *cobra.Command {
return fmt.Errorf("github owner or repository is empty. owner: %s, repository: %s", owner, repo)
}

statusValidator, err := status.CreateValidator(github.NewClient(ctx, ghToken),
t := http.DefaultTransport
if githubClientRetry > 0 {
t = github.NewRetryTransport(githubClientRetry)
}
statusValidator, err := status.CreateValidator(
github.NewClient(ctx, ghToken, github.WithTransport(t)),
status.WithSelfJob(selfJobName),
status.WithGitHubOwnerAndRepo(owner, repo),
status.WithGitHubRef(ghRef),
Expand All @@ -72,6 +79,8 @@ func validateCmd() *cobra.Command {

cmd.PersistentFlags().StringVarP(&ignoredJobs, "ignored", "i", "", "set ignored jobs (comma-separated list)")

cmd.PersistentFlags().IntVar(&githubClientRetry, "github-client-retry", 0, "set retry count for GitHub client")

return cmd
}

Expand Down
34 changes: 29 additions & 5 deletions internal/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package github

import (
"context"
"net/http"

"github.com/google/go-github/v38/github"
"golang.org/x/oauth2"
Expand All @@ -25,17 +26,40 @@ type Client interface {
ListCheckRunsForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error)
}

type clientConfigOption func(*clientConfig)

type client struct {
ghc *github.Client
}

func NewClient(ctx context.Context, token string) Client {
type clientConfig struct {
transport http.RoundTripper
}

func WithTransport(transport http.RoundTripper) clientConfigOption {
return func(c *clientConfig) {
c.transport = transport
}
}

func NewClient(ctx context.Context, token string, opts ...clientConfigOption) Client {
clientConfig := &clientConfig{}

for _, opt := range opts {
opt(clientConfig)
}

return &client{
ghc: github.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: token,
ghc: github.NewClient(&http.Client{
Transport: &oauth2.Transport{
Base: clientConfig.transport,
Source: oauth2.ReuseTokenSource(nil, oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: token,
},
)),
},
))),
}),
}
}

Expand Down
48 changes: 48 additions & 0 deletions internal/github/retry_roundtripper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package github

import (
"log"
"math"
"net/http"
"time"
)

type RetryTransport struct {
next http.RoundTripper
Retries int
log log.Logger
}

func NewRetryTransport(retry int) *RetryTransport {
return &RetryTransport{
next: http.DefaultTransport,
Retries: retry,
}
}

func (t *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
var (
resp *http.Response
err error
)

for i := 0; i <= t.Retries; i++ {
resp, err = t.next.RoundTrip(req)
if err != nil {
return resp, err
}

if resp.StatusCode >= 500 && resp.StatusCode < 600 {
log.Printf("Retrying request due to status code: %d\n", resp.StatusCode)
delay := time.Duration(math.Pow(2, float64(i))) * time.Second
time.Sleep(delay)

resp.Body.Close()
continue
}

break
}

return resp, err
}