Skip to content

Commit 598f1b0

Browse files
committed
cmd/gerritbot: update CLA check
The CLA checker is no longer setting the 'cla: yes' label. Instead, it functions as a standard GitHub Checker which we have to look at. Rather than manually caching the ListCheckRunsForRef call, replace the existing manual caching with httpcache, which I've verified prevents us from consuming quota on repeated checks. Fixes golang/go#49696. Change-Id: I362e8bc34bf503bf04771f5da0de7a7e53942f29 Reviewed-on: https://go-review.googlesource.com/c/build/+/371815 Trust: Heschi Kreinick <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 6e9359b commit 598f1b0

File tree

1 file changed

+50
-56
lines changed

1 file changed

+50
-56
lines changed

cmd/gerritbot/gerritbot.go

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"cloud.google.com/go/compute/metadata"
2929
"github.com/google/go-github/github"
30+
"github.com/gregjones/httpcache"
3031
"golang.org/x/build/gerrit"
3132
"golang.org/x/build/internal/https"
3233
"golang.org/x/build/internal/secret"
@@ -116,9 +117,18 @@ func githubClient(sc *secret.Client) (*github.Client, error) {
116117
if err != nil {
117118
return nil, err
118119
}
119-
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
120-
tc := oauth2.NewClient(context.Background(), ts)
121-
return github.NewClient(tc), nil
120+
oauthTransport := &oauth2.Transport{
121+
Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}),
122+
}
123+
cachingTransport := &httpcache.Transport{
124+
Transport: oauthTransport,
125+
Cache: httpcache.NewMemoryCache(),
126+
MarkCachedResponses: true,
127+
}
128+
httpClient := &http.Client{
129+
Transport: cachingTransport,
130+
}
131+
return github.NewClient(httpClient), nil
122132
}
123133

124134
func githubToken(sc *secret.Client) (string, error) {
@@ -212,27 +222,16 @@ func genProjectWhitelist() map[string]bool {
212222
return m
213223
}
214224

215-
type cachedPullRequest struct {
216-
pr *github.PullRequest
217-
etag string
218-
}
219-
220225
type bot struct {
221226
githubClient *github.Client
222227
gerritClient *gerrit.Client
223228

224229
sync.RWMutex // Protects all fields below
225230
corpus *maintner.Corpus
226231

227-
// importedPRs and cachedPRs should share the same method for determining keys
228-
// given the same Pull Request, as the presence of a key in the former determined
229-
// whether it should remain in the latter.
232+
// PRs and their corresponding Gerrit CLs.
230233
importedPRs map[string]*maintner.GerritCL // GitHub owner/repo#n -> Gerrit CL
231234

232-
// Pull Requests that have been cached locally since maintner doesn’t support
233-
// PRs and this is used to make conditional requests to the API.
234-
cachedPRs map[string]*cachedPullRequest // GitHub owner/repo#n -> GitHub Pull Request
235-
236235
// CLs that have been created/updated on Gerrit for GitHub PRs but are not yet
237236
// reflected in the maintner corpus yet.
238237
pendingCLs map[string]string // GitHub owner/repo#n -> Commit message from PR
@@ -247,7 +246,6 @@ func newBot(githubClient *github.Client, gerritClient *gerrit.Client) *bot {
247246
gerritClient: gerritClient,
248247
importedPRs: map[string]*maintner.GerritCL{},
249248
pendingCLs: map[string]string{},
250-
cachedPRs: map[string]*cachedPullRequest{},
251249
cachedGerritAccounts: map[int]*gerrit.AccountInfo{},
252250
}
253251
}
@@ -309,13 +307,6 @@ func (b *bot) checkPullRequests() {
309307
})
310308
})
311309

