@@ -34,6 +34,9 @@ import (
34
34
35
35
"github.com/snyk/code-client-go/config"
36
36
codeClientHTTP "github.com/snyk/code-client-go/http"
37
+ testApi "github.com/snyk/code-client-go/internal/api/test/2024-12-21"
38
+ testModels "github.com/snyk/code-client-go/internal/api/test/2024-12-21/models"
39
+ "github.com/snyk/code-client-go/internal/bundle"
37
40
orchestrationClient "github.com/snyk/code-client-go/internal/orchestration/2024-02-16"
38
41
scans "github.com/snyk/code-client-go/internal/orchestration/2024-02-16/scans"
39
42
workspaceClient "github.com/snyk/code-client-go/internal/workspace/2024-05-14"
@@ -48,6 +51,8 @@ type AnalysisOrchestrator interface {
48
51
CreateWorkspace (ctx context.Context , orgId string , requestId string , path scan.Target , bundleHash string ) (string , error )
49
52
RunAnalysis (ctx context.Context , orgId string , rootPath string , workspaceId string ) (* sarif.SarifResponse , error )
50
53
RunIncrementalAnalysis (ctx context.Context , orgId string , rootPath string , workspaceId string , limitToFiles []string ) (* sarif.SarifResponse , error )
54
+
55
+ RunTest (ctx context.Context , orgId string , b bundle.Bundle , target scan.Target ) (* sarif.SarifResponse , error )
51
56
}
52
57
53
58
type analysisOrchestrator struct {
@@ -58,6 +63,7 @@ type analysisOrchestrator struct {
58
63
trackerFactory scan.TrackerFactory
59
64
config config.Config
60
65
flow scans.Flow
66
+ testType testModels.Scan
61
67
}
62
68
63
69
type OptionFunc func (* analysisOrchestrator )
@@ -86,10 +92,9 @@ func WithTrackerFactory(factory scan.TrackerFactory) func(*analysisOrchestrator)
86
92
}
87
93
}
88
94
89
- func WithFlow ( flow string ) func (* analysisOrchestrator ) {
95
+ func WithResultType ( t testModels. Scan ) func (* analysisOrchestrator ) {
90
96
return func (a * analysisOrchestrator ) {
91
- a .flow = scans.Flow {}
92
- _ = a .flow .UnmarshalJSON ([]byte (fmt .Sprintf (`{"name": "%s"}` , flow )))
97
+ a .testType = t
93
98
}
94
99
}
95
100
@@ -109,7 +114,7 @@ func NewAnalysisOrchestrator(
109
114
trackerFactory : scan .NewNoopTrackerFactory (),
110
115
errorReporter : observability .NewErrorReporter (& nopLogger ),
111
116
logger : & nopLogger ,
112
- flow : flow ,
117
+ testType : testModels . CodeSecurityCodeQuality ,
113
118
}
114
119
115
120
for _ , option := range options {
@@ -428,11 +433,11 @@ func (a *analysisOrchestrator) retrieveFindings(ctx context.Context, scanJobId u
428
433
return nil , errors .New ("do not have a findings URL" )
429
434
}
430
435
req , err := http .NewRequest (http .MethodGet , findingsUrl , nil )
431
- req = req .WithContext (ctx )
432
-
433
436
if err != nil {
434
437
return nil , err
435
438
}
439
+ req = req .WithContext (ctx )
440
+
436
441
rsp , err := a .httpClient .Do (req )
437
442
if err != nil {
438
443
return nil , err
@@ -471,3 +476,136 @@ func (a *analysisOrchestrator) host(isHidden bool) string {
471
476
}
472
477
return fmt .Sprintf ("%s/%s" , apiUrl , path )
473
478
}
479
+
480
+ func (a * analysisOrchestrator ) RunTest (ctx context.Context , orgId string , b bundle.Bundle , target scan.Target ) (* sarif.SarifResponse , error ) {
481
+ tracker := a .trackerFactory .GenerateTracker ()
482
+ tracker .Begin ("Snyk Code analysis for " + target .GetPath (), "Retrieving results..." )
483
+
484
+ orgUuid := uuid .MustParse (orgId )
485
+ host := a .host (true )
486
+ var repoUrl * string = nil
487
+ if repoTarget , ok := target .(* scan.RepositoryTarget ); ok {
488
+ tmp := repoTarget .GetRepositoryUrl ()
489
+ repoUrl = & tmp
490
+ }
491
+
492
+ client , err := testApi .NewClient (host , testApi .WithHTTPClient (a .httpClient ))
493
+ if err != nil {
494
+ return nil , err
495
+ }
496
+
497
+ params := testApi.CreateTestParams {Version : testApi .ApiVersion }
498
+ body := testApi .NewCreateTestApplicationBody (
499
+ testApi .WithInputBundle (b .GetBundleHash (), target .GetPath (), repoUrl , b .GetLimitToFiles ()),
500
+ testApi .WithScanType (a .testType ),
501
+ )
502
+
503
+ // create test
504
+ resp , err := client .CreateTestWithApplicationVndAPIPlusJSONBody (ctx , orgUuid , & params , * body )
505
+ if err != nil {
506
+ return nil , err
507
+ }
508
+
509
+ parsedResponse , err := testApi .ParseCreateTestResponse (resp )
510
+ defer func () {
511
+ closeErr := resp .Body .Close ()
512
+ a .logger .Err (closeErr ).Msg ("failed to close response body" )
513
+ }()
514
+ if err != nil {
515
+ a .logger .Debug ().Msg (err .Error ())
516
+ return nil , err
517
+ }
518
+
519
+ switch parsedResponse .StatusCode () {
520
+ case http .StatusCreated :
521
+ // poll results
522
+ result , pollErr := a .pollTestForFindings (ctx , client , orgUuid , parsedResponse .ApplicationvndApiJSON201 .Data .Id )
523
+ tracker .End ("Analysis complete." )
524
+ return result , pollErr
525
+ default :
526
+ return nil , fmt .Errorf ("failed to create test: %s" , parsedResponse .Status ())
527
+ }
528
+ }
529
+
530
+ func (a * analysisOrchestrator ) pollTestForFindings (ctx context.Context , client * testApi.Client , org uuid.UUID , testId openapi_types.UUID ) (* sarif.SarifResponse , error ) {
531
+ method := "analysis.pollTestForFindings"
532
+ logger := a .logger .With ().Str ("method" , method ).Logger ()
533
+
534
+ pollingTicker := time .NewTicker (1 * time .Second )
535
+ defer pollingTicker .Stop ()
536
+ timeoutTimer := time .NewTimer (a .config .SnykCodeAnalysisTimeout ())
537
+ defer timeoutTimer .Stop ()
538
+ for {
539
+ select {
540
+ case <- timeoutTimer .C :
541
+ msg := "Snyk Code analysis timed out"
542
+ logger .Error ().Str ("scanJobId" , testId .String ()).Msg (msg )
543
+ return nil , errors .New (msg )
544
+ case <- pollingTicker .C :
545
+ findingsUrl , complete , err := a .retrieveTestURL (ctx , client , org , testId )
546
+ if err != nil {
547
+ return nil , err
548
+ }
549
+ if complete {
550
+ findings , findingsErr := a .retrieveFindings (ctx , testId , findingsUrl )
551
+ if findingsErr != nil {
552
+ return nil , findingsErr
553
+ }
554
+ return findings , nil
555
+ }
556
+ }
557
+ }
558
+ }
559
+
560
+ func (a * analysisOrchestrator ) retrieveTestURL (ctx context.Context , client * testApi.Client , org uuid.UUID , testId openapi_types.UUID ) (url string , completed bool , err error ) {
561
+ method := "analysis.retrieveTestURL"
562
+ logger := a .logger .With ().Str ("method" , method ).Logger ()
563
+ logger .Debug ().Msg ("retrieving Test URL" )
564
+
565
+ httpResponse , err := client .GetTestResult (
566
+ ctx ,
567
+ org ,
568
+ testId ,
569
+ & testApi.GetTestResultParams {Version : testApi .ApiVersion },
570
+ )
571
+ if err != nil {
572
+ logger .Err (err ).Str ("testId" , testId .String ()).Msg ("error requesting the ScanJobResult" )
573
+ return "" , false , err
574
+ }
575
+ defer func () {
576
+ closeErr := httpResponse .Body .Close ()
577
+ a .logger .Err (closeErr ).Msg ("failed to close response body" )
578
+ }()
579
+
580
+ parsedResponse , err := testApi .ParseGetTestResultResponse (httpResponse )
581
+ if err != nil {
582
+ return "" , false , err
583
+ }
584
+
585
+ switch parsedResponse .StatusCode () {
586
+ case 200 :
587
+ stateDiscriminator , stateError := parsedResponse .ApplicationvndApiJSON200 .Data .Attributes .Discriminator ()
588
+ if stateError != nil {
589
+ return "" , false , stateError
590
+ }
591
+
592
+ switch stateDiscriminator {
593
+ case string (testModels .TestAcceptedStateStatusAccepted ):
594
+ fallthrough
595
+ case string (testModels .TestInProgressStateStatusInProgress ):
596
+ return "" , false , nil
597
+ case string (testModels .TestCompletedStateStatusCompleted ):
598
+ testCompleted , stateCompleteError := parsedResponse .ApplicationvndApiJSON200 .Data .Attributes .AsTestCompletedState ()
599
+ if stateCompleteError != nil {
600
+ return "" , false , stateCompleteError
601
+ }
602
+
603
+ findingsUrl := a .host (true ) + testCompleted .Documents .EnrichedSarif + "?version=" + testApi .DocumentApiVersion
604
+ return findingsUrl , true , nil
605
+ default :
606
+ return "" , false , fmt .Errorf ("unexpected test status \" %s\" " , stateDiscriminator )
607
+ }
608
+ default :
609
+ return "" , false , fmt .Errorf ("unexpected response status \" %d\" " , parsedResponse .StatusCode ())
610
+ }
611
+ }
0 commit comments