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 }