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 }