312-
// Remove any cached PRs that are no longer being checked.
313-
for k := range b.cachedPRs {
314-
if b.importedPRs[k] == nil {
315-
delete(b.cachedPRs, k)
316-
}
317-
}
318-
319310
b.corpus.GitHub().ForeachRepo(func(ghr *maintner.GitHubRepo) error {
320311
id := ghr.ID()
321312
if id.Owner != "golang" || !gerritProjectWhitelist[id.Repo] {
@@ -339,14 +330,22 @@ func (b *bot) checkPullRequests() {
339330
}
340331
return nil
341332
}
342-
if issue.Closed || !issue.PullRequest || !issue.HasLabel("cla: yes") {
333+
if issue.Closed || !issue.PullRequest {
343334
return nil
344335
}
345336
pr, err := b.getFullPR(ctx, id.Owner, id.Repo, int(issue.Number))
346337
if err != nil {
347338
log.Printf("getFullPR(ctx, %q, %q, %d): %v", id.Owner, id.Repo, issue.Number, err)
348339
return nil
349340
}
341+
approved, err := b.claApproved(ctx, id, pr)
342+
if err != nil {
343+
log.Printf("checking CLA approval: %v", err)
344+
return nil
345+
}
346+
if !approved {
347+
return nil
348+
}
350349
if err := b.processPullRequest(ctx, pr); err != nil {
351350
log.Printf("processPullRequest: %v", err)
352351
return nil
@@ -356,6 +355,31 @@ func (b *bot) checkPullRequests() {
356355
})
357356
}
358357

358+
// claApproved reports whether the latest head commit of the given PR in repo
359+
// has been approved by the Google CLA checker.
360+
func (b *bot) claApproved(ctx context.Context, repo maintner.GitHubRepoID, pr *github.PullRequest) (bool, error) {
361+
if pr.GetHead().GetSHA() == "" {
362+
// Paranoia check. This should never happen.
363+
return false, fmt.Errorf("no head SHA for PR %v %v", repo, pr.GetNumber())
364+
}
365+
runs, _, err := b.githubClient.Checks.ListCheckRunsForRef(ctx, repo.Owner, repo.Repo, pr.GetHead().GetSHA(), &github.ListCheckRunsOptions{
366+
CheckName: github.String("cla/google"),
367+
Status: github.String("completed"),
368+
Filter: github.String("latest"),
369+
// TODO(heschi): filter for App ID once supported by go-github
370+
})
371+
if err != nil {
372+
return false, err
373+
}
374+
for _, run := range runs.CheckRuns {
375+
if run.GetApp().GetID() != 42202 {
376+
continue
377+
}
378+
return run.GetConclusion() == "success", nil
379+
}
380+
return false, nil
381+
}
382+
359383
// prShortLink returns text referencing an Issue or Pull Request that will be
360384
// automatically converted into a link by GitHub.
361385
func githubShortLink(owner, repo string, number int) string {
@@ -798,42 +822,12 @@ func reposRoot() string {
798822
}
799823

800824
// getFullPR retrieves a Pull Request via GitHub’s API.
801-
// b.RWMutex must be Lock'ed.
802825
func (b *bot) getFullPR(ctx context.Context, owner, repo string, number int) (*github.PullRequest, error) {
803-
shortLink := githubShortLink(owner, repo, number)
804-
cpr := b.cachedPRs[shortLink]
805-
var etag string
806-
if cpr != nil {
807-
etag = cpr.etag
808-
log.Printf("Retrieving PR %s from GitHub using Etag %q ...", shortLink, etag)
809-
} else {
810-
log.Printf("Retrieving PR %s from GitHub without an Etag ...", shortLink)
811-
}
812-
813-
u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
814-
req, err := b.githubClient.NewRequest(http.MethodGet, u, nil)
826+
pr, resp, err := b.githubClient.PullRequests.Get(ctx, owner, repo, number)
815827
if err != nil {
816-
return nil, fmt.Errorf("b.githubClient.NewRequest(%q, %q, nil): %v", http.MethodGet, u, err)
817-
}
818-
if etag != "" {
819-
req.Header.Add("If-None-Match", etag)
820-
}
821-
822-
pr := new(github.PullRequest)
823-
resp, err := b.githubClient.Do(ctx, req, pr)
824-
logGitHubRateLimits(resp)
825-
if err != nil {
826-
if resp != nil && resp.StatusCode == http.StatusNotModified {
827-
log.Println("Returning cached version of", shortLink)
828-
return cpr.pr, nil
829-
}
830828
return nil, fmt.Errorf("b.githubClient.Do: %v", err)
831829
}
832-
833-
b.cachedPRs[shortLink] = &cachedPullRequest{
834-
etag: resp.Header.Get("Etag"),
835-
pr: pr,
836-
}
830+
logGitHubRateLimits(resp)
837831
return pr, nil
838832
}
839833

0 commit comments

Comments
 (0)