github.com/massongit/reviewdog@v0.0.0-20240331071725-4a16675475a8/service/bitbucket/cloud_api_client.go (about) 1 package bitbucket 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 10 bbapi "github.com/reviewdog/go-bitbucket" 11 ) 12 13 const ( 14 // PipelineProxyURL available while using Bitbucket Pipelines and 15 // allows you to use the Reports-API without extra authentication. 16 // For that you need to send your request through a proxy server that runs alongside with 17 // every pipeline on ‘localhost:29418’, and a valid Auth-Header will automatically be added to your request. 18 // https://support.atlassian.com/bitbucket-cloud/docs/code-insights/#Authentication 19 // However, if using proxy HTTP API endpoint need to be used 20 pipelineProxyURL = "http://localhost:29418" 21 22 // PipeProxyURL is to be used when reviewdog is running within a Bitbucket Pipe 23 // Pipes run in docker containers and as a result will need to connect to the proxy via this Docker DNS. 24 pipeProxyURL = "http://host.docker.internal:29418" 25 ) 26 27 // CloudAPIClient is wrapper for Bitbucket Cloud Code Insights API client 28 type CloudAPIClient struct { 29 cli *bbapi.APIClient 30 helper *CloudAPIHelper 31 } 32 33 // NewCloudAPIClient creates client for Bitbucket Cloud Insights API 34 func NewCloudAPIClient(isInPipeline bool, isInPipe bool) APIClient { 35 httpClient := &http.Client{ 36 Timeout: httpTimeout, 37 } 38 39 server := bbapi.ServerConfiguration{ 40 URL: "https://api.bitbucket.org/2.0", 41 Description: `HTTPS API endpoint`, 42 } 43 44 if isInPipeline { 45 var proxyURL *url.URL 46 if isInPipe { 47 // if we are executing a pipe within a pipeline, use docker endpoint 48 // and proxy 49 proxyURL, _ = url.Parse(pipeProxyURL) 50 } else { 51 // if we are on the Bitbucket Pipeline, use HTTP endpoint 52 // and proxy 53 proxyURL, _ = url.Parse(pipelineProxyURL) 54 } 55 56 httpClient.Transport = &http.Transport{ 57 Proxy: http.ProxyURL(proxyURL), 58 } 59 60 server = bbapi.ServerConfiguration{ 61 URL: "http://api.bitbucket.org/2.0", 62 Description: `If called from Bitbucket Pipelines, using HTTP API endpoint and AuthProxy`, 63 } 64 } 65 66 return NewCloudAPIClientWithConfigurations(httpClient, server) 67 } 68 69 // NewCloudAPIClientWithConfigurations creates client for Bitbucket Cloud Insights API with specified configuration 70 func NewCloudAPIClientWithConfigurations(client *http.Client, server bbapi.ServerConfiguration) APIClient { 71 config := bbapi.NewConfiguration() 72 if client != nil { 73 config.HTTPClient = client 74 } else { 75 config.HTTPClient = &http.Client{ 76 Timeout: httpTimeout, 77 } 78 } 79 config.Servers = bbapi.ServerConfigurations{server} 80 81 return &CloudAPIClient{ 82 cli: bbapi.NewAPIClient(config), 83 helper: &CloudAPIHelper{}, 84 } 85 } 86 87 // CreateOrUpdateReport creates or updates specified report 88 func (c *CloudAPIClient) CreateOrUpdateReport(ctx context.Context, req *ReportRequest) error { 89 _, resp, err := c.cli. 90 ReportsApi.CreateOrUpdateReport(ctx, req.Owner, req.Repository, req.Commit, req.ReportID). 91 Body(c.helper.BuildReport(req)). 92 Execute() 93 94 if err := c.checkAPIError(err, resp, http.StatusOK); err != nil { 95 return fmt.Errorf("failed to create code insights report: %w", err) 96 } 97 98 return nil 99 } 100 101 // CreateOrUpdateAnnotations creates or updates annotations 102 func (c *CloudAPIClient) CreateOrUpdateAnnotations(ctx context.Context, req *AnnotationsRequest) error { 103 _, resp, err := c.cli.ReportsApi. 104 BulkCreateOrUpdateAnnotations(ctx, req.Owner, req.Repository, req.Commit, req.ReportID). 105 Body(c.helper.BuildAnnotations(req.Comments)). 106 Execute() 107 108 if err := c.checkAPIError(err, resp, http.StatusOK); err != nil { 109 return fmt.Errorf("failed to create code insights annotations: %w", err) 110 } 111 112 return nil 113 } 114 115 func (c *CloudAPIClient) checkAPIError(err error, resp *http.Response, expectedCode int) error { 116 if err != nil { 117 return fmt.Errorf("bitbucket Cloud API error: %w", err) 118 } 119 120 if resp != nil && resp.StatusCode != expectedCode { 121 body, _ := io.ReadAll(resp.Body) 122 123 return UnexpectedResponseError{ 124 Code: resp.StatusCode, 125 Body: body, 126 } 127 } 128 129 return nil 130 }