github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/services/scm_provider/github.go (about) 1 package scm_provider 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "os" 9 10 "github.com/google/go-github/v35/github" 11 "golang.org/x/oauth2" 12 ) 13 14 type GithubProvider struct { 15 client *github.Client 16 organization string 17 allBranches bool 18 } 19 20 var _ SCMProviderService = &GithubProvider{} 21 22 func NewGithubProvider(ctx context.Context, organization string, token string, url string, allBranches bool) (*GithubProvider, error) { 23 var ts oauth2.TokenSource 24 // Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits. 25 if token == "" { 26 token = os.Getenv("GITHUB_TOKEN") 27 } 28 if token != "" { 29 ts = oauth2.StaticTokenSource( 30 &oauth2.Token{AccessToken: token}, 31 ) 32 } 33 httpClient := oauth2.NewClient(ctx, ts) 34 var client *github.Client 35 if url == "" { 36 client = github.NewClient(httpClient) 37 } else { 38 var err error 39 client, err = github.NewEnterpriseClient(url, url, httpClient) 40 if err != nil { 41 return nil, err 42 } 43 } 44 return &GithubProvider{client: client, organization: organization, allBranches: allBranches}, nil 45 } 46 47 func (g *GithubProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) { 48 repos := []*Repository{} 49 branches, err := g.listBranches(ctx, repo) 50 if err != nil { 51 return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err) 52 } 53 54 for _, branch := range branches { 55 repos = append(repos, &Repository{ 56 Organization: repo.Organization, 57 Repository: repo.Repository, 58 URL: repo.URL, 59 Branch: branch.GetName(), 60 SHA: branch.GetCommit().GetSHA(), 61 Labels: repo.Labels, 62 RepositoryId: repo.RepositoryId, 63 }) 64 } 65 return repos, nil 66 } 67 68 func (g *GithubProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) { 69 opt := &github.RepositoryListByOrgOptions{ 70 ListOptions: github.ListOptions{PerPage: 100}, 71 } 72 repos := []*Repository{} 73 for { 74 githubRepos, resp, err := g.client.Repositories.ListByOrg(ctx, g.organization, opt) 75 if err != nil { 76 return nil, fmt.Errorf("error listing repositories for %s: %w", g.organization, err) 77 } 78 for _, githubRepo := range githubRepos { 79 var url string 80 switch cloneProtocol { 81 // Default to SSH if unspecified (i.e. if ""). 82 case "", "ssh": 83 url = githubRepo.GetSSHURL() 84 case "https": 85 url = githubRepo.GetCloneURL() 86 default: 87 return nil, fmt.Errorf("unknown clone protocol for GitHub %v", cloneProtocol) 88 } 89 repos = append(repos, &Repository{ 90 Organization: githubRepo.Owner.GetLogin(), 91 Repository: githubRepo.GetName(), 92 Branch: githubRepo.GetDefaultBranch(), 93 URL: url, 94 Labels: githubRepo.Topics, 95 RepositoryId: githubRepo.ID, 96 }) 97 } 98 if resp.NextPage == 0 { 99 break 100 } 101 opt.Page = resp.NextPage 102 } 103 return repos, nil 104 } 105 106 func (g *GithubProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) { 107 _, _, resp, err := g.client.Repositories.GetContents(ctx, repo.Organization, repo.Repository, path, &github.RepositoryContentGetOptions{ 108 Ref: repo.Branch, 109 }) 110 // 404s are not an error here, just a normal false. 111 if resp != nil && resp.StatusCode == 404 { 112 return false, nil 113 } 114 if err != nil { 115 return false, err 116 } 117 return true, nil 118 } 119 120 func (g *GithubProvider) listBranches(ctx context.Context, repo *Repository) ([]github.Branch, error) { 121 // If we don't specifically want to query for all branches, just use the default branch and call it a day. 122 if !g.allBranches { 123 defaultBranch, _, err := g.client.Repositories.GetBranch(ctx, repo.Organization, repo.Repository, repo.Branch) 124 if err != nil { 125 var githubErrorResponse *github.ErrorResponse 126 if errors.As(err, &githubErrorResponse) { 127 if githubErrorResponse.Response.StatusCode == http.StatusNotFound { 128 // Default branch doesn't exist, so the repo is empty. 129 return []github.Branch{}, nil 130 } 131 } 132 return nil, err 133 } 134 return []github.Branch{*defaultBranch}, nil 135 } 136 // Otherwise, scrape the ListBranches API. 137 opt := &github.BranchListOptions{ 138 ListOptions: github.ListOptions{PerPage: 100}, 139 } 140 branches := []github.Branch{} 141 for { 142 githubBranches, resp, err := g.client.Repositories.ListBranches(ctx, repo.Organization, repo.Repository, opt) 143 if err != nil { 144 return nil, err 145 } 146 for _, githubBranch := range githubBranches { 147 branches = append(branches, *githubBranch) 148 } 149 150 if resp.NextPage == 0 { 151 break 152 } 153 opt.Page = resp.NextPage 154 } 155 return branches, nil 156 }