@@ -23,33 +23,44 @@ import (
2323 "encoding/json"
2424 "fmt"
2525 "github.com/google/uuid"
26+ openapi_types "github.com/oapi-codegen/runtime/types"
2627 "github.com/pkg/errors"
2728 "github.com/rs/zerolog"
2829 "github.com/snyk/code-client-go/config"
2930 codeClientHTTP "github.com/snyk/code-client-go/http"
31+ orchestrationClient "github.com/snyk/code-client-go/internal/orchestration/2024-02-16"
32+ scans "github.com/snyk/code-client-go/internal/orchestration/2024-02-16/scans"
3033 "github.com/snyk/code-client-go/internal/util"
3134 workspaceClient "github.com/snyk/code-client-go/internal/workspace/2024-03-12"
32- externalRef3 "github.com/snyk/code-client-go/internal/workspace/2024-03-12/workspaces"
35+ workspaces "github.com/snyk/code-client-go/internal/workspace/2024-03-12/workspaces"
3336 "github.com/snyk/code-client-go/observability"
3437 "github.com/snyk/code-client-go/sarif"
38+
3539 "strings"
40+ "time"
3641)
3742
38- //go:embed fake.json
39- var fakeResponse []byte
43+ //go:generate mockgen -destination=mocks/analysis.go -source=analysis.go -package mocks
44+ type AnalysisOrchestrator interface {
45+ CreateWorkspace (ctx context.Context , orgId string , requestId string , path string , bundleHash string ) (string , error )
46+ RunAnalysis (ctx context.Context , orgId string , workspaceId string ) (* sarif.SarifResponse , error )
47+ }
4048
4149type analysisOrchestrator struct {
42- httpClient codeClientHTTP.HTTPClient
43- instrumentor observability.Instrumentor
44- errorReporter observability.ErrorReporter
45- logger * zerolog.Logger
46- config config.Config
50+ httpClient codeClientHTTP.HTTPClient
51+ instrumentor observability.Instrumentor
52+ errorReporter observability.ErrorReporter
53+ logger * zerolog.Logger
54+ config config.Config
55+ timeoutInSeconds time.Duration
4756}
4857
49- //go:generate mockgen -destination=mocks/analysis.go -source=analysis.go -package mocks
50- type AnalysisOrchestrator interface {
51- CreateWorkspace (ctx context.Context , orgId string , requestId string , path string , bundleHash string ) (string , error )
52- RunAnalysis () (* sarif.SarifResponse , error )
58+ type OptionFunc func (* analysisOrchestrator )
59+
60+ func WithTimeoutInSeconds (timeoutInSeconds time.Duration ) func (* analysisOrchestrator ) {
61+ return func (a * analysisOrchestrator ) {
62+ a .timeoutInSeconds = timeoutInSeconds
63+ }
5364}
5465
5566func NewAnalysisOrchestrator (
@@ -58,20 +69,27 @@ func NewAnalysisOrchestrator(
5869 httpClient codeClientHTTP.HTTPClient ,
5970 instrumentor observability.Instrumentor ,
6071 errorReporter observability.ErrorReporter ,
61- ) * analysisOrchestrator {
62- return & analysisOrchestrator {
63- httpClient ,
64- instrumentor ,
65- errorReporter ,
66- logger ,
67- config ,
72+ options ... OptionFunc ,
73+ ) AnalysisOrchestrator {
74+ a := & analysisOrchestrator {
75+ httpClient : httpClient ,
76+ instrumentor : instrumentor ,
77+ errorReporter : errorReporter ,
78+ logger : logger ,
79+ config : config ,
80+ timeoutInSeconds : 120 * time .Second ,
81+ }
82+ for _ , option := range options {
83+ option (a )
6884 }
85+
86+ return a
6987}
7088
7189func (a * analysisOrchestrator ) CreateWorkspace (ctx context.Context , orgId string , requestId string , path string , bundleHash string ) (string , error ) {
7290 method := "analysis.CreateWorkspace"
73- log := a .logger .With ().Str ("method" , method ).Logger ()
74- log .Debug ().Msg ("API: Creating the workspace" )
91+ logger := a .logger .With ().Str ("method" , method ).Logger ()
92+ logger .Debug ().Msg ("API: Creating the workspace" )
7593
7694 span := a .instrumentor .StartSpan (ctx , method )
7795 defer a .instrumentor .Finish (span )
@@ -84,7 +102,7 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
84102 return "" , fmt .Errorf ("workspace is not a repository, cannot scan, %w" , err )
85103 }
86104
87- host := a .host ()
105+ host := a .host (true )
88106 a .logger .Info ().Str ("host" , host ).Str ("path" , path ).Str ("repositoryUri" , repositoryUri ).Msg ("creating workspace" )
89107
90108 workspace , err := workspaceClient .NewClientWithResponses (host , workspaceClient .WithHTTPClient (a .httpClient ))
@@ -101,26 +119,26 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
101119 }, workspaceClient.CreateWorkspaceApplicationVndAPIPlusJSONRequestBody {
102120 Data : struct {
103121 Attributes struct {
104- BundleId string `json:"bundle_id"`
105- RepositoryUri string `json:"repository_uri"`
106- WorkspaceType externalRef3 .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
122+ BundleId string `json:"bundle_id"`
123+ RepositoryUri string `json:"repository_uri"`
124+ WorkspaceType workspaces .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
107125 } `json:"attributes"`
108- Type externalRef3 .WorkspacePostRequestDataType `json:"type"`
126+ Type workspaces .WorkspacePostRequestDataType `json:"type"`
109127 }(struct {
110128 Attributes struct {
111- BundleId string `json:"bundle_id"`
112- RepositoryUri string `json:"repository_uri"`
113- WorkspaceType externalRef3 .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
129+ BundleId string `json:"bundle_id"`
130+ RepositoryUri string `json:"repository_uri"`
131+ WorkspaceType workspaces .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
114132 }
115- Type externalRef3 .WorkspacePostRequestDataType
133+ Type workspaces .WorkspacePostRequestDataType
116134 }{Attributes : struct {
117- BundleId string `json:"bundle_id"`
118- RepositoryUri string `json:"repository_uri"`
119- WorkspaceType externalRef3 .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
135+ BundleId string `json:"bundle_id"`
136+ RepositoryUri string `json:"repository_uri"`
137+ WorkspaceType workspaces .WorkspacePostRequestDataAttributesWorkspaceType `json:"workspace_type"`
120138 }(struct {
121139 BundleId string
122140 RepositoryUri string
123- WorkspaceType externalRef3 .WorkspacePostRequestDataAttributesWorkspaceType
141+ WorkspaceType workspaces .WorkspacePostRequestDataAttributesWorkspaceType
124142 }{
125143 BundleId : bundleHash ,
126144 RepositoryUri : repositoryUri ,
@@ -152,17 +170,163 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
152170 return workspaceResponse .ApplicationvndApiJSON201 .Data .Id .String (), nil
153171}
154172
155- func (* analysisOrchestrator ) RunAnalysis () (* sarif.SarifResponse , error ) {
156- var response sarif.SarifResponse
173+ //go:embed fake.json
174+ var fakeResponse []byte
175+
176+ func (a * analysisOrchestrator ) RunAnalysis (ctx context.Context , orgId string , workspaceId string ) (* sarif.SarifResponse , error ) {
177+ method := "analysis.RunAnalysis"
178+ logger := a .logger .With ().Str ("method" , method ).Logger ()
179+ logger .Debug ().Msg ("API: Creating the scan" )
180+ org := uuid .MustParse (orgId )
181+
182+ host := a .host (false )
183+ a .logger .Debug ().Str ("host" , host ).Str ("workspaceId" , workspaceId ).Msg ("starting scan" )
184+
185+ client , err := orchestrationClient .NewClientWithResponses (host , orchestrationClient .WithHTTPClient (a .httpClient ))
157186
158- err := json .Unmarshal (fakeResponse , & response )
159187 if err != nil {
160- return nil , fmt .Errorf ("failed to create SARIF response: %w" , err )
188+ return nil , fmt .Errorf ("failed to create orchestrationClient: %w" , err )
189+ }
190+
191+ flow := scans.Flow {}
192+ err = flow .UnmarshalJSON ([]byte (`{"name": "cli_test"}` ))
193+ if err != nil {
194+ return nil , fmt .Errorf ("failed to create scan request: %w" , err )
195+ }
196+ createScanResponse , err := client .CreateScanWorkspaceJobForUserWithApplicationVndAPIPlusJSONBodyWithResponse (
197+ ctx ,
198+ org ,
199+ & orchestrationClient.CreateScanWorkspaceJobForUserParams {Version : "2024-02-16~experimental" },
200+ orchestrationClient.CreateScanWorkspaceJobForUserApplicationVndAPIPlusJSONRequestBody {Data : struct {
201+ Attributes struct {
202+ Flow scans.Flow `json:"flow"`
203+ WorkspaceUrl string `json:"workspace_url"`
204+ } `json:"attributes"`
205+ Id * openapi_types.UUID `json:"id,omitempty"`
206+ Type scans.PostScanRequestDataType `json:"type"`
207+ }(struct {
208+ Attributes struct {
209+ Flow scans.Flow `json:"flow"`
210+ WorkspaceUrl string `json:"workspace_url"`
211+ }
212+ Id * openapi_types.UUID
213+ Type scans.PostScanRequestDataType
214+ }{
215+ Attributes : struct {
216+ Flow scans.Flow `json:"flow"`
217+ WorkspaceUrl string `json:"workspace_url"`
218+ }(struct {
219+ Flow scans.Flow
220+ WorkspaceUrl string
221+ }{
222+ Flow : flow ,
223+ WorkspaceUrl : fmt .Sprintf ("http://workspace-service/workspaces/%s" , workspaceId ),
224+ }),
225+ Type : "workspace" ,
226+ })})
227+
228+ if err != nil {
229+ return nil , fmt .Errorf ("failed to trigger scan: %w" , err )
230+ }
231+
232+ if createScanResponse .ApplicationvndApiJSON201 == nil {
233+ msg := a .getStatusCode (createScanResponse )
234+ return nil , errors .New (msg )
235+ }
236+
237+ scanJobId := createScanResponse .ApplicationvndApiJSON201 .Data .Id
238+ a .logger .Debug ().Str ("host" , host ).Str ("workspaceId" , workspaceId ).Msg ("starting scan" )
239+
240+ // Actual polling loop.
241+ pollingTicker := time .NewTicker (1 * time .Second )
242+ defer pollingTicker .Stop ()
243+ timeoutTimer := time .NewTimer (a .timeoutInSeconds )
244+ defer timeoutTimer .Stop ()
245+ for {
246+ select {
247+ case <- timeoutTimer .C :
248+ msg := "timeout requesting the ScanJobResult"
249+ logger .Error ().Str ("scanJobId" , scanJobId .String ()).Msg (msg )
250+ return nil , errors .New (msg )
251+ case <- pollingTicker .C :
252+ _ , complete , err := a .poller (ctx , logger , client , org , scanJobId , method ) // todo add processing of the response with the findings
253+ if err != nil {
254+ return nil , err
255+ }
256+ if ! complete {
257+ continue
258+ }
259+
260+ var response sarif.SarifResponse
261+ _ = json .Unmarshal (fakeResponse , & response )
262+
263+ return & response , nil
264+ }
161265 }
162- return & response , nil
163266}
164267
165- func (a * analysisOrchestrator ) host () string {
268+ func (a * analysisOrchestrator ) poller (ctx context.Context , logger zerolog.Logger , client * orchestrationClient.ClientWithResponses , org uuid.UUID , scanJobId openapi_types.UUID , method string ) (response * orchestrationClient.GetScanWorkspaceJobForUserResponse , complete bool , err error ) {
269+ logger .Debug ().Msg ("polling for ScanJobResult" )
270+ httpResponse , err := client .GetScanWorkspaceJobForUserWithResponse (
271+ ctx ,
272+ org ,
273+ scanJobId ,
274+ & orchestrationClient.GetScanWorkspaceJobForUserParams {Version : "2024-02-16~experimental" },
275+ )
276+ if err != nil {
277+ logger .Err (err ).Str ("method" , method ).Str ("scanJobId" , scanJobId .String ()).Msg ("error requesting the ScanJobResult" )
278+ return httpResponse , true , err
279+ }
280+
281+ var msg string
282+ switch httpResponse .StatusCode () {
283+ case 200 :
284+ scanJobStatus := httpResponse .ApplicationvndApiJSON200 .Data .Attributes .Status
285+ if scanJobStatus == scans .ScanJobResultsAttributesStatusInProgress {
286+ return httpResponse , false , nil
287+ } else {
288+ return httpResponse , true , nil
289+ }
290+ case 400 :
291+ msg = httpResponse .ApplicationvndApiJSON400 .Errors [0 ].Detail
292+ case 401 :
293+ msg = httpResponse .ApplicationvndApiJSON401 .Errors [0 ].Detail
294+ case 403 :
295+ msg = httpResponse .ApplicationvndApiJSON403 .Errors [0 ].Detail
296+ case 404 :
297+ msg = httpResponse .ApplicationvndApiJSON404 .Errors [0 ].Detail
298+ case 429 :
299+ msg = httpResponse .ApplicationvndApiJSON429 .Errors [0 ].Detail
300+ case 500 :
301+ msg = httpResponse .ApplicationvndApiJSON500 .Errors [0 ].Detail
302+ }
303+ return nil , true , errors .New (msg )
304+ }
305+
306+ func (a * analysisOrchestrator ) getStatusCode (createScanResponse * orchestrationClient.CreateScanWorkspaceJobForUserResponse ) string {
307+ var msg string
308+ switch createScanResponse .StatusCode () {
309+ case 400 :
310+ msg = createScanResponse .ApplicationvndApiJSON400 .Errors [0 ].Detail
311+ case 401 :
312+ msg = createScanResponse .ApplicationvndApiJSON401 .Errors [0 ].Detail
313+ case 403 :
314+ msg = createScanResponse .ApplicationvndApiJSON403 .Errors [0 ].Detail
315+ case 404 :
316+ msg = createScanResponse .ApplicationvndApiJSON404 .Errors [0 ].Detail
317+ case 429 :
318+ msg = createScanResponse .ApplicationvndApiJSON429 .Errors [0 ].Detail
319+ case 500 :
320+ msg = createScanResponse .ApplicationvndApiJSON500 .Errors [0 ].Detail
321+ }
322+ return msg
323+ }
324+
325+ func (a * analysisOrchestrator ) host (isHidden bool ) string {
166326 apiUrl := strings .TrimRight (a .config .SnykApi (), "/" )
167- return fmt .Sprintf ("%s/hidden" , apiUrl )
327+ path := "rest"
328+ if isHidden {
329+ path = "hidden"
330+ }
331+ return fmt .Sprintf ("%s/%s" , apiUrl , path )
168332}
0 commit comments