github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/services/scm_provider/bitbucket_cloud.go (about)

     1  package scm_provider
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	bitbucket "github.com/ktrysmt/go-bitbucket"
    11  )
    12  
    13  type BitBucketCloudProvider struct {
    14  	client      *ExtendedClient
    15  	allBranches bool
    16  	owner       string
    17  }
    18  
    19  type ExtendedClient struct {
    20  	*bitbucket.Client
    21  	username string
    22  	password string
    23  	owner    string
    24  }
    25  
    26  func (c *ExtendedClient) GetContents(repo *Repository, path string) (bool, error) {
    27  	urlStr := c.GetApiBaseURL()
    28  
    29  	// Getting file contents from V2 defined at https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get
    30  	urlStr += fmt.Sprintf("/repositories/%s/%s/src/%s/%s?format=meta", c.owner, repo.Repository, repo.SHA, path)
    31  	body := strings.NewReader("")
    32  
    33  	req, err := http.NewRequest(http.MethodGet, urlStr, body)
    34  	if err != nil {
    35  		return false, err
    36  	}
    37  	req.SetBasicAuth(c.username, c.password)
    38  	resp, err := c.HttpClient.Do(req)
    39  	if err != nil {
    40  		return false, err
    41  	}
    42  	defer resp.Body.Close()
    43  	if resp.StatusCode == http.StatusNotFound {
    44  		return false, nil
    45  	}
    46  	if resp.StatusCode == http.StatusOK {
    47  		return true, nil
    48  	}
    49  
    50  	return false, fmt.Errorf("%s", resp.Status)
    51  }
    52  
    53  var _ SCMProviderService = &BitBucketCloudProvider{}
    54  
    55  func NewBitBucketCloudProvider(owner string, user string, password string, allBranches bool) (*BitBucketCloudProvider, error) {
    56  	client := &ExtendedClient{
    57  		bitbucket.NewBasicAuth(user, password),
    58  		user,
    59  		password,
    60  		owner,
    61  	}
    62  	return &BitBucketCloudProvider{client: client, owner: owner, allBranches: allBranches}, nil
    63  }
    64  
    65  func (g *BitBucketCloudProvider) GetBranches(_ context.Context, repo *Repository) ([]*Repository, error) {
    66  	repos := []*Repository{}
    67  	branches, err := g.listBranches(repo)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
    70  	}
    71  
    72  	for _, branch := range branches {
    73  		hash, ok := branch.Target["hash"].(string)
    74  		if !ok {
    75  			return nil, fmt.Errorf("error getting SHA for branch for %s/%s/%s: %w", g.owner, repo.Repository, branch.Name, err)
    76  		}
    77  		repos = append(repos, &Repository{
    78  			Organization: repo.Organization,
    79  			Repository:   repo.Repository,
    80  			URL:          repo.URL,
    81  			Branch:       branch.Name,
    82  			SHA:          hash,
    83  			Labels:       repo.Labels,
    84  			RepositoryId: repo.RepositoryId,
    85  		})
    86  	}
    87  	return repos, nil
    88  }
    89  
    90  func (g *BitBucketCloudProvider) ListRepos(_ context.Context, cloneProtocol string) ([]*Repository, error) {
    91  	if cloneProtocol == "" {
    92  		cloneProtocol = "ssh"
    93  	}
    94  	opt := &bitbucket.RepositoriesOptions{
    95  		Owner: g.owner,
    96  		Role:  "member",
    97  	}
    98  	repos := []*Repository{}
    99  	accountReposResp, err := g.client.Repositories.ListForAccount(opt)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("error listing repositories for %s: %w", g.owner, err)
   102  	}
   103  	for _, bitBucketRepo := range accountReposResp.Items {
   104  		cloneURL, err := findCloneURL(cloneProtocol, &bitBucketRepo)
   105  		if err != nil {
   106  			return nil, fmt.Errorf("error fetching clone url for repo %s: %w", bitBucketRepo.Slug, err)
   107  		}
   108  		repos = append(repos, &Repository{
   109  			Organization: g.owner,
   110  			Repository:   bitBucketRepo.Slug,
   111  			Branch:       bitBucketRepo.Mainbranch.Name,
   112  			URL:          *cloneURL,
   113  			Labels:       []string{},
   114  			RepositoryId: bitBucketRepo.Uuid,
   115  		})
   116  	}
   117  	return repos, nil
   118  }
   119  
   120  func (g *BitBucketCloudProvider) RepoHasPath(_ context.Context, repo *Repository, path string) (bool, error) {
   121  	contents, err := g.client.GetContents(repo, path)
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  	if contents {
   126  		return true, nil
   127  	}
   128  	return false, nil
   129  }
   130  
   131  func (g *BitBucketCloudProvider) listBranches(repo *Repository) ([]bitbucket.RepositoryBranch, error) {
   132  	if !g.allBranches {
   133  		repoBranch, err := g.client.Repositories.Repository.GetBranch(&bitbucket.RepositoryBranchOptions{
   134  			Owner:      g.owner,
   135  			RepoSlug:   repo.Repository,
   136  			BranchName: repo.Branch,
   137  		})
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  		return []bitbucket.RepositoryBranch{
   142  			*repoBranch,
   143  		}, nil
   144  	}
   145  
   146  	branches, err := g.client.Repositories.Repository.ListBranches(&bitbucket.RepositoryBranchOptions{
   147  		Owner:    g.owner,
   148  		RepoSlug: repo.Repository,
   149  	})
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	return branches.Branches, nil
   154  }
   155  
   156  func findCloneURL(cloneProtocol string, repo *bitbucket.Repository) (*string, error) {
   157  	cloneLinks, ok := repo.Links["clone"].([]any)
   158  	if !ok {
   159  		return nil, errors.New("unknown type returned from repo links")
   160  	}
   161  	for _, link := range cloneLinks {
   162  		linkEntry, ok := link.(map[string]any)
   163  		if !ok {
   164  			return nil, errors.New("unknown type returned from clone link")
   165  		}
   166  		if linkEntry["name"] == cloneProtocol {
   167  			url, ok := linkEntry["href"].(string)
   168  			if !ok {
   169  				return nil, errors.New("could not find href for clone link")
   170  			}
   171  			return &url, nil
   172  		}
   173  	}
   174  	return nil, fmt.Errorf("unknown clone protocol for Bitbucket cloud %v", cloneProtocol)
   175  }