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  }