github.com/cobalt77/jfrog-client-go@v0.14.5/artifactory/services/xrayscan.go (about)

     1  package services
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"net/http"
     7  	"time"
     8  
     9  	rthttpclient "github.com/cobalt77/jfrog-client-go/artifactory/httpclient"
    10  	"github.com/cobalt77/jfrog-client-go/artifactory/services/utils"
    11  	"github.com/cobalt77/jfrog-client-go/auth"
    12  	"github.com/cobalt77/jfrog-client-go/httpclient"
    13  	clientutils "github.com/cobalt77/jfrog-client-go/utils"
    14  	"github.com/cobalt77/jfrog-client-go/utils/errorutils"
    15  )
    16  
    17  const SCAN_BUILD_API_URL = "api/xray/scanBuild"
    18  const XRAY_SCAN_RETRY_CONSECUTIVE_RETRIES = 10           // Retrying to resume the scan 10 times after a stable connection
    19  const XRAY_SCAN_CONNECTION_TIMEOUT = 90 * time.Second    // Expecting \r\n every 30 seconds
    20  const XRAY_SCAN_SLEEP_BETWEEN_RETRIES = 15 * time.Second // 15 seconds sleep between retry
    21  const XRAY_SCAN_STABLE_CONNECTION_WINDOW = 100 * time.Second
    22  const XRAY_FATAL_FAIL_STATUS = -1
    23  
    24  type XrayScanService struct {
    25  	client     *rthttpclient.ArtifactoryHttpClient
    26  	ArtDetails auth.ServiceDetails
    27  }
    28  
    29  func NewXrayScanService(client *rthttpclient.ArtifactoryHttpClient) *XrayScanService {
    30  	return &XrayScanService{client: client}
    31  }
    32  
    33  func (ps *XrayScanService) ScanBuild(scanParams XrayScanParams) ([]byte, error) {
    34  	url := ps.ArtDetails.GetUrl()
    35  	requestFullUrl, err := utils.BuildArtifactoryUrl(url, SCAN_BUILD_API_URL, make(map[string]string))
    36  	if err != nil {
    37  		return []byte{}, err
    38  	}
    39  	data := XrayScanBody{
    40  		BuildName:   scanParams.GetBuildName(),
    41  		BuildNumber: scanParams.GetBuildNumber(),
    42  		Context:     clientutils.GetUserAgent(),
    43  	}
    44  
    45  	requestContent, err := json.Marshal(data)
    46  	if err != nil {
    47  		return []byte{}, errorutils.CheckError(err)
    48  	}
    49  
    50  	connection := httpclient.RetryableConnection{
    51  		ReadTimeout:            XRAY_SCAN_CONNECTION_TIMEOUT,
    52  		RetriesNum:             XRAY_SCAN_RETRY_CONSECUTIVE_RETRIES,
    53  		StableConnectionWindow: XRAY_SCAN_STABLE_CONNECTION_WINDOW,
    54  		SleepBetweenRetries:    XRAY_SCAN_SLEEP_BETWEEN_RETRIES,
    55  		ConnectHandler: func() (*http.Response, error) {
    56  			return ps.execScanRequest(requestFullUrl, requestContent)
    57  		},
    58  		ErrorHandler: func(content []byte) error {
    59  			return checkForXrayResponseError(content, true)
    60  		},
    61  	}
    62  	result, err := connection.Do()
    63  	if err != nil {
    64  		return []byte{}, err
    65  	}
    66  
    67  	return result, nil
    68  }
    69  
    70  func isFatalScanError(errResp *errorResponse) bool {
    71  	if errResp == nil {
    72  		return false
    73  	}
    74  	for _, v := range errResp.Errors {
    75  		if v.Status == XRAY_FATAL_FAIL_STATUS {
    76  			return true
    77  		}
    78  	}
    79  	return false
    80  }
    81  
    82  func checkForXrayResponseError(content []byte, ignoreFatalError bool) error {
    83  	respErrors := &errorResponse{}
    84  	err := json.Unmarshal(content, respErrors)
    85  	if errorutils.CheckError(err) != nil {
    86  		return err
    87  	}
    88  
    89  	if respErrors.Errors == nil {
    90  		return nil
    91  	}
    92  
    93  	if ignoreFatalError && isFatalScanError(respErrors) {
    94  		// fatal error should be interpreted as no errors so no more retries will accrue
    95  		return nil
    96  	}
    97  	return errorutils.CheckError(errors.New("Artifactory response: " + string(content)))
    98  }
    99  
   100  func (ps *XrayScanService) execScanRequest(url string, content []byte) (*http.Response, error) {
   101  	httpClientsDetails := ps.ArtDetails.CreateHttpClientDetails()
   102  	utils.SetContentType("application/json", &httpClientsDetails.Headers)
   103  
   104  	// The scan build operation can take a long time to finish.
   105  	// To keep the connection open, when Xray starts scanning the build, it starts sending new-lines
   106  	// on the open channel. This tells the client that the operation is still in progress and the
   107  	// connection does not get timed out.
   108  	// We need make sure the new-lines are not buffered on the nginx and are flushed
   109  	// as soon as Xray sends them.
   110  	utils.DisableAccelBuffering(&httpClientsDetails.Headers)
   111  
   112  	resp, _, _, err := ps.client.Send("POST", url, content, true, false, &httpClientsDetails)
   113  	if err != nil {
   114  		return resp, err
   115  	}
   116  
   117  	if resp.StatusCode != http.StatusOK {
   118  		err = errorutils.CheckError(errors.New("Artifactory Response: " + resp.Status))
   119  	}
   120  	return resp, err
   121  }
   122  
   123  type errorResponse struct {
   124  	Errors []errorsStatusResponse `json:"errors,omitempty"`
   125  }
   126  
   127  type errorsStatusResponse struct {
   128  	Status int `json:"status,omitempty"`
   129  }
   130  
   131  type XrayScanBody struct {
   132  	BuildName   string `json:"buildName,omitempty"`
   133  	BuildNumber string `json:"buildNumber,omitempty"`
   134  	Context     string `json:"context,omitempty"`
   135  }
   136  
   137  type XrayScanParams struct {
   138  	BuildName   string
   139  	BuildNumber string
   140  }
   141  
   142  func (bp *XrayScanParams) GetBuildName() string {
   143  	return bp.BuildName
   144  }
   145  
   146  func (bp *XrayScanParams) GetBuildNumber() string {
   147  	return bp.BuildNumber
   148  }
   149  
   150  func NewXrayScanParams() XrayScanParams {
   151  	return XrayScanParams{}
   152  }