github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/services/scm_provider/gitlab.go (about)

     1  package scm_provider
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	pathpkg "path"
     9  
    10  	"github.com/argoproj/argo-cd/v2/applicationset/utils"
    11  	"github.com/hashicorp/go-retryablehttp"
    12  	"github.com/xanzy/go-gitlab"
    13  )
    14  
    15  type GitlabProvider struct {
    16  	client                *gitlab.Client
    17  	organization          string
    18  	allBranches           bool
    19  	includeSubgroups      bool
    20  	includeSharedProjects bool
    21  	topic                 string
    22  }
    23  
    24  var _ SCMProviderService = &GitlabProvider{}
    25  
    26  func NewGitlabProvider(ctx context.Context, organization string, token string, url string, allBranches, includeSubgroups, includeSharedProjects, insecure bool, scmRootCAPath, topic string) (*GitlabProvider, error) {
    27  	// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
    28  	if token == "" {
    29  		token = os.Getenv("GITLAB_TOKEN")
    30  	}
    31  	var client *gitlab.Client
    32  
    33  	tr := http.DefaultTransport.(*http.Transport).Clone()
    34  	tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
    35  
    36  	retryClient := retryablehttp.NewClient()
    37  	retryClient.HTTPClient.Transport = tr
    38  
    39  	if url == "" {
    40  		var err error
    41  		client, err = gitlab.NewClient(token, gitlab.WithHTTPClient(retryClient.HTTPClient))
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  	} else {
    46  		var err error
    47  		client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url), gitlab.WithHTTPClient(retryClient.HTTPClient))
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  
    53  	return &GitlabProvider{client: client, organization: organization, allBranches: allBranches, includeSubgroups: includeSubgroups, includeSharedProjects: includeSharedProjects, topic: topic}, nil
    54  }
    55  
    56  func (g *GitlabProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) {
    57  	repos := []*Repository{}
    58  	branches, err := g.listBranches(ctx, repo)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
    61  	}
    62  
    63  	for _, branch := range branches {
    64  		repos = append(repos, &Repository{
    65  			Organization: repo.Organization,
    66  			Repository:   repo.Repository,
    67  			URL:          repo.URL,
    68  			Branch:       branch.Name,
    69  			SHA:          branch.Commit.ID,
    70  			Labels:       repo.Labels,
    71  			RepositoryId: repo.RepositoryId,
    72  		})
    73  	}
    74  	return repos, nil
    75  }
    76  
    77  func (g *GitlabProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
    78  	opt := &gitlab.ListGroupProjectsOptions{
    79  		ListOptions:      gitlab.ListOptions{PerPage: 100},
    80  		IncludeSubGroups: &g.includeSubgroups,
    81  		WithShared:       &g.includeSharedProjects,
    82  		Topic:            &g.topic,
    83  	}
    84  
    85  	repos := []*Repository{}
    86  	for {
    87  		gitlabRepos, resp, err := g.client.Groups.ListGroupProjects(g.organization, opt)
    88  		if err != nil {
    89  			return nil, fmt.Errorf("error listing projects for %s: %v", g.organization, err)
    90  		}
    91  		for _, gitlabRepo := range gitlabRepos {
    92  			var url string
    93  			switch cloneProtocol {
    94  			// Default to SSH if unspecified (i.e. if "").
    95  			case "", "ssh":
    96  				url = gitlabRepo.SSHURLToRepo
    97  			case "https":
    98  				url = gitlabRepo.HTTPURLToRepo
    99  			default:
   100  				return nil, fmt.Errorf("unknown clone protocol for Gitlab %v", cloneProtocol)
   101  			}
   102  
   103  			var repoLabels []string
   104  			if len(gitlabRepo.Topics) == 0 {
   105  				// fallback to for gitlab prior to 14.5
   106  				repoLabels = gitlabRepo.TagList
   107  			} else {
   108  				repoLabels = gitlabRepo.Topics
   109  			}
   110  
   111  			repos = append(repos, &Repository{
   112  				Organization: gitlabRepo.Namespace.FullPath,
   113  				Repository:   gitlabRepo.Path,
   114  				URL:          url,
   115  				Branch:       gitlabRepo.DefaultBranch,
   116  				Labels:       repoLabels,
   117  				RepositoryId: gitlabRepo.ID,
   118  			})
   119  		}
   120  		if resp.CurrentPage >= resp.TotalPages {
   121  			break
   122  		}
   123  		opt.Page = resp.NextPage
   124  	}
   125  	return repos, nil
   126  }
   127  
   128  func (g *GitlabProvider) RepoHasPath(_ context.Context, repo *Repository, path string) (bool, error) {
   129  	p, _, err := g.client.Projects.GetProject(repo.Organization+"/"+repo.Repository, nil)
   130  	if err != nil {
   131  		return false, err
   132  	}
   133  	directories := []string{
   134  		path,
   135  		pathpkg.Dir(path),
   136  	}
   137  	for _, directory := range directories {
   138  		options := gitlab.ListTreeOptions{
   139  			Path: &directory,
   140  			Ref:  &repo.Branch,
   141  		}
   142  		for {
   143  			treeNode, resp, err := g.client.Repositories.ListTree(p.ID, &options)
   144  			if err != nil {
   145  				return false, err
   146  			}
   147  			if path == directory {
   148  				if resp.TotalItems > 0 {
   149  					return true, nil
   150  				}
   151  			}
   152  			for i := range treeNode {
   153  				if treeNode[i].Path == path {
   154  					return true, nil
   155  				}
   156  			}
   157  			if resp.NextPage == 0 {
   158  				// no future pages
   159  				break
   160  			}
   161  			options.Page = resp.NextPage
   162  		}
   163  	}
   164  	return false, nil
   165  }
   166  
   167  func (g *GitlabProvider) listBranches(_ context.Context, repo *Repository) ([]gitlab.Branch, error) {
   168  	branches := []gitlab.Branch{}
   169  	// If we don't specifically want to query for all branches, just use the default branch and call it a day.
   170  	if !g.allBranches {
   171  		gitlabBranch, resp, err := g.client.Branches.GetBranch(repo.RepositoryId, repo.Branch, nil)
   172  		// 404s are not an error here, just a normal false.
   173  		if resp != nil && resp.StatusCode == http.StatusNotFound {
   174  			return []gitlab.Branch{}, nil
   175  		}
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		branches = append(branches, *gitlabBranch)
   180  		return branches, nil
   181  	}
   182  	// Otherwise, scrape the ListBranches API.
   183  	opt := &gitlab.ListBranchesOptions{
   184  		ListOptions: gitlab.ListOptions{PerPage: 100},
   185  	}
   186  	for {
   187  		gitlabBranches, resp, err := g.client.Branches.ListBranches(repo.RepositoryId, opt)
   188  		// 404s are not an error here, just a normal false.
   189  		if resp != nil && resp.StatusCode == http.StatusNotFound {
   190  			return []gitlab.Branch{}, nil
   191  		}
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		for _, gitlabBranch := range gitlabBranches {
   196  			branches = append(branches, *gitlabBranch)
   197  		}
   198  
   199  		if resp.NextPage == 0 {
   200  			break
   201  		}
   202  		opt.Page = resp.NextPage
   203  	}
   204  	return branches, nil
   205  }