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 }