github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/services/scm_provider/bitbucket_server.go (about) 1 package scm_provider 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 8 "github.com/argoproj/argo-cd/v2/applicationset/utils" 9 bitbucketv1 "github.com/gfleury/go-bitbucket-v1" 10 log "github.com/sirupsen/logrus" 11 ) 12 13 type BitbucketServerProvider struct { 14 client *bitbucketv1.APIClient 15 projectKey string 16 allBranches bool 17 } 18 19 var _ SCMProviderService = &BitbucketServerProvider{} 20 21 func NewBitbucketServerProviderBasicAuth(ctx context.Context, username, password, url, projectKey string, allBranches bool) (*BitbucketServerProvider, error) { 22 bitbucketConfig := bitbucketv1.NewConfiguration(url) 23 // Avoid the XSRF check 24 bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check") 25 bitbucketConfig.AddDefaultHeader("x-requested-with", "XMLHttpRequest") 26 27 ctx = context.WithValue(ctx, bitbucketv1.ContextBasicAuth, bitbucketv1.BasicAuth{ 28 UserName: username, 29 Password: password, 30 }) 31 return newBitbucketServerProvider(ctx, bitbucketConfig, projectKey, allBranches) 32 } 33 34 func NewBitbucketServerProviderNoAuth(ctx context.Context, url, projectKey string, allBranches bool) (*BitbucketServerProvider, error) { 35 return newBitbucketServerProvider(ctx, bitbucketv1.NewConfiguration(url), projectKey, allBranches) 36 } 37 38 func newBitbucketServerProvider(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey string, allBranches bool) (*BitbucketServerProvider, error) { 39 bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath) 40 bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig) 41 42 return &BitbucketServerProvider{ 43 client: bitbucketClient, 44 projectKey: projectKey, 45 allBranches: allBranches, 46 }, nil 47 } 48 49 func (b *BitbucketServerProvider) ListRepos(_ context.Context, cloneProtocol string) ([]*Repository, error) { 50 paged := map[string]interface{}{ 51 "limit": 100, 52 } 53 repos := []*Repository{} 54 for { 55 response, err := b.client.DefaultApi.GetRepositoriesWithOptions(b.projectKey, paged) 56 if err != nil { 57 return nil, fmt.Errorf("error listing repositories for %s: %v", b.projectKey, err) 58 } 59 repositories, err := bitbucketv1.GetRepositoriesResponse(response) 60 if err != nil { 61 log.Errorf("error parsing repositories response '%v'", response.Values) 62 return nil, fmt.Errorf("error parsing repositories response %s: %v", b.projectKey, err) 63 } 64 for _, bitbucketRepo := range repositories { 65 var url string 66 switch cloneProtocol { 67 // Default to SSH if unspecified (i.e. if ""). 68 case "", "ssh": 69 url = getCloneURLFromLinks(bitbucketRepo.Links.Clone, "ssh") 70 case "https": 71 url = getCloneURLFromLinks(bitbucketRepo.Links.Clone, "http") 72 default: 73 return nil, fmt.Errorf("unknown clone protocol for Bitbucket Server %v", cloneProtocol) 74 } 75 76 org := bitbucketRepo.Project.Key 77 repo := bitbucketRepo.Name 78 // Bitbucket doesn't return the default branch in the repo query, fetch it here 79 branch, err := b.getDefaultBranch(org, repo) 80 if err != nil { 81 return nil, err 82 } 83 if branch == nil { 84 log.Debugf("%s/%s does not have a default branch, skipping", org, repo) 85 continue 86 } 87 88 repos = append(repos, &Repository{ 89 Organization: org, 90 Repository: repo, 91 URL: url, 92 Branch: branch.DisplayID, 93 SHA: branch.LatestCommit, 94 Labels: []string{}, // Not supported by library 95 RepositoryId: bitbucketRepo.ID, 96 }) 97 } 98 hasNextPage, nextPageStart := bitbucketv1.HasNextPage(response) 99 if !hasNextPage { 100 break 101 } 102 paged["start"] = nextPageStart 103 } 104 return repos, nil 105 } 106 107 func (b *BitbucketServerProvider) RepoHasPath(_ context.Context, repo *Repository, path string) (bool, error) { 108 opts := map[string]interface{}{ 109 "limit": 100, 110 "at": repo.Branch, 111 "type_": true, 112 } 113 // No need to query for all pages here 114 response, err := b.client.DefaultApi.GetContent_0(repo.Organization, repo.Repository, path, opts) 115 if response != nil && response.StatusCode == 404 { 116 // File/directory not found 117 return false, nil 118 } 119 if err != nil { 120 return false, err 121 } 122 return true, nil 123 } 124 125 func (b *BitbucketServerProvider) GetBranches(_ context.Context, repo *Repository) ([]*Repository, error) { 126 repos := []*Repository{} 127 branches, err := b.listBranches(repo) 128 if err != nil { 129 return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err) 130 } 131 132 for _, branch := range branches { 133 repos = append(repos, &Repository{ 134 Organization: repo.Organization, 135 Repository: repo.Repository, 136 URL: repo.URL, 137 Branch: branch.DisplayID, 138 SHA: branch.LatestCommit, 139 Labels: repo.Labels, 140 RepositoryId: repo.RepositoryId, 141 }) 142 } 143 return repos, nil 144 } 145 146 func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.Branch, error) { 147 // If we don't specifically want to query for all branches, just use the default branch and call it a day. 148 if !b.allBranches { 149 branch, err := b.getDefaultBranch(repo.Organization, repo.Repository) 150 if err != nil { 151 return nil, err 152 } 153 if branch == nil { 154 return []bitbucketv1.Branch{}, nil 155 } 156 return []bitbucketv1.Branch{*branch}, nil 157 } 158 // Otherwise, scrape the GetBranches API. 159 branches := []bitbucketv1.Branch{} 160 paged := map[string]interface{}{ 161 "limit": 100, 162 } 163 for { 164 response, err := b.client.DefaultApi.GetBranches(repo.Organization, repo.Repository, paged) 165 if err != nil { 166 return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err) 167 } 168 bitbucketBranches, err := bitbucketv1.GetBranchesResponse(response) 169 if err != nil { 170 log.Errorf("error parsing branches response '%v'", response.Values) 171 return nil, fmt.Errorf("error parsing branches response for %s/%s: %v", repo.Organization, repo.Repository, err) 172 } 173 174 branches = append(branches, bitbucketBranches...) 175 176 hasNextPage, nextPageStart := bitbucketv1.HasNextPage(response) 177 if !hasNextPage { 178 break 179 } 180 paged["start"] = nextPageStart 181 } 182 return branches, nil 183 } 184 185 func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bitbucketv1.Branch, error) { 186 response, err := b.client.DefaultApi.GetDefaultBranch(org, repo) 187 // The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset, 188 // we will get an EOF and a nil response. 189 if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) { 190 return nil, nil 191 } 192 if err != nil { 193 return nil, err 194 } 195 branch, err := bitbucketv1.GetBranchResponse(response) 196 if err != nil { 197 return nil, err 198 } 199 return &branch, nil 200 } 201 202 func getCloneURLFromLinks(links []bitbucketv1.CloneLink, name string) string { 203 for _, link := range links { 204 if link.Name == name { 205 return link.Href 206 } 207 } 208 return "" 209 }