Skip to content
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
10 changes: 5 additions & 5 deletions bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ package bundle_test
import (
"context"
"github.com/rs/zerolog"
"github.com/snyk/code-client-go/deepcode"
mocks2 "github.com/snyk/code-client-go/deepcode/mocks"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/snyk/code-client-go/bundle"
"github.com/snyk/code-client-go/deepcode"
deepcodeMocks "github.com/snyk/code-client-go/deepcode/mocks"
"github.com/snyk/code-client-go/observability/mocks"
)

Expand All @@ -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 := mocks2.NewMockSnykCodeClient(ctrl)
mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl)

mockSpan := mocks.NewMockSpan(ctrl)
mockSpan.EXPECT().Context().AnyTimes()
Expand All @@ -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 := mocks2.NewMockSnykCodeClient(ctrl)
mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl)
mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "testBundleHash", map[string]deepcode.BundleFile{
"file": {},
}, []string{}).Return("testBundleHash", []string{}, nil)
Expand All @@ -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 := mocks2.NewMockSnykCodeClient(ctrl)
mockSnykCodeClient := deepcodeMocks.NewMockSnykCodeClient(ctrl)
mockSnykCodeClient.EXPECT().ExtendBundle(gomock.Any(), "testBundleHash", map[string]deepcode.BundleFile{
"another": {},
"file": {},
Expand Down
2 changes: 1 addition & 1 deletion http/config.go → config/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http
package config

// Config defines the configurable options for the HTTP client.
//
Expand Down
File renamed without changes.
141 changes: 134 additions & 7 deletions deepcode/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
package deepcode

import (
"bytes"
"context"
"encoding/json"
"errors"
"github.com/snyk/code-client-go/config"
"github.com/snyk/code-client-go/internal/util/encoding"
"io"
"net/http"
"net/url"
"regexp"
"strconv"

"github.com/rs/zerolog"
Expand Down Expand Up @@ -62,17 +70,21 @@ type BundleResponse struct {
}

type snykCodeClient struct {
httpClient codeClientHTTP.HTTPClient
instrumentor observability.Instrumentor
logger *zerolog.Logger
httpClient codeClientHTTP.HTTPClient
instrumentor observability.Instrumentor
errorReporter observability.ErrorReporter
logger *zerolog.Logger
config config.Config
}

func NewSnykCodeClient(
logger *zerolog.Logger,
httpClient codeClientHTTP.HTTPClient,
instrumentor observability.Instrumentor,
errorReporter observability.ErrorReporter,
Copy link
Contributor

Choose a reason for hiding this comment

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

We should consider using the Function Options Pattern, as the constructor parameters are growing quite a bit :).

I also wonder, if we do that, if we should assume sensible defaults for errorReporter, e.g implementing an error reporter that just logs, an instrumentor that does nothing etc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea, that's very similar to what I did in #23 for the code scanner. This deepcode client is hopefully not going to be here very long so I think I will leave it as is for now and look at doing what you suggested after PR#23 gets merged.

config config.Config,
) *snykCodeClient {
return &snykCodeClient{httpClient, instrumentor, logger}
return &snykCodeClient{httpClient, instrumentor, errorReporter, logger, config}
}

func (s *snykCodeClient) GetFilters(ctx context.Context) (
Expand All @@ -86,7 +98,12 @@ func (s *snykCodeClient) GetFilters(ctx context.Context) (
span := s.instrumentor.StartSpan(ctx, method)
defer s.instrumentor.Finish(span)

responseBody, err := s.httpClient.DoCall(span.Context(), "GET", "/filters", nil)
host, err := s.Host()
if err != nil {
return FiltersResponse{ConfigFiles: nil, Extensions: nil}, err
}

responseBody, err := s.Request(host, http.MethodGet, "/filters", nil)
if err != nil {
return FiltersResponse{ConfigFiles: nil, Extensions: nil}, err
}
Expand All @@ -110,12 +127,17 @@ func (s *snykCodeClient) CreateBundle(
span := s.instrumentor.StartSpan(ctx, method)
defer s.instrumentor.Finish(span)

host, err := s.Host()
if err != nil {
return "", nil, err
}

requestBody, err := json.Marshal(filesToFilehashes)
if err != nil {
return "", nil, err
}

responseBody, err := s.httpClient.DoCall(span.Context(), "POST", "/bundle", requestBody)
responseBody, err := s.Request(host, http.MethodPost, "/bundle", requestBody)
if err != nil {
return "", nil, err
}
Expand Down Expand Up @@ -143,6 +165,11 @@ func (s *snykCodeClient) ExtendBundle(
span := s.instrumentor.StartSpan(ctx, method)
defer s.instrumentor.Finish(span)

host, err := s.Host()
if err != nil {
return "", nil, err
}

requestBody, err := json.Marshal(ExtendBundleRequest{
Files: files,
RemovedFiles: removedFiles,
Expand All @@ -151,11 +178,111 @@ func (s *snykCodeClient) ExtendBundle(
return "", nil, err
}

responseBody, err := s.httpClient.DoCall(span.Context(), "PUT", "/bundle/"+bundleHash, requestBody)
responseBody, err := s.Request(host, http.MethodPut, "/bundle/"+bundleHash, requestBody)
if err != nil {
return "", nil, err
}
var bundleResponse BundleResponse
err = json.Unmarshal(responseBody, &bundleResponse)
return bundleResponse.BundleHash, bundleResponse.MissingFiles, err
}

// This is only exported for tests.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be an option, to just use the same package?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're doing blackbox testing here. Technically I should not test this function but the code I extracted from snyk-ls was testing it and I wanted to keep it to make sure it continue working. I'll maybe think about writing the other tests in a way that tests this function too

func (s *snykCodeClient) Host() (string, error) {
var codeApiRegex = regexp.MustCompile(`^(deeproxy\.)?`)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is not correct - for local code engine and on Fedramp, it is not always deeproxy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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


snykCodeApiUrl := s.config.SnykCodeApi()
if !s.config.IsFedramp() {
return snykCodeApiUrl, nil
}
u, err := url.Parse(snykCodeApiUrl)
if err != nil {
return "", err
}

u.Host = codeApiRegex.ReplaceAllString(u.Host, "api.")

organization := s.config.Organization()
if organization == "" {
return "", errors.New("Organization is required in a fedramp environment")
}

u.Path = "/hidden/orgs/" + organization + "/code"

return u.String(), nil
}

func (s *snykCodeClient) Request(
host string,
method string,
path string,
requestBody []byte,
) ([]byte, error) {
log := s.logger.With().Str("method", "deepcode.Request").Logger()

bodyBuffer, err := s.encodeIfNeeded(method, requestBody)
if err != nil {
return nil, err
}

req, err := http.NewRequest(method, host+path, bodyBuffer)
if err != nil {
return nil, err
}

s.addHeaders(method, req)

response, err := s.httpClient.Do(req)
if err != nil {
return nil, err
}
defer func() {
closeErr := response.Body.Close()
if closeErr != nil {
s.logger.Error().Err(closeErr).Msg("Couldn't close response body in call to Snyk Code")
}
}()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
log.Error().Err(err).Msg("error reading response body")
s.errorReporter.CaptureError(err, observability.ErrorReporterOptions{ErrorDiagnosticPath: req.RequestURI})
return nil, err
}

return responseBody, nil
}

func (s *snykCodeClient) addHeaders(method string, req *http.Request) {
// Setting a chosen org name for the request
org := s.config.Organization()
if org != "" {
req.Header.Set("snyk-org-name", org)
}
// https://www.keycdn.com/blog/http-cache-headers
req.Header.Set("Cache-Control", "private, max-age=0, no-cache")
if s.mustBeEncoded(method) {
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Encoding", "gzip")
} else {
req.Header.Set("Content-Type", "application/json")
}
}

func (s *snykCodeClient) encodeIfNeeded(method string, requestBody []byte) (*bytes.Buffer, error) {
b := new(bytes.Buffer)
mustBeEncoded := s.mustBeEncoded(method)
if mustBeEncoded {
enc := encoding.NewEncoder(b)
_, err := enc.Write(requestBody)
if err != nil {
return nil, err
}
} else {
b = bytes.NewBuffer(requestBody)
}
return b, nil
}

func (s *snykCodeClient) mustBeEncoded(method string) bool {
return method == http.MethodPost || method == http.MethodPut
}
8 changes: 4 additions & 4 deletions deepcode/client_pact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import (
"github.com/pact-foundation/pact-go/dsl"
"github.com/stretchr/testify/assert"

confMocks "github.com/snyk/code-client-go/config/mocks"
"github.com/snyk/code-client-go/deepcode"
codeClientHTTP "github.com/snyk/code-client-go/http"
httpmocks "github.com/snyk/code-client-go/http/mocks"
"github.com/snyk/code-client-go/internal/util"
"github.com/snyk/code-client-go/internal/util/testutil"
)
Expand Down Expand Up @@ -214,18 +214,18 @@ func setupPact(t *testing.T) {

pact.Setup(true)
ctrl := gomock.NewController(t)
config := httpmocks.NewMockConfig(ctrl)
config := confMocks.NewMockConfig(ctrl)
config.EXPECT().IsFedramp().AnyTimes().Return(false)
config.EXPECT().Organization().AnyTimes().Return(orgUUID)
snykCodeApiUrl := fmt.Sprintf("http://localhost:%d", pact.Server.Port)
config.EXPECT().SnykCodeApi().AnyTimes().Return(snykCodeApiUrl)

instrumentor := testutil.NewTestInstrumentor()
errorReporter := testutil.NewTestErrorReporter()
httpClient := codeClientHTTP.NewHTTPClient(newLogger(t), config, func() *http.Client {
httpClient := codeClientHTTP.NewHTTPClient(newLogger(t), func() *http.Client {
return http.DefaultClient
}, instrumentor, errorReporter)
client = deepcode.NewSnykCodeClient(newLogger(t), httpClient, instrumentor)
client = deepcode.NewSnykCodeClient(newLogger(t), httpClient, instrumentor, errorReporter, config)
}

func getPutPostHeaderMatcher() dsl.MapMatcher {
Expand Down
Loading