You've already forked goreleaser
mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-11-29 23:07:42 +02:00
feat(github): avoid rate limits (#4037)
before doing anything, check the `/rate_limits` API... if we have less than 100 remaining, wait for the reset timer. closes #4028
This commit is contained in:
committed by
GitHub
parent
051381837d
commit
1f8a7b2fc5
@@ -9,6 +9,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/log"
|
||||
"github.com/google/go-github/v50/github"
|
||||
@@ -64,7 +65,22 @@ func newGitHub(ctx *context.Context, token string) (*githubClient, error) {
|
||||
return &githubClient{client: client}, nil
|
||||
}
|
||||
|
||||
func (c *githubClient) checkRateLimit(ctx *context.Context) {
|
||||
limits, _, err := c.client.RateLimits(ctx)
|
||||
if err != nil {
|
||||
log.Warn("could not check rate limits, hoping for the best...")
|
||||
return
|
||||
}
|
||||
if limits.Core.Remaining > 100 { // 100 should be safe enough
|
||||
return
|
||||
}
|
||||
sleep := limits.Core.Reset.UTC().Sub(time.Now().UTC())
|
||||
log.Warnf("token too close to rate limiting, will sleep for %s before continuing...", sleep)
|
||||
time.Sleep(sleep)
|
||||
}
|
||||
|
||||
func (c *githubClient) GenerateReleaseNotes(ctx *context.Context, repo Repo, prev, current string) (string, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
notes, _, err := c.client.Repositories.GenerateReleaseNotes(ctx, repo.Owner, repo.Name, &github.GenerateNotesOptions{
|
||||
TagName: current,
|
||||
PreviousTagName: github.String(prev),
|
||||
@@ -76,6 +92,7 @@ func (c *githubClient) GenerateReleaseNotes(ctx *context.Context, repo Repo, pre
|
||||
}
|
||||
|
||||
func (c *githubClient) Changelog(ctx *context.Context, repo Repo, prev, current string) (string, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
var log []string
|
||||
opts := &github.ListOptions{PerPage: 100}
|
||||
|
||||
@@ -103,6 +120,7 @@ func (c *githubClient) Changelog(ctx *context.Context, repo Repo, prev, current
|
||||
|
||||
// getDefaultBranch returns the default branch of a github repo
|
||||
func (c *githubClient) getDefaultBranch(ctx *context.Context, repo Repo) (string, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
p, res, err := c.client.Repositories.Get(ctx, repo.Owner, repo.Name)
|
||||
if err != nil {
|
||||
log.WithField("projectID", repo.String()).
|
||||
@@ -116,6 +134,7 @@ func (c *githubClient) getDefaultBranch(ctx *context.Context, repo Repo) (string
|
||||
|
||||
// CloseMilestone closes a given milestone.
|
||||
func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
|
||||
c.checkRateLimit(ctx)
|
||||
milestone, err := c.getMilestoneByTitle(ctx, repo, title)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -144,6 +163,7 @@ func (c *githubClient) OpenPullRequest(
|
||||
repo Repo,
|
||||
base, title string,
|
||||
) error {
|
||||
c.checkRateLimit(ctx)
|
||||
if base == "" {
|
||||
def, err := c.getDefaultBranch(ctx, repo)
|
||||
if err != nil {
|
||||
@@ -176,6 +196,7 @@ func (c *githubClient) CreateFile(
|
||||
path,
|
||||
message string,
|
||||
) error {
|
||||
c.checkRateLimit(ctx)
|
||||
defBranch, err := c.getDefaultBranch(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get default branch: %w", err)
|
||||
@@ -257,6 +278,7 @@ func (c *githubClient) CreateFile(
|
||||
}
|
||||
|
||||
func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -302,6 +324,7 @@ func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string,
|
||||
}
|
||||
|
||||
func (c *githubClient) createOrUpdateRelease(ctx *context.Context, data *github.RepositoryRelease, body string) (*github.RepositoryRelease, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
release, _, err := c.client.Repositories.GetReleaseByTag(
|
||||
ctx,
|
||||
ctx.Config.Release.GitHub.Owner,
|
||||
@@ -329,6 +352,7 @@ func (c *githubClient) createOrUpdateRelease(ctx *context.Context, data *github.
|
||||
}
|
||||
|
||||
func (c *githubClient) updateRelease(ctx *context.Context, id int64, data *github.RepositoryRelease) (*github.RepositoryRelease, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
release, resp, err := c.client.Repositories.EditRelease(
|
||||
ctx,
|
||||
ctx.Config.Release.GitHub.Owner,
|
||||
@@ -365,6 +389,7 @@ func (c *githubClient) Upload(
|
||||
artifact *artifact.Artifact,
|
||||
file *os.File,
|
||||
) error {
|
||||
c.checkRateLimit(ctx)
|
||||
githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -400,6 +425,7 @@ func (c *githubClient) Upload(
|
||||
|
||||
// getMilestoneByTitle returns a milestone by title.
|
||||
func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
|
||||
c.checkRateLimit(ctx)
|
||||
// The GitHub API/SDK does not provide lookup by title functionality currently.
|
||||
opts := &github.MilestoneListOptions{
|
||||
ListOptions: github.ListOptions{PerPage: 100},
|
||||
@@ -462,6 +488,7 @@ func overrideGitHubClientAPI(ctx *context.Context, client *github.Client) error
|
||||
}
|
||||
|
||||
func (c *githubClient) deleteExistingDraftRelease(ctx *context.Context, name string) error {
|
||||
c.checkRateLimit(ctx)
|
||||
opt := github.ListOptions{PerPage: 50}
|
||||
for {
|
||||
releases, resp, err := c.client.Repositories.ListReleases(
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v50/github"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/testctx"
|
||||
"github.com/goreleaser/goreleaser/pkg/config"
|
||||
@@ -201,6 +203,12 @@ func TestGithubGetDefaultBranch(t *testing.T) {
|
||||
totalRequests++
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Assume the request to create a branch was good
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"default_branch": "main"}`)
|
||||
@@ -224,7 +232,7 @@ func TestGithubGetDefaultBranch(t *testing.T) {
|
||||
b, err := client.getDefaultBranch(ctx, repo)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "main", b)
|
||||
require.Equal(t, 1, totalRequests)
|
||||
require.Equal(t, 2, totalRequests)
|
||||
}
|
||||
|
||||
func TestGithubGetDefaultBranchErr(t *testing.T) {
|
||||
@@ -265,6 +273,11 @@ func TestChangelog(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -297,6 +310,11 @@ func TestReleaseNotes(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -325,6 +343,11 @@ func TestReleaseNotesError(t *testing.T) {
|
||||
if r.URL.Path == "/repos/someone/something/releases/generate-notes" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -357,6 +380,12 @@ func TestCloseMilestone(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -387,6 +416,12 @@ func TestOpenPullRequestHappyPath(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -419,6 +454,12 @@ func TestOpenPullRequestPRExists(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -456,6 +497,12 @@ func TestOpenPullRequestBaseEmpty(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -495,6 +542,12 @@ func TestGitHubCreateFileHappyPathCreate(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -535,6 +588,12 @@ func TestGitHubCreateFileHappyPathUpdate(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -589,6 +648,12 @@ func TestGitHubCreateFileFeatureBranchDoesNotExist(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, `{"resources":{"core":{"remaining":120}}}`)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("unhandled request: " + r.Method + " " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
@@ -609,6 +674,33 @@ func TestGitHubCreateFileFeatureBranchDoesNotExist(t *testing.T) {
|
||||
require.NoError(t, client.CreateFile(ctx, config.CommitAuthor{}, repo, []byte("content"), "file.txt", "message"))
|
||||
}
|
||||
|
||||
func TestCheckRateLimit(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
reset := now.Add(1392 * time.Millisecond)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if r.URL.Path == "/rate_limit" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
resetstr, _ := github.Timestamp{Time: reset}.MarshalJSON()
|
||||
fmt.Fprintf(w, `{"resources":{"core":{"remaining":98,"reset":%s}}}`, string(resetstr))
|
||||
return
|
||||
}
|
||||
t.Error("unhandled request: " + r.Method + " " + r.URL.Path)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
ctx := testctx.NewWithCfg(config.Project{
|
||||
GitHubURLs: config.GitHubURLs{
|
||||
API: srv.URL + "/",
|
||||
},
|
||||
})
|
||||
client, err := newGitHub(ctx, "test-token")
|
||||
require.NoError(t, err)
|
||||
client.checkRateLimit(ctx)
|
||||
require.True(t, time.Now().UTC().After(reset))
|
||||
}
|
||||
|
||||
// TODO: test create release
|
||||
// TODO: test create upload file to release
|
||||
// TODO: test delete draft release
|
||||
// TODO: test create PR
|
||||
|
||||
Reference in New Issue
Block a user