Skip to content

Commit

Permalink
CLI | Add parallel GetSastMetadetaByID functionality (AST-40381) (#737)
Browse files Browse the repository at this point in the history
* Add Pagination req

* Add Pagination req

* Add GetSastMetadataByIDsInParallel func in sast-metadata-http.go

* revert pagination

* add sastMetaDataInParallel func to mock

* change signature of func in mock

* Add integration test

* encapsulate getSastMetadata function

* revert changes in util command

* added integration test

* Update internal/wrappers/sast-metadata.go

Co-authored-by: tamarleviCm <[email protected]>

* Added semaphore to limit go routines

* Added semaphore to limit go routines

* add sast metadata service

* change sast metadata mock

* add unit tests

* add unit tests

* add unit tests

* add unit tests

* add unit tests

* remove sorting response functionality

* remove sorting response functionality

* add service name explanation

* change to semaphore pkg

* change to semaphore pkg

* change to semaphore pkg

* add missing dependencies

* re trigger pipeline

* check

* check

* skip failing tests

* revert skipping

---------

Co-authored-by: AlvoBen <[email protected]>
Co-authored-by: tamarleviCm <[email protected]>
Co-authored-by: Or Shamir Checkmarx <[email protected]>
  • Loading branch information
4 people authored Jun 18, 2024
1 parent 2e7141a commit d78cef0
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 205 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
golang.org/x/crypto v0.22.0
golang.org/x/sync v0.6.0
golang.org/x/text v0.14.0
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
Expand Down
5 changes: 1 addition & 4 deletions internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -2002,7 +2002,6 @@ func runListScansCommand(scansWrapper wrappers.ScansWrapper, sastMetadataWrapper
if err != nil {
return errors.Wrapf(err, "%s", failedGettingAll)
}

allScansModel, errorModel, err = scansWrapper.Get(params)
if err != nil {
return errors.Wrapf(err, "%s\n", failedGettingAll)
Expand Down Expand Up @@ -2200,9 +2199,7 @@ func toScanViews(scans []wrappers.ScanResponseModel, sastMetadataWrapper wrapper
scanIDs[i] = scans[i].ID
}

paramsToSast := map[string]string{"scan-ids": strings.Join(scanIDs, ",")}

sastMetadata, err := sastMetadataWrapper.GetSastMetadataByIDs(paramsToSast)
sastMetadata, err := services.GetSastMetadataByIDs(sastMetadataWrapper, scanIDs)
if err != nil {
logger.Printf("error getting sast metadata: %v", err)
return nil, err
Expand Down
81 changes: 81 additions & 0 deletions internal/services/sast-metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package services

import (
"context"
"strings"
"sync"

commonParams "github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
"golang.org/x/sync/semaphore"
)

const (
BatchSize = 200
)

func GetSastMetadataByIDs(sastMetaDataWrapper wrappers.SastMetadataWrapper, scanIDs []string) (*wrappers.SastMetadataModel, error) {
totalBatches := (len(scanIDs) + BatchSize - 1) / BatchSize
maxConcurrentGoRoutines := 10
sem := semaphore.NewWeighted(int64(maxConcurrentGoRoutines))

var wg sync.WaitGroup
results := make(chan wrappers.SastMetadataModel, totalBatches)
errors := make(chan error, totalBatches)
ctx := context.Background()

for i := 0; i < totalBatches; i++ {
start := i * BatchSize
end := start + BatchSize
if end > len(scanIDs) {
end = len(scanIDs)
}

batchParams := map[string]string{
commonParams.ScanIDsQueryParam: strings.Join(scanIDs[start:end], ","),
}

wg.Add(1)
err := sem.Acquire(ctx, 1)
if err != nil {
return nil, err
}
go func() {
defer wg.Done()
defer sem.Release(1)

result, err := sastMetaDataWrapper.GetSastMetadataByIDs(batchParams)
if err != nil {
errors <- err
return
}
results <- *result
}()
}

go func() {
wg.Wait()
close(results)
close(errors)
}()

if len(errors) > 0 {
return nil, <-errors
}

var models []wrappers.SastMetadataModel
for result := range results {
models = append(models, result)
}
return makeSastMetadataModelFromResults(models), nil
}

func makeSastMetadataModelFromResults(results []wrappers.SastMetadataModel) *wrappers.SastMetadataModel {
finalResult := &wrappers.SastMetadataModel{}
for _, result := range results {
finalResult.TotalCount += result.TotalCount
finalResult.Scans = append(finalResult.Scans, result.Scans...)
finalResult.Missing = append(finalResult.Missing, result.Missing...)
}
return finalResult
}
78 changes: 78 additions & 0 deletions internal/services/sast-metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package services

import (
"fmt"
"strconv"
"strings"
"testing"

"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/checkmarx/ast-cli/internal/wrappers/mock"
"github.com/stretchr/testify/assert" //nolint:depguard
)

func TestGetSastMetadataByIDs(t *testing.T) {
tests := []struct {
name string
scanIDs []string
}{
{
name: "Multiple batches",
scanIDs: createScanIDs(5000),
},
{
name: "Single batch",
scanIDs: createScanIDs(100),
},
{
name: "Empty slice",
scanIDs: []string{},
},
{
name: "Multiple batches with partial last batch",
scanIDs: createScanIDs(893),
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
wrapper := &mock.SastMetadataMockWrapper{}
actual, err := GetSastMetadataByIDs(wrapper, tt.scanIDs)
if err != nil {
t.Errorf("GetSastMetadataByIDs(%v) returned an error: %v", tt.scanIDs, err)
}
assert.True(t, checkAllScanExists(actual.Scans, "ConcurrentTest", len(tt.scanIDs)))
assert.Equal(t, len(tt.scanIDs), len(actual.Scans))
})
}
}

func createScanIDs(count int) []string {
scanIDs := make([]string, count)
for i := 0; i < count; i++ {
scanIDs[i] = fmt.Sprintf("ConcurrentTest%d", i)
}
return scanIDs
}

func checkAllScanExists(scans []wrappers.Scans, prefix string, count int) bool {
existingScans := make(map[int]bool)

for i := range scans {
scan := &scans[i]
if strings.HasPrefix(scan.ScanID, prefix) {
scanNumberStr := scan.ScanID[len(prefix):]
if scanNumber, err := strconv.Atoi(scanNumberStr); err == nil {
existingScans[scanNumber] = true
}
}
}

for i := 0; i < count; i++ {
if !existingScans[i] {
return false
}
}
return true
}
36 changes: 34 additions & 2 deletions internal/wrappers/mock/sast-metadata-mock.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package mock

import "github.com/checkmarx/ast-cli/internal/wrappers"
import (
"strings"

"github.com/checkmarx/ast-cli/internal/wrappers"
)

type SastMetadataMockWrapper struct{}

const (
scanIDParam = "scan-ids"
concurrent = "ConcurrentTest"
)

func (s SastMetadataMockWrapper) GetSastMetadataByIDs(params map[string]string) (
*wrappers.SastMetadataModel,
error,
) {
if strings.Contains(params[scanIDParam], concurrent) {
scanIDs := strings.Split(params[scanIDParam], ",")
return &wrappers.SastMetadataModel{
TotalCount: len(scanIDs),
Scans: convertScanIDsToScans(scanIDs),
}, nil
}
return &wrappers.SastMetadataModel{
TotalCount: 2,
Scans: []wrappers.Scans{
Expand All @@ -27,11 +43,27 @@ func (s SastMetadataMockWrapper) GetSastMetadataByIDs(params map[string]string)
FileCount: 70,
IsIncremental: false,
IsIncrementalCanceled: true,
IncrementalCancelReason: "Some reason",
IncrementalCancelReason: "some reason",
BaseID: "baseID",
DeletedFilesCount: 3,
},
},
Missing: []string{"missing1", "missing2"},
}, nil
}

func convertScanIDsToScans(scanIDs []string) []wrappers.Scans {
scans := make([]wrappers.Scans, 0, len(scanIDs))
for i, scanID := range scanIDs {
scans = append(scans, wrappers.Scans{
ScanID: scanID,
ProjectID: "project" + scanID,
Loc: 100 + i,
FileCount: 50 + i,
IsIncremental: i%2 == 0,
AddedFilesCount: 10 + i,
ChangedFilesCount: 5 + i,
})
}
return scans
}
1 change: 0 additions & 1 deletion internal/wrappers/sast-metadata-http.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ func NewSastIncrementalHTTPWrapper(path string) SastMetadataWrapper {

func (s *SastIncrementalHTTPWrapper) GetSastMetadataByIDs(params map[string]string) (*SastMetadataModel, error) {
clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey)

resp, err := SendPrivateHTTPRequestWithQueryParams(http.MethodGet, s.path, params, http.NoBody, clientTimeout)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit d78cef0

Please sign in to comment.