Skip to content

Commit c1e9cfd

Browse files
committed
refactor: use impersonation token for authencation
1 parent 568d241 commit c1e9cfd

File tree

9 files changed

+233
-181
lines changed

9 files changed

+233
-181
lines changed

clone.go

+51-142
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"net/http"
87
"os"
98
"os/exec"
109
"path"
1110
"path/filepath"
12-
"sync"
13-
"time"
1411

15-
"github.com/bradleyfalzon/ghinstallation/v2"
1612
"github.com/google/go-github/v57/github"
1713
"github.com/qiniu/reviewbot/config"
14+
"github.com/qiniu/reviewbot/internal/linters"
1815
"github.com/qiniu/reviewbot/internal/lintersutil"
1916
"github.com/qiniu/x/log"
2017
gitv2 "sigs.k8s.io/prow/pkg/git/v2"
2118
)
2219

23-
func (s *Server) prepareGitRepos(ctx context.Context, org, repo string, num int, platform config.Platform, installationID int64) (workspace string, workDir string, err error) {
20+
var errUnsupportedPlatform = errors.New("unsupported platform")
21+
22+
func (s *Server) prepareGitRepos(ctx context.Context, org, repo string, num int, platform config.Platform, installationID int64, provider linters.Provider) (workspace string, workDir string, err error) {
2423
log := lintersutil.FromContext(ctx)
2524
workspace, err = prepareRepoDir(org, repo, num)
2625
if err != nil {
@@ -38,23 +37,22 @@ func (s *Server) prepareGitRepos(ctx context.Context, org, repo string, num int,
3837
refs, workDir := s.fixRefs(workspace, org, repo)
3938
log.Debugf("refs: %+v", refs)
4039
for _, ref := range refs {
41-
if err := s.handleSingleRef(ctx, ref, org, repo, platform, installationID, num); err != nil {
40+
if err := s.handleSingleRef(ctx, ref, org, repo, platform, installationID, num, provider); err != nil {
4241
return "", "", err
4342
}
4443
}
4544

4645
return workspace, workDir, nil
4746
}
4847

49-
func (s *Server) handleSingleRef(ctx context.Context, ref config.Refs, org, repo string, platform config.Platform, installationID int64, num int) error {
48+
func (s *Server) handleSingleRef(ctx context.Context, ref config.Refs, org, repo string, platform config.Platform, installationID int64, num int, provider linters.Provider) error {
5049
opt := gitv2.ClientFactoryOpts{
5150
CacheDirBase: github.String(s.repoCacheDir),
5251
Persist: github.Bool(true),
5352
}
5453

55-
gitConfig := s.newGitConfigBuilder(ref.Org, ref.Repo, platform, installationID).Build()
56-
log.Debugf("git config: %+v", gitConfig)
57-
if err := s.configureGitAuth(&opt, gitConfig); err != nil {
54+
gb := s.newGitConfigBuilder(ref.Org, ref.Repo, platform, installationID, provider)
55+
if err := gb.configureGitAuth(&opt); err != nil {
5856
return fmt.Errorf("failed to configure git auth: %w", err)
5957
}
6058

@@ -213,188 +211,100 @@ type GitAuth struct {
213211
GitLabPersonalAccessToken string
214212
}
215213

216-
// GitConfig stores the Git repository configuration.
217-
type GitConfig struct {
218-
Platform config.Platform
219-
Host string // gitlab/github host
220-
Auth GitAuth
221-
}
222-
223-
// githubAppTokenCache implements the cache for GitHub App tokens.
224-
type githubAppTokenCache struct {
225-
sync.RWMutex
226-
tokens map[string]tokenWithExp // key: installationID, value: token
227-
appAuth *GitHubAppAuth
228-
}
229-
230214
// GitConfigBuilder is used to build the Git configuration for a specific request.
231215
type GitConfigBuilder struct {
232216
server *Server
233217
org string
234218
repo string
219+
host string
235220
platform config.Platform
221+
provider linters.Provider
236222
// installationID is the installation ID for the GitHub App
237223
installationID int64
238224
}
239225

240-
type tokenWithExp struct {
241-
token string
242-
exp time.Time
243-
}
244-
245-
// newGitHubAppTokenCache creates a new token cache.
246-
func newGitHubAppTokenCache(appID int64, privateKeyPath string) *githubAppTokenCache {
247-
return &githubAppTokenCache{
248-
tokens: make(map[string]tokenWithExp),
249-
appAuth: &GitHubAppAuth{
250-
AppID: appID,
251-
PrivateKeyPath: privateKeyPath,
252-
},
253-
}
254-
}
255-
func (c *githubAppTokenCache) getToken(org string, installationID int64) (string, error) {
256-
key := fmt.Sprintf("%s-%d", org, installationID)
257-
c.RLock()
258-
t, exists := c.tokens[key]
259-
c.RUnlock()
260-
261-
if exists && t.exp.After(time.Now()) {
262-
return t.token, nil
263-
}
264-
265-
c.Lock()
266-
defer c.Unlock()
267-
268-
// double check
269-
if t, exists := c.tokens[key]; exists {
270-
if t.exp.After(time.Now()) {
271-
return t.token, nil
272-
}
273-
}
274-
275-
// get new token
276-
token, err := c.refreshToken(installationID)
277-
if err != nil {
278-
return "", err
279-
}
280-
281-
log.Debugf("refreshed token for %s, installationID: %d", org, installationID)
282-
// cache token, 1 hour expiry
283-
c.tokens[key] = tokenWithExp{
284-
token: token,
285-
// add a buffer to avoid token expired
286-
exp: time.Now().Add(time.Hour - time.Minute),
287-
}
288-
289-
return token, nil
290-
}
291-
292-
// refreshToken refresh the GitHub App token.
293-
func (c *githubAppTokenCache) refreshToken(installationID int64) (string, error) {
294-
tr, err := ghinstallation.NewKeyFromFile(
295-
http.DefaultTransport,
296-
c.appAuth.AppID,
297-
installationID,
298-
c.appAuth.PrivateKeyPath,
299-
)
300-
if err != nil {
301-
return "", fmt.Errorf("failed to create github app transport: %w", err)
302-
}
303-
304-
token, err := tr.Token(context.Background())
305-
if err != nil {
306-
return "", fmt.Errorf("failed to get installation token: %w", err)
307-
}
308-
309-
return token, nil
310-
}
311-
312-
func (s *Server) newGitConfigBuilder(org, repo string, platform config.Platform, installationID int64) *GitConfigBuilder {
313-
return &GitConfigBuilder{
226+
func (s *Server) newGitConfigBuilder(org, repo string, platform config.Platform, installationID int64, provider linters.Provider) *GitConfigBuilder {
227+
g := &GitConfigBuilder{
314228
server: s,
315229
org: org,
316230
repo: repo,
317231
platform: platform,
318232
installationID: installationID,
233+
provider: provider,
319234
}
235+
g.host = g.getHostForPlatform(platform)
236+
return g
320237
}
321238

322-
func (b *GitConfigBuilder) Build() GitConfig {
323-
config := GitConfig{
324-
Platform: b.platform,
325-
Host: b.getHostForPlatform(b.platform),
326-
Auth: b.buildAuth(),
239+
func (g *GitConfigBuilder) configureGitAuth(opt *gitv2.ClientFactoryOpts) error {
240+
auth := g.buildAuth()
241+
opt.Host = g.host
242+
switch g.platform {
243+
case config.GitHub:
244+
return g.configureGitHubAuth(opt, auth)
245+
case config.GitLab:
246+
return g.configureGitLabAuth(opt, auth)
247+
default:
248+
log.Errorf("unsupported platform: %s", g.platform)
249+
return errUnsupportedPlatform
327250
}
328-
329-
return config
330251
}
331252

332-
func (b *GitConfigBuilder) getHostForPlatform(platform config.Platform) string {
253+
func (g *GitConfigBuilder) getHostForPlatform(platform config.Platform) string {
333254
switch platform {
334255
case config.GitLab:
335-
if b.server.gitLabHost != "" {
336-
return b.server.gitLabHost
256+
if g.server.gitLabHost != "" {
257+
return g.server.gitLabHost
337258
}
338259
return "gitlab.com"
339-
default:
260+
case config.GitHub:
340261
return "github.com"
262+
default:
263+
log.Errorf("unsupported platform: %s", g.platform)
264+
return ""
341265
}
342266
}
343267

344-
func (b *GitConfigBuilder) buildAuth() GitAuth {
345-
switch b.platform {
268+
func (g *GitConfigBuilder) buildAuth() GitAuth {
269+
switch g.platform {
346270
case config.GitHub:
347-
return b.buildGitHubAuth()
271+
return g.buildGitHubAuth()
348272
case config.GitLab:
349-
return b.buildGitLabAuth()
273+
return g.buildGitLabAuth()
350274
default:
351275
return GitAuth{}
352276
}
353277
}
354278

355-
func (b *GitConfigBuilder) buildGitHubAuth() GitAuth {
356-
if b.server.gitHubAppAuth != nil {
357-
appAuth := *b.server.gitHubAppAuth
358-
appAuth.InstallationID = b.installationID
279+
func (g *GitConfigBuilder) buildGitHubAuth() GitAuth {
280+
if g.server.gitHubAppAuth != nil {
281+
appAuth := *g.server.gitHubAppAuth
282+
appAuth.InstallationID = g.installationID
359283
return GitAuth{
360284
GitHubAppAuth: &appAuth,
361285
}
362286
}
363287

364-
if b.server.gitHubPersonalAccessToken != "" {
288+
if g.server.gitHubPersonalAccessToken != "" {
365289
return GitAuth{
366-
GitHubAccessToken: b.server.gitHubPersonalAccessToken,
290+
GitHubAccessToken: g.server.gitHubPersonalAccessToken,
367291
}
368292
}
369293

370294
return GitAuth{}
371295
}
372296

373-
func (b *GitConfigBuilder) buildGitLabAuth() GitAuth {
374-
if b.server.gitLabPersonalAccessToken != "" {
297+
func (g *GitConfigBuilder) buildGitLabAuth() GitAuth {
298+
if g.server.gitLabPersonalAccessToken != "" {
375299
return GitAuth{
376-
GitLabPersonalAccessToken: b.server.gitLabPersonalAccessToken,
300+
GitLabPersonalAccessToken: g.server.gitLabPersonalAccessToken,
377301
}
378302
}
379303

380304
return GitAuth{}
381305
}
382306

383-
func (s *Server) configureGitAuth(opt *gitv2.ClientFactoryOpts, gConf GitConfig) error {
384-
opt.Host = gConf.Host
385-
switch gConf.Platform {
386-
case config.GitHub:
387-
return s.configureGitHubAuth(opt, gConf)
388-
case config.GitLab:
389-
return s.configureGitLabAuth(opt, gConf)
390-
default:
391-
return fmt.Errorf("unsupported platform: %s", gConf.Platform)
392-
}
393-
}
394-
395-
func (s *Server) configureGitHubAuth(opt *gitv2.ClientFactoryOpts, config GitConfig) error {
396-
auth := config.Auth
397-
307+
func (g *GitConfigBuilder) configureGitHubAuth(opt *gitv2.ClientFactoryOpts, auth GitAuth) error {
398308
switch {
399309
case auth.GitHubAppAuth != nil:
400310
opt.UseSSH = github.Bool(false)
@@ -403,7 +313,7 @@ func (s *Server) configureGitHubAuth(opt *gitv2.ClientFactoryOpts, config GitCon
403313
}
404314
opt.Token = func(org string) (string, error) {
405315
log.Debugf("get token for %s, installationID: %d", org, auth.GitHubAppAuth.InstallationID)
406-
return s.githubAppTokenCache.getToken(org, auth.GitHubAppAuth.InstallationID)
316+
return g.provider.GetToken()
407317
}
408318
return nil
409319

@@ -423,17 +333,16 @@ func (s *Server) configureGitHubAuth(opt *gitv2.ClientFactoryOpts, config GitCon
423333
return nil
424334
}
425335

426-
func (s *Server) configureGitLabAuth(opt *gitv2.ClientFactoryOpts, config GitConfig) error {
427-
auth := config.Auth
428-
336+
func (g *GitConfigBuilder) configureGitLabAuth(opt *gitv2.ClientFactoryOpts, auth GitAuth) error {
429337
switch {
430338
case auth.GitLabPersonalAccessToken != "":
431339
opt.UseSSH = github.Bool(false)
432340
opt.Username = func() (string, error) {
433341
return "oauth2", nil
434342
}
435343
opt.Token = func(org string) (string, error) {
436-
return auth.GitLabPersonalAccessToken, nil
344+
log.Infof("get token for %s, personal access token: %s", org, auth.GitLabPersonalAccessToken)
345+
return g.provider.GetToken()
437346
}
438347
return nil
439348
default:

internal/cache/token.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cache
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
// DefaultTokenCache uses to cache provider impersonation tokens.
9+
var DefaultTokenCache = NewGitHubAppTokenCache()
10+
11+
// TokenCache implements the cache for provider impersonation tokens.
12+
type TokenCache struct {
13+
sync.RWMutex
14+
tokens map[string]tokenWithExp
15+
}
16+
17+
type tokenWithExp struct {
18+
token string
19+
exp time.Time
20+
}
21+
22+
// NewGitHubAppTokenCache creates a new token cache.
23+
func NewGitHubAppTokenCache() *TokenCache {
24+
return &TokenCache{
25+
tokens: make(map[string]tokenWithExp),
26+
}
27+
}
28+
29+
func (c *TokenCache) GetToken(key string) (string, bool) {
30+
c.RLock()
31+
t, exists := c.tokens[key]
32+
c.RUnlock()
33+
34+
if exists && t.exp.After(time.Now()) {
35+
return t.token, true
36+
}
37+
38+
return "", false
39+
}
40+
41+
func (c *TokenCache) SetToken(key string, token string, exp time.Time) {
42+
c.Lock()
43+
defer c.Unlock()
44+
c.tokens[key] = tokenWithExp{
45+
token: token,
46+
exp: exp,
47+
}
48+
}

internal/linters/go/golangci_lint/golangci_lint.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,19 @@ func golangciLintHandler(ctx context.Context, a linters.Agent) error {
7373
}
7474

7575
type goModTidyModifier struct {
76-
next config.Modifier
76+
prev config.Modifier
7777
goModDirs []string
7878
}
7979

80-
func newGoModTidyBuilder(next config.Modifier, goModDirs []string) config.Modifier {
80+
func newGoModTidyBuilder(prev config.Modifier, goModDirs []string) config.Modifier {
8181
return &goModTidyModifier{
82-
next: next,
82+
prev: prev,
8383
goModDirs: goModDirs,
8484
}
8585
}
8686

8787
func (b *goModTidyModifier) Modify(cfg *config.Linter) (*config.Linter, error) {
88-
base, err := b.next.Modify(cfg)
88+
base, err := b.prev.Modify(cfg)
8989
if err != nil {
9090
return nil, err
9191
}

internal/linters/provider.go

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ type Provider interface {
2323
GetFiles(predicate func(filepath string) bool) []string
2424
// GetCodeReviewInfo gets the code review information for the PR/MR.
2525
GetCodeReviewInfo() CodeReview
26+
// GetToken gets the token in order to interact with the git provider.
27+
// Base on the provider, the token may be the impersonation token or the personal access token.
28+
// the instance of a provider should know the necessary information to get the token. like platform, installationID, etc.
29+
GetToken() (string, error)
2630

2731
// ListCommits lists the commits in the PR/MR.
2832
ListCommits(ctx context.Context, org, repo string, number int) ([]Commit, error)

0 commit comments

Comments
 (0)