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

     1  package pull_request
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/ktrysmt/go-bitbucket"
    12  )
    13  
    14  type BitbucketCloudService struct {
    15  	client         *bitbucket.Client
    16  	owner          string
    17  	repositorySlug string
    18  }
    19  
    20  type BitbucketCloudPullRequest struct {
    21  	ID          int                                  `json:"id"`
    22  	Title       string                               `json:"title"`
    23  	Source      BitbucketCloudPullRequestSource      `json:"source"`
    24  	Author      BitbucketCloudPullRequestAuthor      `json:"author"`
    25  	Destination BitbucketCloudPullRequestDestination `json:"destination"`
    26  }
    27  
    28  type BitbucketCloudPullRequestDestination struct {
    29  	Branch BitbucketCloudPullRequestDestinationBranch `json:"branch"`
    30  }
    31  
    32  type BitbucketCloudPullRequestDestinationBranch struct {
    33  	Name string `json:"name"`
    34  }
    35  
    36  type BitbucketCloudPullRequestSource struct {
    37  	Branch BitbucketCloudPullRequestSourceBranch `json:"branch"`
    38  	Commit BitbucketCloudPullRequestSourceCommit `json:"commit"`
    39  }
    40  
    41  type BitbucketCloudPullRequestSourceBranch struct {
    42  	Name string `json:"name"`
    43  }
    44  
    45  type BitbucketCloudPullRequestSourceCommit struct {
    46  	Hash string `json:"hash"`
    47  }
    48  
    49  // Also have display_name and uuid, but don't plan to use them.
    50  type BitbucketCloudPullRequestAuthor struct {
    51  	Nickname string `json:"nickname"`
    52  }
    53  
    54  type PullRequestResponse struct {
    55  	Page     int32         `json:"page"`
    56  	Size     int32         `json:"size"`
    57  	Pagelen  int32         `json:"pagelen"`
    58  	Next     string        `json:"next"`
    59  	Previous string        `json:"previous"`
    60  	Items    []PullRequest `json:"values"`
    61  }
    62  
    63  var _ PullRequestService = (*BitbucketCloudService)(nil)
    64  
    65  func parseURL(uri string) (*url.URL, error) {
    66  	if uri == "" {
    67  		uri = "https://api.bitbucket.org/2.0"
    68  	}
    69  
    70  	url, err := url.Parse(uri)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	return url, nil
    76  }
    77  
    78  func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repositorySlug string) (PullRequestService, error) {
    79  	url, err := parseURL(baseURL)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
    82  	}
    83  
    84  	bitbucketClient := bitbucket.NewBasicAuth(username, password)
    85  	bitbucketClient.SetApiBaseURL(*url)
    86  
    87  	return &BitbucketCloudService{
    88  		client:         bitbucketClient,
    89  		owner:          owner,
    90  		repositorySlug: repositorySlug,
    91  	}, nil
    92  }
    93  
    94  func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
    95  	url, err := parseURL(baseURL)
    96  	if err != nil {
    97  		return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
    98  	}
    99  
   100  	bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
   101  	bitbucketClient.SetApiBaseURL(*url)
   102  
   103  	return &BitbucketCloudService{
   104  		client:         bitbucketClient,
   105  		owner:          owner,
   106  		repositorySlug: repositorySlug,
   107  	}, nil
   108  }
   109  
   110  func NewBitbucketCloudServiceNoAuth(baseURL, owner, repositorySlug string) (PullRequestService, error) {
   111  	// There is currently no method to explicitly not require auth
   112  	return NewBitbucketCloudServiceBearerToken(baseURL, "", owner, repositorySlug)
   113  }
   114  
   115  func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) {
   116  	opts := &bitbucket.PullRequestsOptions{
   117  		Owner:    b.owner,
   118  		RepoSlug: b.repositorySlug,
   119  	}
   120  
   121  	pullRequests := []*PullRequest{}
   122  
   123  	response, err := b.client.Repositories.PullRequests.Gets(opts)
   124  	if err != nil {
   125  		// A standard Http 404 error is not returned for Bitbucket Cloud,
   126  		// so checking the error message for a specific pattern
   127  		if strings.Contains(err.Error(), "404 Not Found") {
   128  			// return a custom error indicating that the repository is not found,
   129  			// but also return the empty result since the decision to continue or not in this case is made by the caller
   130  			return pullRequests, NewRepositoryNotFoundError(err)
   131  		}
   132  		return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err)
   133  	}
   134  
   135  	resp, ok := response.(map[string]any)
   136  	if !ok {
   137  		return nil, errors.New("unknown type returned from bitbucket pull requests")
   138  	}
   139  
   140  	repoArray, ok := resp["values"].([]any)
   141  	if !ok {
   142  		return nil, errors.New("unknown type returned from response values")
   143  	}
   144  
   145  	jsonStr, err := json.Marshal(repoArray)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("error marshalling response body to json: %w", err)
   148  	}
   149  
   150  	var pulls []BitbucketCloudPullRequest
   151  	if err := json.Unmarshal(jsonStr, &pulls); err != nil {
   152  		return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %w", err)
   153  	}
   154  
   155  	for _, pull := range pulls {
   156  		pullRequests = append(pullRequests, &PullRequest{
   157  			Number:       pull.ID,
   158  			Title:        pull.Title,
   159  			Branch:       pull.Source.Branch.Name,
   160  			TargetBranch: pull.Destination.Branch.Name,
   161  			HeadSHA:      pull.Source.Commit.Hash,
   162  			Author:       pull.Author.Nickname,
   163  		})
   164  	}
   165  
   166  	return pullRequests, nil
   167  }