Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Introduce Option to specify repository URL #42

Merged
merged 4 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions internal/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,28 @@ import (
"io"
"net/http"
"strings"

"time"

"github.com/google/uuid"
openapi_types "github.com/oapi-codegen/runtime/types"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/snyk/code-client-go/config"

"github.com/snyk/code-client-go/config"
codeClientHTTP "github.com/snyk/code-client-go/http"
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"
"github.com/snyk/code-client-go/internal/util"
workspaceClient "github.com/snyk/code-client-go/internal/workspace/2024-03-12"
workspaces "github.com/snyk/code-client-go/internal/workspace/2024-03-12/workspaces"
"github.com/snyk/code-client-go/observability"
"github.com/snyk/code-client-go/sarif"
"github.com/snyk/code-client-go/scan"
)

//go:generate mockgen -destination=mocks/analysis.go -source=analysis.go -package mocks
type AnalysisOrchestrator interface {
CreateWorkspace(ctx context.Context, orgId string, requestId string, path string, bundleHash string) (string, error)
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)
}

Expand Down Expand Up @@ -89,7 +90,7 @@ func NewAnalysisOrchestrator(
return a
}

func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string, requestId string, path string, bundleHash string) (string, error) {
func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string, requestId string, target scan.Target, bundleHash string) (string, error) {
method := "analysis.CreateWorkspace"
logger := a.logger.With().Str("method", method).Logger()
logger.Debug().Msg("API: Creating the workspace")
Expand All @@ -99,18 +100,23 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string

orgUUID := uuid.MustParse(orgId)

repositoryUri, err := util.GetRepositoryUrl(path)
if err != nil {
a.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: path})
return "", fmt.Errorf("workspace is not a repository, cannot scan, %w", err)
if target == nil {
return "", fmt.Errorf("target is nil")
}

repositoryTarget, ok := target.(*scan.RepositoryTarget)
if !ok || repositoryTarget.GetRepositoryUrl() == "" {
err := fmt.Errorf("workspace is not a repository, cannot scan")
a.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: target.GetPath()})
return "", err
}

host := a.host(true)
a.logger.Info().Str("host", host).Str("path", path).Str("repositoryUri", repositoryUri).Msg("creating workspace")
a.logger.Info().Str("host", host).Str("path", repositoryTarget.GetPath()).Str("repositoryUri", repositoryTarget.GetRepositoryUrl()).Msg("creating workspace")

workspace, err := workspaceClient.NewClientWithResponses(host, workspaceClient.WithHTTPClient(a.httpClient))
if err != nil {
a.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: path})
a.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: repositoryTarget.GetPath()})
return "", fmt.Errorf("failed to connect to the workspace API %w", err)
}

Expand Down Expand Up @@ -144,7 +150,7 @@ func (a *analysisOrchestrator) CreateWorkspace(ctx context.Context, orgId string
WorkspaceType workspaces.WorkspacePostRequestDataAttributesWorkspaceType
}{
BundleId: bundleHash,
RepositoryUri: repositoryUri,
RepositoryUri: repositoryTarget.GetRepositoryUrl(),
WorkspaceType: "file_bundle_workspace",
}),
Type: "workspace",
Expand Down
31 changes: 22 additions & 9 deletions internal/analysis/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
httpmocks "github.com/snyk/code-client-go/http/mocks"
"github.com/snyk/code-client-go/internal/analysis"
"github.com/snyk/code-client-go/observability/mocks"
"github.com/snyk/code-client-go/scan"
)

func setup(t *testing.T) (*confMocks.MockConfig, *httpmocks.MockHTTPClient, *mocks.MockInstrumentor, *mocks.MockErrorReporter, zerolog.Logger) {
Expand Down Expand Up @@ -80,12 +81,15 @@ func TestAnalysis_CreateWorkspace(t *testing.T) {
Body: io.NopCloser(bytes.NewReader([]byte(`{"data":{"id": "c172d1db-b465-4764-99e1-ecedad03b06a"}}`))),
}, nil).Times(1)

target, err := scan.NewRepositoryTarget("../../")
assert.NoError(t, err)

analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter)
_, err := analysisOrchestrator.CreateWorkspace(
_, err = analysisOrchestrator.CreateWorkspace(
context.Background(),
"4a72d1db-b465-4764-99e1-ecedad03b06a",
"b372d1db-b465-4764-99e1-ecedad03b06a",
"../../",
target,
"testBundleHash")
assert.NoError(t, err)
}
Expand All @@ -95,15 +99,18 @@ func TestAnalysis_CreateWorkspace_NotARepository(t *testing.T) {
mockErrorReporter.EXPECT().CaptureError(gomock.Any(), gomock.Any())

repoDir := t.TempDir()
target, err := scan.NewRepositoryTarget(repoDir)
assert.ErrorContains(t, err, "open local repository")

analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter)
_, err := analysisOrchestrator.CreateWorkspace(
_, err = analysisOrchestrator.CreateWorkspace(
context.Background(),
"4a72d1db-b465-4764-99e1-ecedad03b06a",
"b372d1db-b465-4764-99e1-ecedad03b06a",
repoDir,
target,
"testBundleHash",
)
assert.ErrorContains(t, err, "open local repository")
assert.ErrorContains(t, err, "workspace is not a repository")
}

func TestAnalysis_CreateWorkspace_Failure(t *testing.T) {
Expand All @@ -126,12 +133,15 @@ func TestAnalysis_CreateWorkspace_Failure(t *testing.T) {
Body: io.NopCloser(bytes.NewReader([]byte(`{"errors": [{"detail": "error detail", "status": "400"}], "jsonapi": {"version": "version"}}`))),
}, nil).Times(1)

target, err := scan.NewRepositoryTarget("../../")
assert.NoError(t, err)

analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter)
_, err := analysisOrchestrator.CreateWorkspace(
_, err = analysisOrchestrator.CreateWorkspace(
context.Background(),
"4a72d1db-b465-4764-99e1-ecedad03b06a",
"b372d1db-b465-4764-99e1-ecedad03b06a",
"../../",
target,
"testBundleHash")
assert.ErrorContains(t, err, "error detail")
}
Expand Down Expand Up @@ -196,12 +206,15 @@ func TestAnalysis_CreateWorkspace_KnownErrors(t *testing.T) {

logger := zerolog.Nop()

target, err := scan.NewRepositoryTarget("../../")
assert.NoError(t, err)

analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter)
_, err := analysisOrchestrator.CreateWorkspace(
_, err = analysisOrchestrator.CreateWorkspace(
context.Background(),
"4a72d1db-b465-4764-99e1-ecedad03b06a",
"b372d1db-b465-4764-99e1-ecedad03b06a",
"../../",
target,
"testBundleHash",
)
assert.ErrorContains(t, err, tc.expectedError)
Expand Down
3 changes: 2 additions & 1 deletion internal/analysis/mocks/analysis.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"providerState": "Scan ID",
"request": {
"method": "GET",
"path": "/orgs/e7ea34c9-de0f-422c-bf2c-4654c2e2da90/scans/d164a5fa-444e-4ed6-a08f-c4e08a9f20a2",
"path": "/orgs/e7ea34c9-de0f-422c-bf2c-4654c2e2da90/scans/748d5bd0-9ce5-4cb9-876a-24c5601036ea",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any idea why this changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I know. I'll fix it, I implemented these tests in a non-determinstic way 🙊

"query": "version=2024-02-16%7Eexperimental"
},
"response": {
Expand Down
4 changes: 2 additions & 2 deletions internal/util/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ func GetRepositoryUrl(path string) (string, error) {

// based on the docs, the first URL is being used to fetch, so this is the one we use
repoUrl := remote.Config().URLs[0]
repoUrl, err = sanitiseCredentials(repoUrl)
repoUrl, err = SanitiseCredentials(repoUrl)

// we need to return an actual URL, not the SSH
repoUrl = strings.Replace(repoUrl, "[email protected]:", "https://github.com/", 1)
return repoUrl, err
}

func sanitiseCredentials(rawUrl string) (string, error) {
func SanitiseCredentials(rawUrl string) (string, error) {
parsedURL, err := url.Parse(rawUrl)
if err != nil {
return rawUrl, nil
Expand Down
13 changes: 7 additions & 6 deletions scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/snyk/code-client-go/internal/deepcode"
"github.com/snyk/code-client-go/observability"
"github.com/snyk/code-client-go/sarif"
"github.com/snyk/code-client-go/scan"
)

type codeScanner struct {
Expand Down Expand Up @@ -141,7 +142,7 @@ func (c *codeScanner) WithAnalysisOrchestrator(analysisOrchestrator analysis.Ana
func (c *codeScanner) UploadAndAnalyze(
ctx context.Context,
requestId string,
path string,
target scan.Target,
files <-chan string,
changedFiles map[string]bool,
) (*sarif.SarifResponse, string, error) {
Expand All @@ -154,14 +155,14 @@ func (c *codeScanner) UploadAndAnalyze(
c.logger.Info().Msg("Canceling Code scan - Code scanner received cancellation signal")
return nil, "", nil
}
b, err := c.bundleManager.Create(ctx, requestId, path, files, changedFiles)
b, err := c.bundleManager.Create(ctx, requestId, target.GetPath(), files, changedFiles)
if err != nil {
if bundle.IsNoFilesError(err) {
return nil, "", nil
}
if ctx.Err() == nil { // Only report errors that are not intentional cancellations
msg := "error creating bundle..."
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: path})
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: target.GetPath()})
return nil, "", err
} else {
c.logger.Info().Msg("Canceling Code scan - Code scanner received cancellation signal")
Expand All @@ -176,7 +177,7 @@ func (c *codeScanner) UploadAndAnalyze(
if err != nil {
if ctx.Err() != nil { // Only handle errors that are not intentional cancellations
msg := "error uploading files..."
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: path})
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: target.GetPath()})
return nil, bundleHash, err
} else {
c.logger.Info().Msg("Canceling Code scan - Code scanner received cancellation signal")
Expand All @@ -189,11 +190,11 @@ func (c *codeScanner) UploadAndAnalyze(
return nil, bundleHash, nil
}

workspaceId, err := c.analysisOrchestrator.CreateWorkspace(ctx, c.config.Organization(), requestId, path, bundleHash)
workspaceId, err := c.analysisOrchestrator.CreateWorkspace(ctx, c.config.Organization(), requestId, target, bundleHash)
if err != nil {
if ctx.Err() == nil { // Only handle errors that are not intentional cancellations
msg := "error creating workspace for bundle..."
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: path})
c.errorReporter.CaptureError(errors.Wrap(err, msg), observability.ErrorReporterOptions{ErrorDiagnosticPath: target.GetPath()})
return nil, bundleHash, err
} else {
c.logger.Info().Msg("Canceling Code scan - Code scanner received cancellation signal")
Expand Down
50 changes: 50 additions & 0 deletions scan/target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package scan

import "github.com/snyk/code-client-go/internal/util"

type RepositoryTarget struct {
LocalFilePath string
repositoryUrl string
}

type Target interface {
GetPath() string
}

func (r RepositoryTarget) GetPath() string {
return r.LocalFilePath
}

func (r RepositoryTarget) GetRepositoryUrl() string {
return r.repositoryUrl
}

type TargetOptions func(*RepositoryTarget) error

func WithRepositoryUrl(repositoryUrl string) TargetOptions {
return func(target *RepositoryTarget) error {
var err error
target.repositoryUrl, err = util.SanitiseCredentials(repositoryUrl)
return err
}
}

func NewRepositoryTarget(path string, options ...TargetOptions) (Target, error) {
result := &RepositoryTarget{
LocalFilePath: path,
}

for _, option := range options {
option(result)
}

if len(result.repositoryUrl) == 0 {
var err error
result.repositoryUrl, err = util.GetRepositoryUrl(path)
if err != nil {
return &RepositoryTarget{}, err
}
}

return result, nil
}
38 changes: 38 additions & 0 deletions scan/target_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package scan

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestTarget_pathOnly(t *testing.T) {
expectedPath := "./"
target, err := NewRepositoryTarget(expectedPath)
assert.NoError(t, err)
repoTarget, ok := target.(*RepositoryTarget)
assert.True(t, ok)
assert.NotEmpty(t, repoTarget.GetRepositoryUrl())
assert.Equal(t, expectedPath, repoTarget.GetPath())
}

func TestTarget_pathToNonRepo(t *testing.T) {
expectedPath := t.TempDir()
target, err := NewRepositoryTarget(expectedPath)
assert.Error(t, err)
repoTarget, ok := target.(*RepositoryTarget)
assert.True(t, ok)
assert.Empty(t, repoTarget.GetRepositoryUrl())
assert.Empty(t, repoTarget.GetPath())
}

func TestTarget_withRepoUrl(t *testing.T) {
expectedRepoUrl := "https://myrepo.com/hello_world"
expectedPath := "/hello_world"
target, err := NewRepositoryTarget(expectedPath, WithRepositoryUrl("https://user:[email protected]/hello_world"))
assert.NoError(t, err)
repoTarget, ok := target.(*RepositoryTarget)
assert.True(t, ok)
assert.Equal(t, expectedRepoUrl, repoTarget.GetRepositoryUrl())
assert.Equal(t, expectedPath, repoTarget.GetPath())
}
Loading
Loading