Skip to content

Commit b513d7d

Browse files
chore: replace orchestration APIs by test API (#70)
Co-authored-by: Trombitas Sandor <[email protected]>
1 parent de071d2 commit b513d7d

32 files changed

+3930
-133
lines changed

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ GOARCH = $(shell go env GOARCH)
44

55
TOOLS_BIN := $(shell pwd)/.bin
66

7-
OVERRIDE_GOCI_LINT_V := v1.55.2
7+
OVERRIDE_GOCI_LINT_V := v1.60.1
88
SHELL:=env PATH=$(TOOLS_BIN)/go:$(TOOLS_BIN)/pact/bin:$(PATH) $(SHELL)
99

1010
## tools: Install required tooling.
@@ -88,7 +88,7 @@ generate-apis: $(TOOLS_BIN)/go/oapi-codegen download-apis
8888
@go generate -tags API,!MOCK ./...
8989

9090
.PHONY: download-apis
91-
download-apis: download-workspace-api download-orchestration-api
91+
download-apis: download-test-api
9292

9393
.PHONY: download-workspace-api
9494
download-workspace-api:
@@ -98,6 +98,10 @@ download-workspace-api:
9898
download-orchestration-api:
9999
./scripts/download-orchestration-api.py
100100

101+
.PHONY: download-test-api
102+
download-test-api:
103+
./scripts/download-test-api.py
104+
101105
.PHONY: help
102106
help:
103107
@echo "Main targets:"

go.mod

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
module github.com/snyk/code-client-go
22

3-
go 1.21
3+
go 1.21.0
4+
5+
toolchain go1.23.2
46

57
require (
6-
github.com/deepmap/oapi-codegen v1.16.3
78
github.com/go-git/go-git/v5 v5.13.1
89
github.com/golang/mock v1.6.0
910
github.com/google/uuid v1.6.0
10-
github.com/oapi-codegen/runtime v1.0.0
11+
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
12+
github.com/oapi-codegen/runtime v1.1.1
1113
github.com/pact-foundation/pact-go/v2 v2.0.5
1214
github.com/pkg/errors v0.9.1
1315
github.com/puzpuzpuz/xsync v1.5.2
@@ -24,17 +26,18 @@ require (
2426
github.com/cloudflare/circl v1.3.7 // indirect
2527
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
2628
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
29+
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
2730
github.com/emirpasic/gods v1.18.1 // indirect
28-
github.com/getkin/kin-openapi v0.118.0 // indirect
31+
github.com/getkin/kin-openapi v0.127.0 // indirect
2932
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
3033
github.com/go-git/go-billy/v5 v5.6.1 // indirect
31-
github.com/go-openapi/jsonpointer v0.20.0 // indirect
32-
github.com/go-openapi/swag v0.22.4 // indirect
34+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
35+
github.com/go-openapi/swag v0.23.0 // indirect
3336
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
3437
github.com/hashicorp/go-version v1.6.0 // indirect
3538
github.com/hashicorp/logutils v1.0.0 // indirect
3639
github.com/inconshreveable/mousetrap v1.1.0 // indirect
37-
github.com/invopop/yaml v0.2.0 // indirect
40+
github.com/invopop/yaml v0.3.1 // indirect
3841
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
3942
github.com/josharian/intern v1.0.0 // indirect
4043
github.com/kevinburke/ssh_config v1.2.0 // indirect
@@ -47,10 +50,12 @@ require (
4750
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
4851
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
4952
github.com/skeema/knownhosts v1.3.0 // indirect
53+
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
5054
github.com/spf13/afero v1.11.0 // indirect
5155
github.com/spf13/cobra v1.8.0 // indirect
5256
github.com/spf13/pflag v1.0.5 // indirect
5357
github.com/stretchr/objx v0.5.2 // indirect
58+
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
5459
github.com/xanzy/ssh-agent v0.3.3 // indirect
5560
golang.org/x/crypto v0.31.0 // indirect
5661
golang.org/x/mod v0.17.0 // indirect

go.sum

Lines changed: 96 additions & 31 deletions
Large diffs are not rendered by default.

http/http.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ func (s *httpClient) Do(req *http.Request) (*http.Response, error) {
126126
}
127127

128128
func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) {
129-
requestId := req.Header.Get("snyk-request-id")
130-
log := s.logger.With().
131-
Str("method", "http.httpCall").
132-
Str("reqMethod", req.Method).
133-
Str("url", req.URL.String()).
134-
Str("snyk-request-id", requestId).
135-
Logger()
136-
137129
// store the request body so that after retrying it can be read again
138130
var copyReqBody io.ReadCloser
139131
var reqBuf []byte
@@ -142,7 +134,6 @@ func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) {
142134
reqBody := io.NopCloser(bytes.NewBuffer(reqBuf))
143135
copyReqBody = io.NopCloser(bytes.NewBuffer(reqBuf))
144136
req.Body = reqBody
145-
log.Debug().Msg("SEND TO REMOTE")
146137
}
147138

148139
response, err := s.httpClientFactory().Do(req)
@@ -153,13 +144,9 @@ func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) {
153144
resBuf, _ = io.ReadAll(response.Body)
154145
copyResBody = io.NopCloser(bytes.NewBuffer(resBuf))
155146
response.Body = copyResBody
156-
log.Debug().Str("response.Status", response.Status).Msg("RECEIVED FROM REMOTE")
157-
} else {
158-
log.Debug().Msg("RECEIVED FROM REMOTE")
159147
}
160148

161149
if err != nil {
162-
log.Error().Err(err).Msg("got http error")
163150
s.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: req.RequestURI})
164151
return nil, err
165152
}

