Skip to content

Commit

Permalink
chore: replace orchestration APIs by test API (#70)
Browse files Browse the repository at this point in the history
Co-authored-by: Trombitas Sandor <[email protected]>
  • Loading branch information
PeterSchafer and sandor-trombitas authored Jan 23, 2025
1 parent de071d2 commit b513d7d
Show file tree
Hide file tree
Showing 32 changed files with 3,930 additions and 133 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ GOARCH = $(shell go env GOARCH)

TOOLS_BIN := $(shell pwd)/.bin

OVERRIDE_GOCI_LINT_V := v1.55.2
OVERRIDE_GOCI_LINT_V := v1.60.1
SHELL:=env PATH=$(TOOLS_BIN)/go:$(TOOLS_BIN)/pact/bin:$(PATH) $(SHELL)

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

.PHONY: download-apis
download-apis: download-workspace-api download-orchestration-api
download-apis: download-test-api

.PHONY: download-workspace-api
download-workspace-api:
Expand All @@ -98,6 +98,10 @@ download-workspace-api:
download-orchestration-api:
./scripts/download-orchestration-api.py

.PHONY: download-test-api
download-test-api:
./scripts/download-test-api.py

.PHONY: help
help:
@echo "Main targets:"
Expand Down
19 changes: 12 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module github.com/snyk/code-client-go

go 1.21
go 1.21.0

toolchain go1.23.2

require (
github.com/deepmap/oapi-codegen v1.16.3
github.com/go-git/go-git/v5 v5.13.1
github.com/golang/mock v1.6.0
github.com/google/uuid v1.6.0
github.com/oapi-codegen/runtime v1.0.0
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
github.com/oapi-codegen/runtime v1.1.1
github.com/pact-foundation/pact-go/v2 v2.0.5
github.com/pkg/errors v0.9.1
github.com/puzpuzpuz/xsync v1.5.2
Expand All @@ -24,17 +26,18 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/getkin/kin-openapi v0.118.0 // indirect
github.com/getkin/kin-openapi v0.127.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.1 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
Expand All @@ -47,10 +50,12 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/mod v0.17.0 // indirect
Expand Down
127 changes: 96 additions & 31 deletions go.sum

Large diffs are not rendered by default.

13 changes: 0 additions & 13 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,6 @@ func (s *httpClient) Do(req *http.Request) (*http.Response, error) {
}

func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) {
requestId := req.Header.Get("snyk-request-id")
log := s.logger.With().
Str("method", "http.httpCall").
Str("reqMethod", req.Method).
Str("url", req.URL.String()).
Str("snyk-request-id", requestId).
Logger()

// store the request body so that after retrying it can be read again
var copyReqBody io.ReadCloser
var reqBuf []byte
Expand All @@ -142,7 +134,6 @@ func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) {
reqBody := io.NopCloser(bytes.NewBuffer(reqBuf))
copyReqBody = io.NopCloser(bytes.NewBuffer(reqBuf))
req.Body = reqBody
log.Debug().Msg("SEND TO REMOTE")
}

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

if err != nil {
log.Error().Err(err).Msg("got http error")
s.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: req.RequestURI})
return nil, err
}
Expand Down
150 changes: 144 additions & 6 deletions internal/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import (

"github.com/snyk/code-client-go/config"
codeClientHTTP "github.com/snyk/code-client-go/http"
testApi "github.com/snyk/code-client-go/internal/api/test/2024-12-21"
testModels "github.com/snyk/code-client-go/internal/api/test/2024-12-21/models"
"github.com/snyk/code-client-go/internal/bundle"
orchestrationClient "github.com/snyk/code-client-go/internal/orchestration/2024-02-16"
scans "github.com/snyk/code-client-go/internal/orchestration/2024-02-16/scans"
workspaceClient "github.com/snyk/code-client-go/internal/workspace/2024-05-14"
Expand All @@ -48,6 +51,8 @@ type AnalysisOrchestrator interface {
CreateWorkspace(ctx context.Context, orgId string, requestId string, path scan.Target, bundleHash string) (string, error)
RunAnalysis(ctx context.Context, orgId string, rootPath string, workspaceId string) (*sarif.SarifResponse, error)
RunIncrementalAnalysis(ctx context.Context, orgId string, rootPath string, workspaceId string, limitToFiles []string) (*sarif.SarifResponse, error)

RunTest(ctx context.Context, orgId string, b bundle.Bundle, target scan.Target) (*sarif.SarifResponse, error)
}

type analysisOrchestrator struct {
Expand All @@ -58,6 +63,7 @@ type analysisOrchestrator struct {
trackerFactory scan.TrackerFactory
config config.Config
flow scans.Flow
testType testModels.Scan
}

type OptionFunc func(*analysisOrchestrator)
Expand Down Expand Up @@ -86,10 +92,9 @@ func WithTrackerFactory(factory scan.TrackerFactory) func(*analysisOrchestrator)
}
}

