pkg/gitprovider/bitbucket.go (220 lines of code) (raw):

package gitprovider import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/utils/ptr" "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider/bitbucket/generated" ) type BitbucketClient struct { client generated.ClientWithResponsesInterface } type BitbucketClientOpts struct { Url string } type BitbucketClientOptsSetter func(*BitbucketClientOpts) func WithBitbucketClientUrl(url string) func(*BitbucketClientOpts) { return func(opts *BitbucketClientOpts) { opts.Url = url } } func NewBitbucketClient(token string, opts ...BitbucketClientOptsSetter) (*BitbucketClient, error) { defaults := &BitbucketClientOpts{ Url: "https://api.bitbucket.org/2.0", } for _, o := range opts { o(defaults) } tokenProvider := NewBasicTokenAuthProvider(token) c, err := generated.NewClientWithResponses( defaults.Url, generated.WithRequestEditorFn(tokenProvider.Intercept), ) if err != nil { return nil, fmt.Errorf("failed to create Bitbucket client: %w", err) } return &BitbucketClient{ client: c, }, nil } func (b *BitbucketClient) CreateWebHook(ctx context.Context, _, _, projectID, webHookSecret, webHookURL string, skipTLS bool) (*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } r, err := b.client.PostRepositoriesWorkspaceRepoSlugHooksWithResponse( ctx, owner, repo, func(ctx context.Context, req *http.Request) error { var body []byte body, err = json.Marshal(map[string]interface{}{ "description": fmt.Sprintf("Automatically created %s", uuid.NewUUID()), "url": webHookURL, "active": true, "secret": webHookSecret, "skip_cert_verification": skipTLS, "history_enabled": true, "events": []string{ "pullrequest:created", "pullrequest:updated", "pullrequest:fulfilled", "pullrequest:comment_created", "pullrequest:comment_updated", }, }) if err != nil { return fmt.Errorf("failed to marshal Bitbucket web hook body: %w", err) } req.Body = io.NopCloser(bytes.NewReader(body)) return nil }, ) if err != nil { return nil, fmt.Errorf("failed to create Bitbucket web hook: %w", err) } if !createObjectStatusOk(r.StatusCode()) { return nil, fmt.Errorf("failed to create Bitbucket web hook: %s %s", r.Status(), r.Body) } if r.JSON201 == nil || r.JSON201.Uuid == nil || r.JSON201.Url == nil { return nil, fmt.Errorf("failed to create Bitbucket web hook: invalid response %s", r.Body) } return &WebHook{ ID: *r.JSON201.Uuid, URL: *r.JSON201.Url, }, nil } func (b *BitbucketClient) CreateWebHookIfNotExists(ctx context.Context, _, _, projectID, webHookSecret, webHookURL string, skipTLS bool) (*WebHook, error) { webHooks, err := b.GetWebHooks(ctx, "", "", projectID) if err != nil { return nil, err } for _, webHook := range webHooks { if webHook.URL == webHookURL { return webHook, nil } } return b.CreateWebHook(ctx, "", "", projectID, webHookSecret, webHookURL, skipTLS) } func (b *BitbucketClient) GetWebHook(ctx context.Context, _, _, projectID, webHookRef string) (*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } r, err := b.client.GetRepositoriesWorkspaceRepoSlugHooksUidWithResponse(ctx, owner, repo, webHookRef) if err != nil { return nil, fmt.Errorf("failed to get Bitbucket web hook: %w", err) } if r.StatusCode() != http.StatusOK { return nil, fmt.Errorf("failed to get Bitbucket web hook: %s %s", r.Status(), r.Body) } if r.JSON200 == nil || r.JSON200.Uuid == nil || r.JSON200.Url == nil { return nil, fmt.Errorf("failed to get Bitbucket web hook: invalid response %s", r.Body) } return &WebHook{ ID: *r.JSON200.Uuid, URL: *r.JSON200.Url, }, nil } func (b *BitbucketClient) GetWebHooks(ctx context.Context, _, _, projectID string) ([]*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } r, err := b.client.GetRepositoriesWorkspaceRepoSlugHooksWithResponse(ctx, owner, repo) if err != nil { return nil, fmt.Errorf("failed to get Bitbucket web hooks: %w", err) } if r.StatusCode() != http.StatusOK { return nil, fmt.Errorf("failed to get Bitbucket web hooks: %s %s", r.Status(), r.Body) } if r.JSON200 == nil || r.JSON200.Values == nil { return []*WebHook{}, nil } webHooks := make([]*WebHook, len(*r.JSON200.Values)) for i, hook := range *r.JSON200.Values { if hook.Uuid == nil || hook.Url == nil { return nil, fmt.Errorf("failed to get Bitbucket web hooks: invalid response %s", r.Body) } webHooks[i] = &WebHook{ ID: *hook.Uuid, URL: *hook.Url, } } return webHooks, nil } func (b *BitbucketClient) DeleteWebHook(ctx context.Context, _, _, projectID, webHookRef string) error { owner, repo, err := parseProjectID(projectID) if err != nil { return err } r, err := b.client.DeleteRepositoriesWorkspaceRepoSlugHooksUidWithResponse(ctx, owner, repo, webHookRef) if err != nil { return fmt.Errorf("failed to delete Bitbucket web hook: %w", err) } if r.StatusCode() != http.StatusNoContent { if r.StatusCode() == http.StatusNotFound { return fmt.Errorf("failed to delete Bitbucket web hook: %w", ErrWebHookNotFound) } return fmt.Errorf("failed to delete Bitbucket web hook: %s %s", r.Status(), r.Body) } return nil } func (b *BitbucketClient) CreateProject(ctx context.Context, _, _, projectID string) error { owner, repo, err := parseProjectID(projectID) if err != nil { return err } reqBody := generated.PostRepositoriesWorkspaceRepoSlugJSONRequestBody{ Type: "repository", IsPrivate: ptr.To(true), } r, err := b.client.PostRepositoriesWorkspaceRepoSlugWithResponse(ctx, owner, repo, reqBody) if err != nil { return fmt.Errorf("failed to create Bitbucket repository: %w", err) } if !createObjectStatusOk(r.StatusCode()) { return fmt.Errorf("failed to create Bitbucket repository: %s %s", r.Status(), r.Body) } return nil } func (b *BitbucketClient) ProjectExists(ctx context.Context, _, _, projectID string) (bool, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return false, err } r, err := b.client.GetRepositoriesWorkspaceWithResponse( ctx, owner, nil, func(ctx context.Context, req *http.Request) error { // nolint: gocritic // Can't use %q instead of "%s" because need double quotes. req.URL.RawQuery = fmt.Sprintf(`q=slug="%s"`, repo) return nil }, ) if err != nil { return false, fmt.Errorf("failed to get Bitbucket repository: %w", err) } if r.StatusCode() != http.StatusOK { return false, fmt.Errorf("failed to get Bitbucket repository: %s %s", r.Status(), r.Body) } if r.JSON200 == nil || r.JSON200.Values == nil { return false, nil } // nolint: gocritic // Force to use copy value. for _, v := range *r.JSON200.Values { if v.FullName != nil && *v.FullName == projectID { return true, nil } } return false, nil } func (*BitbucketClient) SetDefaultBranch(_ context.Context, _, _, _, _ string) error { // Set default branch is not supported by Bitbucket API. // Open ticket: https://jira.atlassian.com/browse/BCLOUD-20340 // https://community.atlassian.com/t5/Bitbucket-questions/Get-and-set-default-branch-in-v2-API/qaq-p/2416227 return fmt.Errorf("setting default branch in Bitbucket repository: %w", ErrApiNotSupported) } func createObjectStatusOk(statusCode int) bool { return statusCode == http.StatusOK || statusCode == http.StatusCreated }