Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 64 additions & 35 deletions internal/wrappers/export-http.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ func NewExportHTTPWrapper(path string) ExportWrapper {
}

const (
retryInterval = 5 * time.Second
timeout = 2 * time.Minute
retryInterval = 5 * time.Second
timeout = 2 * time.Minute
errorTimeoutMsg = "operation timed out: failed to retrieve export report"
)

func (e *ExportHTTPWrapper) InitiateExportRequest(payload *ExportRequestPayload) (*ExportResponse, error) {
Expand Down Expand Up @@ -84,20 +85,20 @@ func (e *ExportHTTPWrapper) InitiateExportRequest(payload *ExportRequestPayload)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse response body")
}
resp.Body.Close()
_ = resp.Body.Close()
return &model, nil
case http.StatusBadRequest:
if time.Now().After(endTime) {
log.Printf("Timeout reached after %d attempts. Last response status code: %d", retryCount+1, resp.StatusCode)
resp.Body.Close()
_ = resp.Body.Close()
return nil, errors.Errorf("failed to initiate export request - response status code %d", resp.StatusCode)
}
retryCount++
logger.PrintfIfVerbose("Received 400 Bad Request. Retrying in %v... (attempt %d/%d)", retryInterval, retryCount, maxRetries)
time.Sleep(retryInterval)
default:
logger.PrintfIfVerbose("Received unexpected status code %d", resp.StatusCode)
resp.Body.Close()
_ = resp.Body.Close()
return nil, errors.Errorf("response status code %d", resp.StatusCode)
}
}
Expand All @@ -107,27 +108,37 @@ func (e *ExportHTTPWrapper) GetExportReportStatus(reportID string) (*ExportPolli
clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey)
path := fmt.Sprintf("%s/%s", e.path, "requests")
params := map[string]string{"returnUrl": "true", "exportId": reportID}
resp, err := SendPrivateHTTPRequestWithQueryParams(http.MethodGet, path, params, nil, clientTimeout)
if err != nil {
return nil, err
}

defer func() {
_ = resp.Body.Close()
}()
start := time.Now()

decoder := json.NewDecoder(resp.Body)
for {
if time.Since(start) > timeout {
return nil, errors.New(errorTimeoutMsg)
}

switch resp.StatusCode {
case http.StatusOK:
model := ExportPollingResponse{}
err = decoder.Decode(&model)
resp, err := SendPrivateHTTPRequestWithQueryParams(http.MethodGet, path, params, nil, clientTimeout)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse response body")
time.Sleep(retryInterval)
continue
}

decoder := json.NewDecoder(resp.Body)

switch resp.StatusCode {
case http.StatusOK:
model := ExportPollingResponse{}
if err = decoder.Decode(&model); err != nil {
_ = resp.Body.Close()
return nil, errors.Wrapf(err, "failed to parse response body")
}
return &model, nil
case http.StatusNotFound:
_ = resp.Body.Close()
time.Sleep(time.Second)
default:
_ = resp.Body.Close()
return nil, errors.Errorf("response status code %d", resp.StatusCode)
}
return &model, nil
default:
return nil, errors.Errorf("response status code %d", resp.StatusCode)
}
}

Expand All @@ -151,7 +162,11 @@ func (e *ExportHTTPWrapper) DownloadExportReport(reportID, targetFile string) er
if err != nil {
return errors.Wrapf(err, "Failed to create file %s", targetFile)
}
defer file.Close()

defer func(file *os.File) {
_ = file.Close()
}(file)

size, err := io.Copy(file, resp.Body)
if err != nil {
return errors.Wrapf(err, "Failed to write file %s", targetFile)
Expand All @@ -162,30 +177,44 @@ func (e *ExportHTTPWrapper) DownloadExportReport(reportID, targetFile string) er
}

func (e *ExportHTTPWrapper) GetScaPackageCollectionExport(fileURL string) (*ScaPackageCollectionExport, error) {
const bomPrefix = "\xef\xbb\xbf"

accessToken, err := GetAccessToken()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to get access token")
}

resp, err := SendHTTPRequestByFullURL(http.MethodGet, fileURL, http.NoBody, true, viper.GetUint(commonParams.ClientTimeoutKey), accessToken, true)
if err != nil {
return nil, err
start := time.Now()
var resp *http.Response

for {
if time.Since(start) > timeout {
return nil, errors.New(errorTimeoutMsg)
}

resp, err = SendHTTPRequestByFullURL(http.MethodGet, fileURL, http.NoBody, true, viper.GetUint(commonParams.ClientTimeoutKey), accessToken, true)
if err == nil && resp.StatusCode == http.StatusOK {
break
}
_ = resp.Body.Close()
time.Sleep(retryInterval)
}
defer resp.Body.Close()

defer func() {
_ = resp.Body.Close()
}()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to read response body")
}
body = bytes.TrimPrefix(body, []byte(bomPrefix))

// Remove BOM if present
body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf"))

var scaPackageCollection ScaPackageCollectionExport
if err := json.Unmarshal(body, &scaPackageCollection); err != nil {
return nil, err
scaPackageCollection := &ScaPackageCollectionExport{}
if err = json.Unmarshal(body, scaPackageCollection); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal response body")
}

logger.PrintIfVerbose("Retrieved SCA package collection export successfully")
return &scaPackageCollection, nil
return scaPackageCollection, nil
}
Loading