internal/analysis/analysis.go

Lines changed: 144 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import (
3434

3535
"github.com/snyk/code-client-go/config"
3636
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"
3740
orchestrationClient "github.com/snyk/code-client-go/internal/orchestration/2024-02-16"
3841
scans "github.com/snyk/code-client-go/internal/orchestration/2024-02-16/scans"
3942
workspaceClient "github.com/snyk/code-client-go/internal/workspace/2024-05-14"
@@ -48,6 +51,8 @@ type AnalysisOrchestrator interface {
4851
CreateWorkspace(ctx context.Context, orgId string, requestId string, path scan.Target, bundleHash string) (string, error)
4952
RunAnalysis(ctx context.Context, orgId string, rootPath string, workspaceId string) (*sarif.SarifResponse, error)
5053
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)
5156
}
5257

5358
type analysisOrchestrator struct {
@@ -58,6 +63,7 @@ type analysisOrchestrator struct {
5863
trackerFactory scan.TrackerFactory
5964
config config.Config
6065
flow scans.Flow
66+
testType testModels.Scan
6167
}
6268

6369
type OptionFunc func(*analysisOrchestrator)
@@ -86,10 +92,9 @@ func WithTrackerFactory(factory scan.TrackerFactory) func(*analysisOrchestrator)
8692
}
8793
}
8894

89-
func WithFlow(flow string) func(*analysisOrchestrator) {
95+
func WithResultType(t testModels.Scan) func(*analysisOrchestrator) {
9096
return func(a *analysisOrchestrator) {
91-
a.flow = scans.Flow{}
92-
_ = a.flow.UnmarshalJSON([]byte(fmt.Sprintf(`{"name": "%s"}`, flow)))
97+
a.testType = t
9398
}
9499
}
95100

@@ -109,7 +114,7 @@ func NewAnalysisOrchestrator(
109114
trackerFactory: scan.NewNoopTrackerFactory(),
110115
errorReporter: observability.NewErrorReporter(&nopLogger),
111116
logger: &nopLogger,
112-
flow: flow,
117+
testType: testModels.CodeSecurityCodeQuality,
113118
}
114119

115120
for _, option := range options {
@@ -428,11 +433,11 @@ func (a *analysisOrchestrator) retrieveFindings(ctx context.Context, scanJobId u
428433
return nil, errors.New("do not have a findings URL")
429434
}
430435
req, err := http.NewRequest(http.MethodGet, findingsUrl, nil)
431-
req = req.WithContext(ctx)
432-
433436
if err != nil {
434437
return nil, err
435438
}
439+
req = req.WithContext(ctx)
440+
436441
rsp, err := a.httpClient.Do(req)
437442
if err != nil {
438443
return nil, err
@@ -471,3 +476,136 @@ func (a *analysisOrchestrator) host(isHidden bool) string {
471476
}
472477
return fmt.Sprintf("%s/%s", apiUrl, path)
473478
}
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

Comments
 (0)