func WithFlow(flow string) func(*analysisOrchestrator) {
func WithResultType(t testModels.Scan) func(*analysisOrchestrator) {
return func(a *analysisOrchestrator) {
a.flow = scans.Flow{}
_ = a.flow.UnmarshalJSON([]byte(fmt.Sprintf(`{"name": "%s"}`, flow)))
a.testType = t
}
}

Expand All @@ -109,7 +114,7 @@ func NewAnalysisOrchestrator(
trackerFactory: scan.NewNoopTrackerFactory(),
errorReporter: observability.NewErrorReporter(&nopLogger),
logger: &nopLogger,
flow: flow,
testType: testModels.CodeSecurityCodeQuality,
}

for _, option := range options {
Expand Down Expand Up @@ -428,11 +433,11 @@ func (a *analysisOrchestrator) retrieveFindings(ctx context.Context, scanJobId u
return nil, errors.New("do not have a findings URL")
}
req, err := http.NewRequest(http.MethodGet, findingsUrl, nil)
req = req.WithContext(ctx)

if err != nil {
return nil, err
}
req = req.WithContext(ctx)

rsp, err := a.httpClient.Do(req)
if err != nil {
return nil, err
Expand Down Expand Up @@ -471,3 +476,136 @@ func (a *analysisOrchestrator) host(isHidden bool) string {
}
return fmt.Sprintf("%s/%s", apiUrl, path)
}

func (a *analysisOrchestrator) RunTest(ctx context.Context, orgId string, b bundle.Bundle, target scan.Target) (*sarif.SarifResponse, error) {
tracker := a.trackerFactory.GenerateTracker()
tracker.Begin("Snyk Code analysis for "+target.GetPath(), "Retrieving results...")

orgUuid := uuid.MustParse(orgId)
host := a.host(true)
var repoUrl *string = nil
if repoTarget, ok := target.(*scan.RepositoryTarget); ok {
tmp := repoTarget.GetRepositoryUrl()
repoUrl = &tmp
}

client, err := testApi.NewClient(host, testApi.WithHTTPClient(a.httpClient))
if err != nil {
return nil, err
}

params := testApi.CreateTestParams{Version: testApi.ApiVersion}
body := testApi.NewCreateTestApplicationBody(
testApi.WithInputBundle(b.GetBundleHash(), target.GetPath(), repoUrl, b.GetLimitToFiles()),
testApi.WithScanType(a.testType),
)

// create test
resp, err := client.CreateTestWithApplicationVndAPIPlusJSONBody(ctx, orgUuid, &params, *body)
if err != nil {
return nil, err
}

parsedResponse, err := testApi.ParseCreateTestResponse(resp)
defer func() {
closeErr := resp.Body.Close()
a.logger.Err(closeErr).Msg("failed to close response body")
}()
if err != nil {
a.logger.Debug().Msg(err.Error())
return nil, err
}

switch parsedResponse.StatusCode() {
case http.StatusCreated:
// poll results
result, pollErr := a.pollTestForFindings(ctx, client, orgUuid, parsedResponse.ApplicationvndApiJSON201.Data.Id)
tracker.End("Analysis complete.")
return result, pollErr
default:
return nil, fmt.Errorf("failed to create test: %s", parsedResponse.Status())
}
}

func (a *analysisOrchestrator) pollTestForFindings(ctx context.Context, client *testApi.Client, org uuid.UUID, testId openapi_types.UUID) (*sarif.SarifResponse, error) {
method := "analysis.pollTestForFindings"
logger := a.logger.With().Str("method", method).Logger()

pollingTicker := time.NewTicker(1 * time.Second)
defer pollingTicker.Stop()
timeoutTimer := time.NewTimer(a.config.SnykCodeAnalysisTimeout())
defer timeoutTimer.Stop()
for {
select {
case <-timeoutTimer.C:
msg := "Snyk Code analysis timed out"
logger.Error().Str("scanJobId", testId.String()).Msg(msg)
return nil, errors.New(msg)
case <-pollingTicker.C:
findingsUrl, complete, err := a.retrieveTestURL(ctx, client, org, testId)
if err != nil {
return nil, err
}
if complete {
findings, findingsErr := a.retrieveFindings(ctx, testId, findingsUrl)
if findingsErr != nil {
return nil, findingsErr
}
return findings, nil
}
}
}
}

func (a *analysisOrchestrator) retrieveTestURL(ctx context.Context, client *testApi.Client, org uuid.UUID, testId openapi_types.UUID) (url string, completed bool, err error) {
method := "analysis.retrieveTestURL"
logger := a.logger.With().Str("method", method).Logger()
logger.Debug().Msg("retrieving Test URL")

httpResponse, err := client.GetTestResult(
ctx,
org,
testId,
&testApi.GetTestResultParams{Version: testApi.ApiVersion},
)
if err != nil {
logger.Err(err).Str("testId", testId.String()).Msg("error requesting the ScanJobResult")
return "", false, err
}
defer func() {
closeErr := httpResponse.Body.Close()
a.logger.Err(closeErr).Msg("failed to close response body")
}()

parsedResponse, err := testApi.ParseGetTestResultResponse(httpResponse)
if err != nil {
return "", false, err
}

switch parsedResponse.StatusCode() {
case 200:
stateDiscriminator, stateError := parsedResponse.ApplicationvndApiJSON200.Data.Attributes.Discriminator()
if stateError != nil {
return "", false, stateError
}

switch stateDiscriminator {
case string(testModels.TestAcceptedStateStatusAccepted):
fallthrough
case string(testModels.TestInProgressStateStatusInProgress):
return "", false, nil
case string(testModels.TestCompletedStateStatusCompleted):
testCompleted, stateCompleteError := parsedResponse.ApplicationvndApiJSON200.Data.Attributes.AsTestCompletedState()
if stateCompleteError != nil {
return "", false, stateCompleteError
}

findingsUrl := a.host(true) + testCompleted.Documents.EnrichedSarif + "?version=" + testApi.DocumentApiVersion
return findingsUrl, true, nil
default:
return "", false, fmt.Errorf("unexpected test status \"%s\"", stateDiscriminator)
}
default:
return "", false, fmt.Errorf("unexpected response status \"%d\"", parsedResponse.StatusCode())
}
}
Loading

0 comments on commit b513d7d

Please sign in to comment.