Skip to content

Commit

Permalink
feat: progress tracker [IDE-222] (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
teodora-sandu committed May 23, 2024
1 parent 2b7057d commit 0b7d5f7
Show file tree
Hide file tree
Showing 18 changed files with 470 additions and 95 deletions.
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ httpClient := codeClientHTTP.NewHTTPClient(

The HTTP client exposes a `Do` function.

### Target

Use the target to record the target of a scan, which can be either a folder enhanced with repository metadata
or a repository.

```go
import (
codeClientScan "github.com/snyk/code-client-go/scan"
)

target, _ := codeClientScan.NewRepositoryTarget(path)

target, _ := codeClientScan.NewRepositoryTarget(path, codeClientScan.WithRepositoryUrl("https://github.com/snyk/code-client-go.git"))
```
### Tracker Factory

Use the tracker factory to generate a tracker used to update the consumer of the client with frequent progress updates.

The tracker either exposes an interface with two `Begin` and `End` functions or an implementation that doesn't do anything.

```go
import (
codeClientScan "github.com/snyk/code-client-go/scan"
)

trackerFactory := codeClientScan.NewNoopTrackerFactory()

tracker := trackerFactory.GenerateTracker()
tracker.Begin()
...
tracker.End()
```

### Configuration

Implement the `config.Config` interface to configure the Snyk Code API client from applications.
Expand All @@ -57,15 +90,20 @@ Use the Code Scanner to trigger a scan for a Snyk Code workspace using the Bundl
The Code Scanner exposes a `UploadAndAnalyze` function, which can be used like this:

```go
import (
codeClient "github.com/snyk/code-client-go"
)

config := newConfigForMyApp()
codeScanner := code.NewCodeScanner(
codeScanner := codeClient.NewCodeScanner(
httpClient,
config,
codeClient.WithTrackerFactory(trackerFactory),
codeClientHTTP.WithLogger(logger),
codeClientHTTP.WithInstrumentor(instrumentor),
codeClientHTTP.WithErrorReporter(errorReporter),
)
code.UploadAndAnalyze(context.Background(), requestId, "path/to/workspace", channelForWalkingFiles, changedFiles)
codeScanner.UploadAndAnalyze(context.Background(), requestId, target, channelForWalkingFiles, changedFiles)
```


Expand Down
47 changes: 35 additions & 12 deletions internal/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ import (
//go:generate mockgen -destination=mocks/analysis.go -source=analysis.go -package mocks
type AnalysisOrchestrator interface {
CreateWorkspace(ctx context.Context, orgId string, requestId string, path scan.Target, bundleHash string) (string, error)
RunAnalysis(ctx context.Context, orgId string, workspaceId string) (*sarif.SarifResponse, error)
RunAnalysis(ctx context.Context, orgId string, rootPath string, workspaceId string) (*sarif.SarifResponse, error)
}

type analysisOrchestrator struct {
httpClient codeClientHTTP.HTTPClient
instrumentor observability.Instrumentor
errorReporter observability.ErrorReporter
logger *zerolog.Logger
trackerFactory scan.TrackerFactory
config config.Config
timeoutInSeconds time.Duration
}
Expand All @@ -73,13 +74,15 @@ func NewAnalysisOrchestrator(
httpClient codeClientHTTP.HTTPClient,
instrumentor observability.Instrumentor,
errorReporter observability.ErrorReporter,
trackerFactory scan.TrackerFactory,
options ...OptionFunc,
) AnalysisOrchestrator {
a := &analysisOrchestrator{
httpClient: httpClient,
instrumentor: instrumentor,
errorReporter: errorReporter,
logger: logger,
trackerFactory: trackerFactory,
config: config,
timeoutInSeconds: 120 * time.Second,
}
Expand All @@ -98,6 +101,10 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
span := a.instrumentor.StartSpan(ctx, method)
defer a.instrumentor.Finish(span)

tracker := a.trackerFactory.GenerateTracker()
tracker.Begin("Creating file bundle workspace", "")
defer tracker.End("")

orgUUID := uuid.MustParse(orgId)

if target == nil {
Expand Down Expand Up @@ -181,23 +188,44 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
return workspaceId, nil
}

func (a *analysisOrchestrator) RunAnalysis(ctx context.Context, orgId string, workspaceId string) (*sarif.SarifResponse, error) {
func (a *analysisOrchestrator) RunAnalysis(ctx context.Context, orgId string, rootPath string, workspaceId string) (*sarif.SarifResponse, error) {
method := "analysis.RunAnalysis"
logger := a.logger.With().Str("method", method).Logger()
logger.Debug().Msg("API: Creating the scan")

tracker := a.trackerFactory.GenerateTracker()
tracker.Begin("Snyk Code analysis for "+rootPath, "Retrieving results...")

org := uuid.MustParse(orgId)

host := a.host(false)
a.logger.Debug().Str("host", host).Str("workspaceId", workspaceId).Msg("starting scan")

client, err := orchestrationClient.NewClientWithResponses(host, orchestrationClient.WithHTTPClient(a.httpClient))

if err != nil {
tracker.End(fmt.Sprintf("Analysis failed: %v", err))
return nil, fmt.Errorf("failed to create orchestrationClient: %w", err)
}

scanJobId, err := a.triggerScan(ctx, client, org, workspaceId)
if err != nil {
tracker.End(fmt.Sprintf("Analysis failed: %v", err))
return nil, err
}

response, err := a.pollScanForFindings(ctx, client, org, *scanJobId)
if err != nil {
tracker.End(fmt.Sprintf("Analysis failed: %v", err))
return nil, err
}

tracker.End("Analysis complete.")
return response, nil
}

func (a *analysisOrchestrator) triggerScan(ctx context.Context, client *orchestrationClient.ClientWithResponses, org uuid.UUID, workspaceId string) (*openapi_types.UUID, error) {
flow := scans.Flow{}
err = flow.UnmarshalJSON([]byte(`{"name": "cli_test"}`))
err := flow.UnmarshalJSON([]byte(`{"name": "cli_test"}`))
if err != nil {
return nil, fmt.Errorf("failed to create scan request: %w", err)
}
Expand Down Expand Up @@ -242,7 +270,7 @@ func (a *analysisOrchestrator) RunAnalysis(ctx context.Context, orgId string, wo
switch createScanResponse.StatusCode() {
case 201:
scanJobId = createScanResponse.ApplicationvndApiJSON201.Data.Id
a.logger.Debug().Str("host", host).Str("workspaceId", workspaceId).Msg("starting scan")
a.logger.Debug().Str("workspaceId", workspaceId).Msg("starting scan")
case 400:
msg = createScanResponse.ApplicationvndApiJSON400.Errors[0].Detail
case 401:
Expand All @@ -260,12 +288,7 @@ func (a *analysisOrchestrator) RunAnalysis(ctx context.Context, orgId string, wo
return nil, errors.New(msg)
}

response, err := a.pollScanForFindings(ctx, client, org, scanJobId)
if err != nil {
return nil, err
}

return response, nil
return &scanJobId, nil
}

func (a *analysisOrchestrator) pollScanForFindings(ctx context.Context, client *orchestrationClient.ClientWithResponses, org uuid.UUID, scanJobId openapi_types.UUID) (*sarif.SarifResponse, error) {
Expand All @@ -279,7 +302,7 @@ func (a *analysisOrchestrator) pollScanForFindings(ctx context.Context, client *
for {
select {
case <-timeoutTimer.C:
msg := "timeout requesting the ScanJobResult"
msg := "Snyk Code analysis timed out"
logger.Error().Str("scanJobId", scanJobId.String()).Msg(msg)
return nil, errors.New(msg)
case <-pollingTicker.C:
Expand Down
Loading

0 comments on commit 0b7d5f7

Please sign in to comment.