diff --git a/README.md b/README.md index 13b93ed2..d39bc8ec 100644 --- a/README.md +++ b/README.md @@ -15,51 +15,55 @@ $ go get github.com/snyk/code-client-go Use the HTTP client to make HTTP requests with configured retriable codes and authorisation headers for Snyk Rest APIs. -Implement the `github.com/snyk/code-client-go/http.Config` interface to configure the Snyk Code API client from applications. +You can either configure the client using the functional options pattern provided or by implementing the interfaces. -Provide a net/http.Client factory to customize the underlying HTTP protocol behavior (timeouts, etc). +Provide a `net/http.Client` factory to customize the underlying HTTP protocol behavior (timeouts, etc). ```go import ( "net/http" "github.com/rs/zerolog" - codehttp "github.com/snyk/code-client-go/http" + codeClientHTTP "github.com/snyk/code-client-go/http" + codeClientObservability "github.com/snyk/code-client-go/observability" ) logger := zerlog.NewLogger(...) -config := newConfigForMyApp() -httpClient := codehttp.NewHTTPClient(logger, config, func() *http.Client { return http.DefaultClient }, codeInstrumentor, codeErrorReporter) +instrumentor := codeClientObservability.NewInstrumentor() +errorReporter := codeClientObservability.NewErrorReporter() +httpClient := codeClientHTTP.NewHTTPClient( + func() *http.Client { + return &http.Client{ + Timeout: time.Duration(1) * time.Second, + } + }, + codeClientHTTP.WithRetryCount(1), + codeClientHTTP.WithLogger(logger), + codeClientHTTP.WithInstrumentor(instrumentor), + codeClientHTTP.WithErrorReporter(errorReporter), +) ``` -The HTTP client exposes a `DoCall` function. +The HTTP client exposes a `Do` function. ### Configuration -Implement the `http.Config` interface to configure the Snyk Code API client from applications. +Implement the `config.Config` interface to configure the Snyk Code API client from applications. ### Code Scanner Use the Code Scanner to trigger a scan for a Snyk Code workspace using the Bundle Manager created above. + The Code Scanner exposes a `UploadAndAnalyze` function, which can be used like this: ```go -import ( - "net/http" - - "github.com/rs/zerolog" - code "github.com/snyk/code-client-go" -) - -logger := zerlog.NewLogger(...) config := newConfigForMyApp() - codeScanner := code.NewCodeScanner( httpClient, - config, - codeInstrumentor, - codeErrorReporter, - logger, + config, + codeClientHTTP.WithLogger(logger), + codeClientHTTP.WithInstrumentor(instrumentor), + codeClientHTTP.WithErrorReporter(errorReporter), ) code.UploadAndAnalyze(context.Background(), requestId, "path/to/workspace", channelForWalkingFiles, changedFiles) ``` diff --git a/http/http.go b/http/http.go index e94abd9a..d13dec7c 100644 --- a/http/http.go +++ b/http/http.go @@ -33,12 +33,14 @@ type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } +type HTTPClientFactory func() *http.Client + type httpClient struct { - retryCount int - clientFactory func() *http.Client - instrumentor observability.Instrumentor - errorReporter observability.ErrorReporter - logger *zerolog.Logger + retryCount int + httpClientFactory HTTPClientFactory + instrumentor observability.Instrumentor + errorReporter observability.ErrorReporter + logger *zerolog.Logger } type OptionFunc func(*httpClient) @@ -68,18 +70,18 @@ func WithLogger(logger *zerolog.Logger) OptionFunc { } func NewHTTPClient( - clientFactory func() *http.Client, + httpClientFactory HTTPClientFactory, options ...OptionFunc, ) HTTPClient { nopLogger := zerolog.Nop() instrumentor := observability.NewInstrumentor() errorReporter := observability.NewErrorReporter(&nopLogger) client := &httpClient{ - retryCount: 3, - clientFactory: clientFactory, - instrumentor: instrumentor, - errorReporter: errorReporter, - logger: &nopLogger, + retryCount: 3, + httpClientFactory: httpClientFactory, + instrumentor: instrumentor, + errorReporter: errorReporter, + logger: &nopLogger, } for _, option := range options { @@ -143,7 +145,7 @@ func (s *httpClient) httpCall(req *http.Request) (*http.Response, error) { copyReqBody = io.NopCloser(bytes.NewBuffer(buf)) req.Body = reqBody } - response, err := s.clientFactory().Do(req) + response, err := s.httpClientFactory().Do(req) req.Body = copyReqBody if err != nil { diff --git a/internal/analysis/analysis.go b/internal/analysis/analysis.go index 980dc072..05634475 100644 --- a/internal/analysis/analysis.go +++ b/internal/analysis/analysis.go @@ -52,11 +52,11 @@ type AnalysisOrchestrator interface { } func NewAnalysisOrchestrator( + config config.Config, logger *zerolog.Logger, httpClient codeClientHTTP.HTTPClient, instrumentor observability.Instrumentor, errorReporter observability.ErrorReporter, - config config.Config, ) *analysisOrchestrator { return &analysisOrchestrator{ httpClient, diff --git a/internal/analysis/analysis_test.go b/internal/analysis/analysis_test.go index 689ab8c5..df8cc123 100644 --- a/internal/analysis/analysis_test.go +++ b/internal/analysis/analysis_test.go @@ -69,7 +69,7 @@ func TestAnalysis_CreateWorkspace(t *testing.T) { logger := zerolog.Nop() - analysisOrchestrator := analysis.NewAnalysisOrchestrator(&logger, mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter) _, err := analysisOrchestrator.CreateWorkspace( context.Background(), "4a72d1db-b465-4764-99e1-ecedad03b06a", @@ -98,7 +98,7 @@ func TestAnalysis_CreateWorkspace_NotARepository(t *testing.T) { logger := zerolog.Nop() repoDir := t.TempDir() - analysisOrchestrator := analysis.NewAnalysisOrchestrator(&logger, mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter) _, err := analysisOrchestrator.CreateWorkspace( context.Background(), "4a72d1db-b465-4764-99e1-ecedad03b06a", @@ -141,7 +141,7 @@ func TestAnalysis_CreateWorkspace_Failure(t *testing.T) { logger := zerolog.Nop() - analysisOrchestrator := analysis.NewAnalysisOrchestrator(&logger, mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter) _, err := analysisOrchestrator.CreateWorkspace( context.Background(), "4a72d1db-b465-4764-99e1-ecedad03b06a", @@ -169,7 +169,7 @@ func TestAnalysis_RunAnalysis(t *testing.T) { logger := zerolog.Nop() - analysisOrchestrator := analysis.NewAnalysisOrchestrator(&logger, mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + analysisOrchestrator := analysis.NewAnalysisOrchestrator(mockConfig, &logger, mockHTTPClient, mockInstrumentor, mockErrorReporter) actual, err := analysisOrchestrator.RunAnalysis() require.NoError(t, err) assert.Equal(t, "COMPLETE", actual.Status) diff --git a/internal/bundle/bundle.go b/internal/bundle/bundle.go index 7d1edbfb..85002ecc 100644 --- a/internal/bundle/bundle.go +++ b/internal/bundle/bundle.go @@ -34,7 +34,7 @@ type Bundle interface { } type deepCodeBundle struct { - SnykCode deepcode.SnykCodeClient + SnykCode deepcode.DeepcodeClient instrumentor observability.Instrumentor errorReporter observability.ErrorReporter logger *zerolog.Logger @@ -46,7 +46,7 @@ type deepCodeBundle struct { } func NewBundle( - snykCode deepcode.SnykCodeClient, + snykCode deepcode.DeepcodeClient, instrumentor observability.Instrumentor, errorReporter observability.ErrorReporter, logger *zerolog.Logger, diff --git a/internal/bundle/bundle_manager.go b/internal/bundle/bundle_manager.go index a60d9f34..ddac5d49 100644 --- a/internal/bundle/bundle_manager.go +++ b/internal/bundle/bundle_manager.go @@ -31,7 +31,7 @@ import ( // TODO: add progress tracker for percentage progress type bundleManager struct { - SnykCode deepcode.SnykCodeClient + deepcodeClient deepcode.DeepcodeClient instrumentor observability.Instrumentor errorReporter observability.ErrorReporter logger *zerolog.Logger @@ -57,13 +57,13 @@ type BundleManager interface { } func NewBundleManager( + deepcodeClient deepcode.DeepcodeClient, logger *zerolog.Logger, - SnykCode deepcode.SnykCodeClient, instrumentor observability.Instrumentor, errorReporter observability.ErrorReporter, ) *bundleManager { return &bundleManager{ - SnykCode: SnykCode, + deepcodeClient: deepcodeClient, instrumentor: instrumentor, errorReporter: errorReporter, logger: logger, @@ -133,10 +133,10 @@ func (b *bundleManager) Create(ctx context.Context, var bundleHash string var missingFiles []string if len(fileHashes) > 0 { - bundleHash, missingFiles, err = b.SnykCode.CreateBundle(span.Context(), fileHashes) + bundleHash, missingFiles, err = b.deepcodeClient.CreateBundle(span.Context(), fileHashes) } bundle = NewBundle( - b.SnykCode, + b.deepcodeClient, b.instrumentor, b.errorReporter, b.logger, @@ -213,7 +213,7 @@ func (b *bundleManager) groupInBatches( func (b *bundleManager) IsSupported(ctx context.Context, file string) (bool, error) { if b.supportedExtensions.Size() == 0 && b.supportedConfigFiles.Size() == 0 { - filters, err := b.SnykCode.GetFilters(ctx) + filters, err := b.deepcodeClient.GetFilters(ctx) if err != nil { b.logger.Error().Err(err).Msg("could not get filters") return false, err diff --git a/internal/bundle/bundle_manager_test.go b/internal/bundle/bundle_manager_test.go index 2116e26f..ba903c7d 100644 --- a/internal/bundle/bundle_manager_test.go +++ b/internal/bundle/bundle_manager_test.go @@ -42,7 +42,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, @@ -61,7 +61,7 @@ func Test_Create(t *testing.T) { err := os.WriteFile(file, []byte(data), 0600) require.NoError(t, err) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", dir, @@ -77,7 +77,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, @@ -93,7 +93,7 @@ func Test_Create(t *testing.T) { err := os.WriteFile(file, []byte(data), 0600) require.NoError(t, err) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", dir, @@ -110,7 +110,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, @@ -130,7 +130,7 @@ func Test_Create(t *testing.T) { ) require.NoError(t, err) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", dir, @@ -147,7 +147,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, @@ -166,7 +166,7 @@ func Test_Create(t *testing.T) { }, ) require.NoError(t, err) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", dir, @@ -181,7 +181,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{".test"}, Extensions: []string{}, @@ -199,7 +199,7 @@ func Test_Create(t *testing.T) { err := os.WriteFile(file, []byte("some content so the file won't be skipped"), 0600) assert.Nil(t, err) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", tempDir, @@ -214,7 +214,7 @@ func Test_Create(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, @@ -247,7 +247,7 @@ func Test_Create(t *testing.T) { require.NoError(t, err) } - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundle, err := bundleManager.Create(context.Background(), "testRequestId", tempDir, @@ -272,14 +272,14 @@ func Test_Upload(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "bundleHash", gomock.Len(1), []string{}).Times(1) mockInstrumentor := mocks.NewMockInstrumentor(ctrl) mockInstrumentor.EXPECT().StartSpan(gomock.Any(), gomock.Any()).Return(mockSpan).Times(2) mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(2) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) documentURI, bundleFile := createTempFileInDir(t, "bundleDoc.java", 10, temporaryDir) bundleFileMap := map[string]deepcode.BundleFile{} bundleFileMap[documentURI] = bundleFile @@ -296,14 +296,14 @@ func Test_Upload(t *testing.T) { ctrl := gomock.NewController(t) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "bundleHash", gomock.Len(3), []string{}).Return("newBundleHash", []string{}, nil).Times(1) mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "newBundleHash", gomock.Len(2), []string{}).Return("newerBundleHash", []string{}, nil).Times(1) mockInstrumentor := mocks.NewMockInstrumentor(ctrl) mockInstrumentor.EXPECT().StartSpan(gomock.Any(), gomock.Any()).Return(mockSpan).Times(2) mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(2) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - var bundleManager = bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + var bundleManager = bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) bundleFileMap := map[string]deepcode.BundleFile{} var missingFiles []string @@ -341,14 +341,14 @@ func createTempFileInDir(t *testing.T, name string, size int, temporaryDir strin func Test_IsSupported_Extensions(t *testing.T) { ctrl := gomock.NewController(t) - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: []string{}, Extensions: []string{".java"}, }, nil) mockInstrumentor := mocks.NewMockInstrumentor(ctrl) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - bundler := bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + bundler := bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) t.Run("should return true for supported languages", func(t *testing.T) { supported, _ := bundler.IsSupported(context.Background(), "C:\\some\\path\\Test.java") @@ -380,14 +380,14 @@ func Test_IsSupported_ConfigFiles(t *testing.T) { } ctrl := gomock.NewController(t) - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().GetFilters(gomock.Any()).Return(deepcode.FiltersResponse{ ConfigFiles: configFilesFromFiltersEndpoint, Extensions: []string{}, }, nil) mockInstrumentor := mocks.NewMockInstrumentor(ctrl) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - bundler := bundle.NewBundleManager(newLogger(t), mockSnykCodeClient, mockInstrumentor, mockErrorReporter) + bundler := bundle.NewBundleManager(mockSnykCodeClient, newLogger(t), mockInstrumentor, mockErrorReporter) dir, _ := os.Getwd() t.Run("should return true for supported config files", func(t *testing.T) { diff --git a/internal/bundle/bundle_test.go b/internal/bundle/bundle_test.go index d2de6c5b..d8242eb3 100644 --- a/internal/bundle/bundle_test.go +++ b/internal/bundle/bundle_test.go @@ -42,7 +42,7 @@ func Test_UploadBatch(t *testing.T) { t.Run("when no documents - creates nothing", func(t *testing.T) { ctrl := gomock.NewController(t) - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSpan := mocks.NewMockSpan(ctrl) mockSpan.EXPECT().Context().AnyTimes() @@ -59,7 +59,7 @@ func Test_UploadBatch(t *testing.T) { t.Run("when no bundles - creates new deepCodeBundle and sets hash", func(t *testing.T) { ctrl := gomock.NewController(t) - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "testBundleHash", map[string]deepcode.BundleFile{ "file": {}, }, []string{}).Return("testBundleHash", []string{}, nil) @@ -78,7 +78,7 @@ func Test_UploadBatch(t *testing.T) { t.Run("when existing bundles - extends deepCodeBundle and updates hash", func(t *testing.T) { ctrl := gomock.NewController(t) - mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl) + mockSnykCodeClient := deepcodeMocks.NewMockDeepcode(ctrl) mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "testBundleHash", map[string]deepcode.BundleFile{ "another": {}, "file": {}, diff --git a/internal/deepcode/client.go b/internal/deepcode/client.go index a4d18ea8..e22ebf31 100644 --- a/internal/deepcode/client.go +++ b/internal/deepcode/client.go @@ -37,7 +37,7 @@ import ( ) //go:generate mockgen -destination=mocks/client.go -source=client.go -package mocks -type SnykCodeClient interface { +type DeepcodeClient interface { GetFilters(ctx context.Context) ( filters FiltersResponse, err error) @@ -70,7 +70,7 @@ type BundleResponse struct { MissingFiles []string `json:"missingFiles"` } -type snykCodeClient struct { +type deepcodeClient struct { httpClient codeClientHTTP.HTTPClient instrumentor observability.Instrumentor errorReporter observability.ErrorReporter @@ -78,14 +78,14 @@ type snykCodeClient struct { config config.Config } -func NewSnykCodeClient( - logger *zerolog.Logger, +func NewDeepcodeClient( + config config.Config, httpClient codeClientHTTP.HTTPClient, + logger *zerolog.Logger, instrumentor observability.Instrumentor, errorReporter observability.ErrorReporter, - config config.Config, -) *snykCodeClient { - return &snykCodeClient{ +) *deepcodeClient { + return &deepcodeClient{ httpClient, instrumentor, errorReporter, @@ -94,7 +94,7 @@ func NewSnykCodeClient( } } -func (s *snykCodeClient) GetFilters(ctx context.Context) ( +func (s *deepcodeClient) GetFilters(ctx context.Context) ( filters FiltersResponse, err error, ) { @@ -118,7 +118,7 @@ func (s *snykCodeClient) GetFilters(ctx context.Context) ( return filters, nil } -func (s *snykCodeClient) CreateBundle( +func (s *deepcodeClient) CreateBundle( ctx context.Context, filesToFilehashes map[string]string, ) (string, []string, error) { @@ -148,7 +148,7 @@ func (s *snykCodeClient) CreateBundle( return bundle.BundleHash, bundle.MissingFiles, nil } -func (s *snykCodeClient) ExtendBundle( +func (s *deepcodeClient) ExtendBundle( ctx context.Context, bundleHash string, files map[string]BundleFile, @@ -180,7 +180,7 @@ func (s *snykCodeClient) ExtendBundle( } // This is only exported for tests. -func (s *snykCodeClient) Host() (string, error) { +func (s *deepcodeClient) Host() (string, error) { var codeApiRegex = regexp.MustCompile(`^(deeproxy\.)?`) snykCodeApiUrl := s.config.SnykCodeApi() @@ -204,7 +204,7 @@ func (s *snykCodeClient) Host() (string, error) { return u.String(), nil } -func (s *snykCodeClient) Request( +func (s *deepcodeClient) Request( method string, path string, requestBody []byte, @@ -256,7 +256,7 @@ func (s *snykCodeClient) Request( return responseBody, nil } -func (s *snykCodeClient) addHeaders(method string, req *http.Request) { +func (s *deepcodeClient) addHeaders(method string, req *http.Request) { // Setting a chosen org name for the request org := s.config.Organization() if org != "" { @@ -272,7 +272,7 @@ func (s *snykCodeClient) addHeaders(method string, req *http.Request) { } } -func (s *snykCodeClient) encodeIfNeeded(method string, requestBody []byte) (*bytes.Buffer, error) { +func (s *deepcodeClient) encodeIfNeeded(method string, requestBody []byte) (*bytes.Buffer, error) { b := new(bytes.Buffer) mustBeEncoded := s.mustBeEncoded(method) if mustBeEncoded { @@ -287,11 +287,11 @@ func (s *snykCodeClient) encodeIfNeeded(method string, requestBody []byte) (*byt return b, nil } -func (s *snykCodeClient) mustBeEncoded(method string) bool { +func (s *deepcodeClient) mustBeEncoded(method string) bool { return method == http.MethodPost || method == http.MethodPut } -func (s *snykCodeClient) checkResponseCode(r *http.Response) error { +func (s *deepcodeClient) checkResponseCode(r *http.Response) error { if r.StatusCode >= 200 && r.StatusCode <= 299 { return nil } diff --git a/internal/deepcode/client_pact_test.go b/internal/deepcode/client_pact_test.go index e2df4fee..21d10ec4 100644 --- a/internal/deepcode/client_pact_test.go +++ b/internal/deepcode/client_pact_test.go @@ -43,7 +43,7 @@ const ( // Common test data var pact dsl.Pact -var client deepcode.SnykCodeClient +var client deepcode.DeepcodeClient func TestSnykCodeClientPact(t *testing.T) { setupPact(t) @@ -230,7 +230,7 @@ func setupPact(t *testing.T) { codeClientHTTP.WithErrorReporter(errorReporter), codeClientHTTP.WithLogger(newLogger(t)), ) - client = deepcode.NewSnykCodeClient(newLogger(t), httpClient, instrumentor, errorReporter, config) + client = deepcode.NewDeepcodeClient(config, httpClient, newLogger(t), instrumentor, errorReporter) } func getPutPostHeaderMatcher() dsl.MapMatcher { diff --git a/internal/deepcode/client_test.go b/internal/deepcode/client_test.go index 199e458c..fe54e96e 100644 --- a/internal/deepcode/client_test.go +++ b/internal/deepcode/client_test.go @@ -88,7 +88,7 @@ func TestSnykCodeBackendService_GetFilters(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(1) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) filters, err := s.GetFilters(context.Background()) assert.Nil(t, err) assert.Equal(t, 1, len(filters.ConfigFiles)) @@ -123,7 +123,7 @@ func TestSnykCodeBackendService_GetFilters_Failure(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(1) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) _, err := s.GetFilters(context.Background()) assert.Error(t, err) } @@ -157,7 +157,7 @@ func TestSnykCodeBackendService_CreateBundle(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(1) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) files := map[string]string{} randomAddition := fmt.Sprintf("\n public void random() { System.out.println(\"%d\") }", time.Now().UnixMicro()) @@ -197,7 +197,7 @@ func TestSnykCodeBackendService_CreateBundle_Failure(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(1) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) files := map[string]string{} randomAddition := fmt.Sprintf("\n public void random() { System.out.println(\"%d\") }", time.Now().UnixMicro()) @@ -247,7 +247,7 @@ func TestSnykCodeBackendService_ExtendBundle(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(2) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) var removedFiles []string files := map[string]string{} files[path1] = util.Hash([]byte(content)) @@ -300,7 +300,7 @@ func TestSnykCodeBackendService_ExtendBundle_Failure(t *testing.T) { mockInstrumentor.EXPECT().Finish(gomock.Any()).Times(2) mockErrorReporter := mocks.NewMockErrorReporter(ctrl) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) var removedFiles []string files := map[string]string{} files[path1] = util.Hash([]byte(content)) @@ -323,7 +323,7 @@ func Test_Host(t *testing.T) { mockConfig.EXPECT().Organization().AnyTimes().Return("00000000-0000-0000-0000-000000000023") mockConfig.EXPECT().IsFedramp().Times(1).Return(true) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) actual, err := s.Host() assert.Nil(t, err) @@ -333,7 +333,7 @@ func Test_Host(t *testing.T) { t.Run("Does not change the URL if it's not FedRAMP", func(t *testing.T) { mockConfig.EXPECT().Organization().AnyTimes().Return("") mockConfig.EXPECT().IsFedramp().Times(1).Return(false) - s := deepcode.NewSnykCodeClient(newLogger(t), mockHTTPClient, mockInstrumentor, mockErrorReporter, mockConfig) + s := deepcode.NewDeepcodeClient(mockConfig, mockHTTPClient, newLogger(t), mockInstrumentor, mockErrorReporter) actual, err := s.Host() assert.Nil(t, err) diff --git a/internal/deepcode/mocks/client.go b/internal/deepcode/mocks/client.go index 03914bb8..0d529401 100644 --- a/internal/deepcode/mocks/client.go +++ b/internal/deepcode/mocks/client.go @@ -12,31 +12,31 @@ import ( deepcode "github.com/snyk/code-client-go/internal/deepcode" ) -// MockSnykCodeClient is a mock of SnykCodeClient interface. -type MockSnykCodeClient struct { +// MockDeepcode is a mock of DeepcodeClient interface. +type MockDeepcode struct { ctrl *gomock.Controller - recorder *MockSnykCodeClientMockRecorder + recorder *MockDeepcodeMockRecorder } -// MockSnykCodeClientMockRecorder is the mock recorder for MockSnykCodeClient. -type MockSnykCodeClientMockRecorder struct { - mock *MockSnykCodeClient +// MockDeepcodeMockRecorder is the mock recorder for MockDeepcode. +type MockDeepcodeMockRecorder struct { + mock *MockDeepcode } -// NewMockSnykCodeClient creates a new mock instance. -func NewMockSnykCodeClient(ctrl *gomock.Controller) *MockSnykCodeClient { - mock := &MockSnykCodeClient{ctrl: ctrl} - mock.recorder = &MockSnykCodeClientMockRecorder{mock} +// NewMockDeepcode creates a new mock instance. +func NewMockDeepcode(ctrl *gomock.Controller) *MockDeepcode { + mock := &MockDeepcode{ctrl: ctrl} + mock.recorder = &MockDeepcodeMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSnykCodeClient) EXPECT() *MockSnykCodeClientMockRecorder { +func (m *MockDeepcode) EXPECT() *MockDeepcodeMockRecorder { return m.recorder } // CreateBundle mocks base method. -func (m *MockSnykCodeClient) CreateBundle(ctx context.Context, files map[string]string) (string, []string, error) { +func (m *MockDeepcode) CreateBundle(ctx context.Context, files map[string]string) (string, []string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateBundle", ctx, files) ret0, _ := ret[0].(string) @@ -46,13 +46,13 @@ func (m *MockSnykCodeClient) CreateBundle(ctx context.Context, files map[string] } // CreateBundle indicates an expected call of CreateBundle. -func (mr *MockSnykCodeClientMockRecorder) CreateBundle(ctx, files interface{}) *gomock.Call { +func (mr *MockDeepcodeMockRecorder) CreateBundle(ctx, files interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBundle", reflect.TypeOf((*MockSnykCodeClient)(nil).CreateBundle), ctx, files) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBundle", reflect.TypeOf((*MockDeepcode)(nil).CreateBundle), ctx, files) } // ExtendBundle mocks base method. -func (m *MockSnykCodeClient) ExtendBundle(ctx context.Context, bundleHash string, files map[string]deepcode.BundleFile, removedFiles []string) (string, []string, error) { +func (m *MockDeepcode) ExtendBundle(ctx context.Context, bundleHash string, files map[string]deepcode.BundleFile, removedFiles []string) (string, []string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ExtendBundle", ctx, bundleHash, files, removedFiles) ret0, _ := ret[0].(string) @@ -62,13 +62,13 @@ func (m *MockSnykCodeClient) ExtendBundle(ctx context.Context, bundleHash string } // ExtendBundle indicates an expected call of ExtendBundle. -func (mr *MockSnykCodeClientMockRecorder) ExtendBundle(ctx, bundleHash, files, removedFiles interface{}) *gomock.Call { +func (mr *MockDeepcodeMockRecorder) ExtendBundle(ctx, bundleHash, files, removedFiles interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendBundle", reflect.TypeOf((*MockSnykCodeClient)(nil).ExtendBundle), ctx, bundleHash, files, removedFiles) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendBundle", reflect.TypeOf((*MockDeepcode)(nil).ExtendBundle), ctx, bundleHash, files, removedFiles) } // GetFilters mocks base method. -func (m *MockSnykCodeClient) GetFilters(ctx context.Context) (deepcode.FiltersResponse, error) { +func (m *MockDeepcode) GetFilters(ctx context.Context) (deepcode.FiltersResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetFilters", ctx) ret0, _ := ret[0].(deepcode.FiltersResponse) @@ -77,7 +77,7 @@ func (m *MockSnykCodeClient) GetFilters(ctx context.Context) (deepcode.FiltersRe } // GetFilters indicates an expected call of GetFilters. -func (mr *MockSnykCodeClientMockRecorder) GetFilters(ctx interface{}) *gomock.Call { +func (mr *MockDeepcodeMockRecorder) GetFilters(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilters", reflect.TypeOf((*MockSnykCodeClient)(nil).GetFilters), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilters", reflect.TypeOf((*MockDeepcode)(nil).GetFilters), ctx) } diff --git a/scan.go b/scan.go index 8173fcb4..066bc1d2 100644 --- a/scan.go +++ b/scan.go @@ -33,8 +33,10 @@ import ( ) type codeScanner struct { + httpClient codeClientHTTP.HTTPClient bundleManager bundle.BundleManager analysisOrchestrator analysis.AnalysisOrchestrator + instrumentor observability.Instrumentor errorReporter observability.ErrorReporter logger *zerolog.Logger config config.Config @@ -50,24 +52,65 @@ type CodeScanner interface { ) (*sarif.SarifResponse, string, error) } +type OptionFunc func(*codeScanner) + +func WithInstrumentor(instrumentor observability.Instrumentor) OptionFunc { + return func(c *codeScanner) { + c.instrumentor = instrumentor + c.initDeps(c.httpClient) + } +} + +func WithErrorReporter(errorReporter observability.ErrorReporter) OptionFunc { + return func(c *codeScanner) { + c.errorReporter = errorReporter + c.initDeps(c.httpClient) + } +} + +func WithLogger(logger *zerolog.Logger) OptionFunc { + return func(c *codeScanner) { + c.logger = logger + c.initDeps(c.httpClient) + } +} + +func (c *codeScanner) initDeps( + httpClient codeClientHTTP.HTTPClient, +) { + deepcodeClient := deepcode.NewDeepcodeClient(c.config, httpClient, c.logger, c.instrumentor, c.errorReporter) + bundleManager := bundle.NewBundleManager(deepcodeClient, c.logger, c.instrumentor, c.errorReporter) + c.bundleManager = bundleManager + analysisOrchestrator := analysis.NewAnalysisOrchestrator(c.config, c.logger, httpClient, c.instrumentor, c.errorReporter) + c.analysisOrchestrator = analysisOrchestrator +} + // NewCodeScanner creates a Code Scanner which can be used to trigger Snyk Code on a folder. func NewCodeScanner( - httpClient codeClientHTTP.HTTPClient, config config.Config, - instrumentor observability.Instrumentor, - errorReporter observability.ErrorReporter, - logger *zerolog.Logger, + httpClient codeClientHTTP.HTTPClient, + options ...OptionFunc, ) *codeScanner { - snykCode := deepcode.NewSnykCodeClient(logger, httpClient, instrumentor, errorReporter, config) - bundleManager := bundle.NewBundleManager(logger, snykCode, instrumentor, errorReporter) - analysisOrchestrator := analysis.NewAnalysisOrchestrator(logger, httpClient, instrumentor, errorReporter, config) - return &codeScanner{ - bundleManager: bundleManager, - analysisOrchestrator: analysisOrchestrator, - errorReporter: errorReporter, - logger: logger, - config: config, + nopLogger := zerolog.Nop() + instrumentor := observability.NewInstrumentor() + errorReporter := observability.NewErrorReporter(&nopLogger) + + scanner := &codeScanner{ + httpClient: httpClient, + errorReporter: errorReporter, + logger: &nopLogger, + instrumentor: instrumentor, + config: config, } + + // initialize other dependencies with the default + scanner.initDeps(httpClient) + + for _, option := range options { + option(scanner) + } + + return scanner } // WithBundleManager creates a new Code Scanner from the current one and replaces the bundle manager. diff --git a/scan_smoke_test.go b/scan_smoke_test.go index 96b47594..2970cb5b 100644 --- a/scan_smoke_test.go +++ b/scan_smoke_test.go @@ -35,7 +35,6 @@ import ( "github.com/snyk/code-client-go/internal/util/testutil" ) -//nolint:dupl // test cases func Test_SmokeScan_HTTPS(t *testing.T) { if os.Getenv("SMOKE_TESTS") != "true" { t.Skip() @@ -63,14 +62,16 @@ func Test_SmokeScan_HTTPS(t *testing.T) { codeClientHTTP.WithLogger(&logger), ) - codeScanner := codeClient.NewCodeScanner(httpClient, config, instrumentor, errorReporter, &logger) + codeScanner := codeClient.NewCodeScanner( + config, + httpClient, + codeClient.WithLogger(&logger), codeClient.WithInstrumentor(instrumentor), codeClient.WithErrorReporter(errorReporter)) response, bundleHash, scanErr := codeScanner.UploadAndAnalyze(context.Background(), uuid.New().String(), cloneTargetDir, files, map[string]bool{}) require.NoError(t, scanErr) require.NotEmpty(t, bundleHash) require.NotNil(t, response) } -//nolint:dupl // test cases func Test_SmokeScan_SSH(t *testing.T) { if os.Getenv("SMOKE_TESTS") != "true" { t.Skip() @@ -98,7 +99,13 @@ func Test_SmokeScan_SSH(t *testing.T) { codeClientHTTP.WithLogger(&logger), ) - codeScanner := codeClient.NewCodeScanner(httpClient, config, instrumentor, errorReporter, &logger) + codeScanner := codeClient.NewCodeScanner( + config, + httpClient, + codeClient.WithInstrumentor(instrumentor), + codeClient.WithErrorReporter(errorReporter), + codeClient.WithLogger(&logger), + ) response, bundleHash, scanErr := codeScanner.UploadAndAnalyze(context.Background(), uuid.New().String(), cloneTargetDir, files, map[string]bool{}) require.NoError(t, scanErr) require.NotEmpty(t, bundleHash) @@ -130,7 +137,13 @@ func Test_SmokeScan_SubFolder(t *testing.T) { codeClientHTTP.WithLogger(&logger), ) - codeScanner := codeClient.NewCodeScanner(httpClient, config, instrumentor, errorReporter, &logger) + codeScanner := codeClient.NewCodeScanner( + config, + httpClient, + codeClient.WithInstrumentor(instrumentor), + codeClient.WithErrorReporter(errorReporter), + codeClient.WithLogger(&logger), + ) response, bundleHash, scanErr := codeScanner.UploadAndAnalyze(context.Background(), uuid.New().String(), cloneTargetDir, files, map[string]bool{}) require.NoError(t, scanErr) require.NotEmpty(t, bundleHash) diff --git a/scan_test.go b/scan_test.go index b610d099..8ddbdde8 100644 --- a/scan_test.go +++ b/scan_test.go @@ -65,12 +65,18 @@ func Test_UploadAndAnalyze(t *testing.T) { t.Run( "should just create bundle when hash empty", func(t *testing.T) { - mockBundle := bundle.NewBundle(deepcodeMocks.NewMockSnykCodeClient(ctrl), mockInstrumentor, mockErrorReporter, &logger, "", files, []string{}, []string{}) + mockBundle := bundle.NewBundle(deepcodeMocks.NewMockDeepcode(ctrl), mockInstrumentor, mockErrorReporter, &logger, "", files, []string{}, []string{}) mockBundleManager := bundleMocks.NewMockBundleManager(ctrl) mockBundleManager.EXPECT().Create(gomock.Any(), "testRequestId", baseDir, gomock.Any(), map[string]bool{}).Return(mockBundle, nil) mockBundleManager.EXPECT().Upload(gomock.Any(), "testRequestId", mockBundle, files).Return(mockBundle, nil) - codeScanner := codeclient.NewCodeScanner(mockHTTPClient, mockConfig, mockInstrumentor, mockErrorReporter, &logger) + codeScanner := codeclient.NewCodeScanner( + mockConfig, + mockHTTPClient, + codeclient.WithInstrumentor(mockInstrumentor), + codeclient.WithErrorReporter(mockErrorReporter), + codeclient.WithLogger(&logger), + ) response, bundleHash, err := codeScanner.WithBundleManager(mockBundleManager).UploadAndAnalyze(context.Background(), "testRequestId", baseDir, docs, map[string]bool{}) require.NoError(t, err) @@ -81,7 +87,7 @@ func Test_UploadAndAnalyze(t *testing.T) { t.Run( "should retrieve from backend", func(t *testing.T) { - mockBundle := bundle.NewBundle(deepcodeMocks.NewMockSnykCodeClient(ctrl), mockInstrumentor, mockErrorReporter, &logger, "testBundleHash", files, []string{}, []string{}) + mockBundle := bundle.NewBundle(deepcodeMocks.NewMockDeepcode(ctrl), mockInstrumentor, mockErrorReporter, &logger, "testBundleHash", files, []string{}, []string{}) mockBundleManager := bundleMocks.NewMockBundleManager(ctrl) mockBundleManager.EXPECT().Create(gomock.Any(), "b372d1db-b465-4764-99e1-ecedad03b06a", baseDir, gomock.Any(), map[string]bool{}).Return(mockBundle, nil) mockBundleManager.EXPECT().Upload(gomock.Any(), "b372d1db-b465-4764-99e1-ecedad03b06a", mockBundle, files).Return(mockBundle, nil) @@ -90,7 +96,13 @@ func Test_UploadAndAnalyze(t *testing.T) { mockAnalysisOrchestrator.EXPECT().CreateWorkspace(gomock.Any(), "4a72d1db-b465-4764-99e1-ecedad03b06a", "b372d1db-b465-4764-99e1-ecedad03b06a", baseDir, "testBundleHash").Return("c172d1db-b465-4764-99e1-ecedad03b06a", nil) mockAnalysisOrchestrator.EXPECT().RunAnalysis().Return(&sarif.SarifResponse{Status: "COMPLETE"}, nil) - codeScanner := codeclient.NewCodeScanner(mockHTTPClient, mockConfig, mockInstrumentor, mockErrorReporter, &logger) + codeScanner := codeclient.NewCodeScanner( + mockConfig, + mockHTTPClient, + codeclient.WithInstrumentor(mockInstrumentor), + codeclient.WithErrorReporter(mockErrorReporter), + codeclient.WithLogger(&logger), + ) response, bundleHash, err := codeScanner. WithBundleManager(mockBundleManager).