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  }