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