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  }