diff --git a/application/config/config.go b/application/config/config.go index 752a1c5bd..26e8c28b8 100644 --- a/application/config/config.go +++ b/application/config/config.go @@ -47,10 +47,10 @@ import ( frameworkLogging "github.com/snyk/go-application-framework/pkg/logging" "github.com/snyk/go-application-framework/pkg/runtimeinfo" "github.com/snyk/go-application-framework/pkg/workflow" - "github.com/snyk/snyk-ls/infrastructure/cli/cli_constants" "github.com/snyk/snyk-ls/infrastructure/cli/filename" "github.com/snyk/snyk-ls/internal/concurrency" + gitconfig "github.com/snyk/snyk-ls/internal/git_config" "github.com/snyk/snyk-ls/internal/logging" "github.com/snyk/snyk-ls/internal/storage" "github.com/snyk/snyk-ls/internal/types" @@ -200,6 +200,7 @@ type Config struct { m sync.RWMutex clientProtocolVersion string isOpenBrowserActionEnabled bool + folderAdditionalParameters map[string][]string } func CurrentConfig() *Config { @@ -231,6 +232,7 @@ func IsDevelopment() bool { // New creates a configuration object with default values func New() *Config { c := &Config{} + c.folderAdditionalParameters = make(map[string][]string) c.scrubbingDict = frameworkLogging.ScrubbingDict{} c.logger = getNewScrubbingLogger(c) c.cliSettings = NewCliSettings(c) @@ -1119,3 +1121,26 @@ func (c *Config) SetSnykOpenBrowserActionsEnabled(enable bool) { defer c.m.Unlock() c.isOpenBrowserActionEnabled = enable } + +func (c *Config) FolderConfig(path string) *types.FolderConfig { + var folderConfig *types.FolderConfig + var err error + folderConfig, err = gitconfig.GetOrCreateFolderConfig(path) + if err != nil { + folderConfig = &types.FolderConfig{} + } + c.m.RLock() + addParams, ok := c.folderAdditionalParameters[path] + if ok { + folderConfig.AdditionalParameters = addParams + } + c.m.RUnlock() + return folderConfig +} + +func (c *Config) SetAdditionalParameters(path string, parameters []string) { + c.m.Lock() + defer c.m.Unlock() + + c.folderAdditionalParameters[path] = parameters +} diff --git a/application/server/configuration.go b/application/server/configuration.go index a74489227..b15a34099 100644 --- a/application/server/configuration.go +++ b/application/server/configuration.go @@ -143,6 +143,11 @@ func updateSnykOpenBrowserCodeActions(c *config.Config, settings types.Settings) func updateFolderConfig(c *config.Config, settings types.Settings) { gitconfig.SetBaseBranch(c.Logger(), settings.FolderConfigs) + for _, folderConfig := range settings.FolderConfigs { + if len(folderConfig.AdditionalParameters) > 0 { + c.SetAdditionalParameters(folderConfig.FolderPath, folderConfig.AdditionalParameters) + } + } } func updateAuthenticationMethod(c *config.Config, settings types.Settings) { diff --git a/application/server/configuration_test.go b/application/server/configuration_test.go index 12bbe4e36..77555b4dc 100644 --- a/application/server/configuration_test.go +++ b/application/server/configuration_test.go @@ -33,7 +33,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" - gitconfig "github.com/snyk/snyk-ls/internal/git_config" "github.com/snyk/snyk-ls/internal/types" "github.com/snyk/snyk-ls/application/config" @@ -200,8 +199,9 @@ func Test_UpdateSettings(t *testing.T) { EnableSnykOpenBrowserActions: "true", FolderConfigs: []types.FolderConfig{ { - FolderPath: tempDir1, - BaseBranch: "testBaseBranch1", + FolderPath: tempDir1, + BaseBranch: "testBaseBranch1", + AdditionalParameters: []string{"--file=asdf"}, }, { FolderPath: tempDir2, @@ -210,6 +210,12 @@ func Test_UpdateSettings(t *testing.T) { }, } + err := initTestRepo(t, tempDir1) + assert.NoError(t, err) + + err = initTestRepo(t, tempDir2) + assert.NoError(t, err) + UpdateSettings(c, settings) assert.Equal(t, false, c.IsSnykCodeEnabled()) @@ -235,17 +241,14 @@ func Test_UpdateSettings(t *testing.T) { assert.Equal(t, sampleSettings.SnykCodeApi, c.SnykCodeApi()) assert.Equal(t, true, c.IsSnykOpenBrowserActionEnabled()) - err := initTestRepo(t, tempDir1) - assert.NoError(t, err) - folderConfig1, err := gitconfig.GetOrCreateFolderConfig(tempDir1) - assert.NoError(t, err) + folderConfig1 := c.FolderConfig(tempDir1) assert.NotEmpty(t, folderConfig1.BaseBranch) + assert.Equal(t, settings.FolderConfigs[0].AdditionalParameters[0], + folderConfig1.AdditionalParameters[0]) - err = initTestRepo(t, tempDir2) - assert.NoError(t, err) - folderConfig2, err := gitconfig.GetOrCreateFolderConfig(tempDir2) - assert.NoError(t, err) + folderConfig2 := c.FolderConfig(tempDir2) assert.NotEmpty(t, folderConfig2.BaseBranch) + assert.Empty(t, folderConfig2.AdditionalParameters) assert.Eventually(t, func() bool { return "a fancy token" == c.Token() }, time.Second*5, time.Millisecond) }) diff --git a/application/server/constants.go b/application/server/constants.go index d57fb254b..49fb5f9a9 100644 --- a/application/server/constants.go +++ b/application/server/constants.go @@ -16,4 +16,6 @@ package server -const nodejsGoof = "https://github.com/snyk-labs/nodejs-goof" +import "github.com/snyk/snyk-ls/internal/testutil" + +const nodejsGoof = testutil.NodejsGoof diff --git a/domain/ide/command/folder_handler.go b/domain/ide/command/folder_handler.go index a96e27253..020889fd0 100644 --- a/domain/ide/command/folder_handler.go +++ b/domain/ide/command/folder_handler.go @@ -19,8 +19,8 @@ package command import ( "context" "fmt" + "github.com/snyk/snyk-ls/domain/snyk/persistence" - gitconfig "github.com/snyk/snyk-ls/internal/git_config" noti "github.com/snyk/snyk-ls/internal/notification" "github.com/pkg/errors" @@ -40,15 +40,11 @@ func HandleFolders(ctx context.Context, srv types.Server, notifier noti.Notifier } func sendFolderConfigsNotification(notifier noti.Notifier) { - logger := config.CurrentConfig().Logger().With().Str("method", "HandleFolders").Logger() + c := config.CurrentConfig() ws := workspace.Get() var folderConfigs []types.FolderConfig for _, f := range ws.Folders() { - folderConfig, err := gitconfig.GetOrCreateFolderConfig(f.Path()) - if err != nil { - logger.Warn().Err(err).Msg("error determining folder config") - continue - } + folderConfig := c.FolderConfig(f.Path()) folderConfigs = append(folderConfigs, *folderConfig) } folderConfigsParam := types.FolderConfigsParam{FolderConfigs: folderConfigs} diff --git a/infrastructure/cli/cli.go b/infrastructure/cli/cli.go index 30e5cc5c4..9ad517f01 100644 --- a/infrastructure/cli/cli.go +++ b/infrastructure/cli/cli.go @@ -66,7 +66,7 @@ func (c *SnykCli) Execute(ctx context.Context, cmd []string, workingDir string) c.c.Logger().Debug().Str("method", method).Interface("cmd", cmd).Str("workingDir", workingDir).Msg("calling Snyk CLI") // set deadline to handle CLI hanging when obtaining semaphore - ctx, cancel := context.WithDeadline(ctx, time.Now().Add(c.cliTimeout)) + ctx, cancel := context.WithTimeout(ctx, c.cliTimeout) defer cancel() output, err := c.doExecute(ctx, cmd, workingDir) diff --git a/infrastructure/oss/cli_package_scan.go b/infrastructure/oss/cli_package_scan.go index 3ad3b5908..8f3f2931b 100644 --- a/infrastructure/oss/cli_package_scan.go +++ b/infrastructure/oss/cli_package_scan.go @@ -18,10 +18,11 @@ package oss import ( "context" - "github.com/snyk/snyk-ls/domain/snyk/scanner" "path/filepath" "strings" + "github.com/snyk/snyk-ls/domain/snyk/scanner" + "github.com/snyk/snyk-ls/application/config" "github.com/snyk/snyk-ls/domain/snyk" "github.com/snyk/snyk-ls/infrastructure/oss/parser" @@ -68,7 +69,7 @@ func (cliScanner *CLIScanner) ScanPackages( notCached := cliScanner.updateCachedDependencies(dependencies) if len(notCached) > 0 { - commandFunc := func(_ []string, _ map[string]bool) (deps []string) { + commandFunc := func(_ []string, _ map[string]bool, path string) (deps []string) { for _, d := range notCached { deps = append(deps, d.ArtifactID+"@"+d.Version) } @@ -77,9 +78,10 @@ func (cliScanner *CLIScanner) ScanPackages( "": true, "--all-projects": true, "--dev": true, + "--file": true, } - return cliScanner.prepareScanCommand(deps, blacklist) + return cliScanner.prepareScanCommand(deps, blacklist, path) } _, err := cliScanner.scanInternal(ctx, path, commandFunc) if err != nil { diff --git a/infrastructure/oss/cli_scanner.go b/infrastructure/oss/cli_scanner.go index efff7b4c1..6cc5a405f 100644 --- a/infrastructure/oss/cli_scanner.go +++ b/infrastructure/oss/cli_scanner.go @@ -156,7 +156,7 @@ func (cliScanner *CLIScanner) Scan(ctx context.Context, path string, _ string) ( return cliScanner.scanInternal(ctx, path, cliScanner.prepareScanCommand) } -func (cliScanner *CLIScanner) scanInternal(ctx context.Context, path string, commandFunc func(args []string, parameterBlacklist map[string]bool) []string) ([]snyk.Issue, error) { +func (cliScanner *CLIScanner) scanInternal(ctx context.Context, path string, commandFunc func(args []string, parameterBlacklist map[string]bool, path string) []string) ([]snyk.Issue, error) { method := "cliScanner.Scan" logger := cliScanner.config.Logger().With().Str("method", method).Logger() @@ -203,7 +203,7 @@ func (cliScanner *CLIScanner) scanInternal(ctx context.Context, path string, com cliScanner.runningScans[workDir] = newScan cliScanner.mutex.Unlock() - cmd := commandFunc([]string{workDir}, map[string]bool{"": true}) + cmd := commandFunc([]string{workDir}, map[string]bool{"": true}, workDir) res, scanErr := cliScanner.cli.Execute(ctx, cmd, workDir) noCancellation := ctx.Err() == nil if scanErr != nil { @@ -230,7 +230,8 @@ func (cliScanner *CLIScanner) scanInternal(ctx context.Context, path string, com return issues, nil } -func (cliScanner *CLIScanner) prepareScanCommand(args []string, parameterBlacklist map[string]bool) []string { +func (cliScanner *CLIScanner) prepareScanCommand(args []string, parameterBlacklist map[string]bool, path string) []string { + c := config.CurrentConfig() allProjectsParamAllowed := true allProjectsParam := "--all-projects" @@ -240,7 +241,14 @@ func (cliScanner *CLIScanner) prepareScanCommand(args []string, parameterBlackli }) cmd = append(cmd, args...) cmd = append(cmd, "--json") + additionalParams := cliScanner.config.CliSettings().AdditionalOssParameters + + // append folder parameters if set + folderConfig := c.FolderConfig(path) + additionalParams = append(additionalParams, folderConfig.AdditionalParameters...) + + // now add all additional parameters, skipping blacklisted ones for _, parameter := range additionalParams { p := strings.Split(parameter, "=")[0] if parameterBlacklist[p] { @@ -249,7 +257,9 @@ func (cliScanner *CLIScanner) prepareScanCommand(args []string, parameterBlackli if allProjectsParamBlacklist[p] { allProjectsParamAllowed = false } - cmd = append(cmd, parameter) + if parameter != allProjectsParam { + cmd = append(cmd, parameter) + } } // only append --all-projects, if it's not on the global blacklist diff --git a/infrastructure/oss/oss_test.go b/infrastructure/oss/oss_test.go index 1147821b4..9c0026aa9 100644 --- a/infrastructure/oss/oss_test.go +++ b/infrastructure/oss/oss_test.go @@ -27,6 +27,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/snyk/snyk-ls/application/config" "github.com/snyk/snyk-ls/domain/snyk" @@ -37,6 +38,7 @@ import ( "github.com/snyk/snyk-ls/internal/observability/error_reporting" "github.com/snyk/snyk-ls/internal/observability/performance" "github.com/snyk/snyk-ls/internal/testutil" + "github.com/snyk/snyk-ls/internal/types" ) const testDataPackageJson = "/testdata/package.json" @@ -356,9 +358,18 @@ func Test_prepareScanCommand(t *testing.T) { } c.SetCliSettings(&settings) - cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}) + repo, err := testutil.SetupCustomTestRepo(t, t.TempDir(), testutil.NodejsGoof, "", c.Logger()) + require.NoError(t, err) + folderConfigs := []types.FolderConfig{{ + FolderPath: repo, + AdditionalParameters: []string{"--file=pom.xml"}, + }} - assert.Contains(t, cmd, "--all-projects") + c.SetAdditionalParameters(repo, folderConfigs[0].AdditionalParameters) + + cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}, repo) + + assert.Contains(t, cmd, "--file=pom.xml") assert.Contains(t, cmd, "-d") }) @@ -369,7 +380,7 @@ func Test_prepareScanCommand(t *testing.T) { } c.SetCliSettings(&settings) - cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}) + cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}, "") assert.NotContains(t, cmd, "--all-projects") assert.Contains(t, cmd, "-d") @@ -383,7 +394,7 @@ func Test_prepareScanCommand(t *testing.T) { } c.SetCliSettings(&settings) - cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}) + cmd := scanner.prepareScanCommand([]string{"a"}, map[string]bool{}, "") assert.Contains(t, cmd, "--all-projects") assert.Len(t, cmd, 4) diff --git a/internal/git_config/git_config.go b/internal/git_config/git_config.go index d1203f0af..1572e52b4 100644 --- a/internal/git_config/git_config.go +++ b/internal/git_config/git_config.go @@ -18,6 +18,7 @@ package gitconfig import ( "fmt" + "github.com/rs/zerolog" "github.com/go-git/go-git/v5" @@ -50,6 +51,20 @@ func GetOrCreateFolderConfig(path string) (*types.FolderConfig, error) { return nil, fmt.Errorf("no local branches found") } + baseBranch, err := getBaseBranch(repoConfig, folderSection, localBranches) + if err != nil { + return nil, err + } + + return &types.FolderConfig{ + FolderPath: path, + BaseBranch: baseBranch, + LocalBranches: localBranches, + AdditionalParameters: nil, + }, nil +} + +func getBaseBranch(repoConfig *config2.Config, folderSection *config.Subsection, localBranches []string) (string, error) { // base branch is either overwritten or we return the default branch baseBranch := repoConfig.Init.DefaultBranch if folderSection.HasOption(baseBranchKey) { @@ -62,15 +77,10 @@ func GetOrCreateFolderConfig(path string) (*types.FolderConfig, error) { } else if slices.Contains(localBranches, "master") { baseBranch = "master" } else { - return nil, fmt.Errorf("could not determine base branch") + return "", fmt.Errorf("could not determine base branch") } } - - return &types.FolderConfig{ - FolderPath: path, - BaseBranch: baseBranch, - LocalBranches: localBranches, - }, nil + return baseBranch, nil } func getConfigSection(path string) (*git.Repository, *config2.Config, *config.Config, *config.Subsection, error) { @@ -108,15 +118,19 @@ func getLocalBranches(repository *git.Repository) ([]string, error) { func SetBaseBranch(logger *zerolog.Logger, config []types.FolderConfig) { for _, folderConfig := range config { - repo, repoConfig, _, subsection, err := getConfigSection(folderConfig.FolderPath) - if err != nil { - logger.Error().Err(err).Msg("could not get git config for folder " + folderConfig.FolderPath) - continue - } - subsection.SetOption(baseBranchKey, folderConfig.BaseBranch) - err = repo.Storer.SetConfig(repoConfig) - if err != nil { - logger.Error().Err(err).Msg("could not store base branch configuration for folder " + folderConfig.FolderPath) - } + SetOption(logger, folderConfig.FolderPath, baseBranchKey, folderConfig.BaseBranch) + } +} + +func SetOption(logger *zerolog.Logger, folderPath, key string, value string) { + repo, repoConfig, _, subsection, err := getConfigSection(folderPath) + if err != nil { + logger.Error().Err(err).Msg("could not get git config for folder " + folderPath) + return + } + subsection.SetOption(key, value) + err = repo.Storer.SetConfig(repoConfig) + if err != nil { + logger.Error().Err(err).Msgf("could not store %s=%s configuration for folder %s", key, value, folderPath) } } diff --git a/internal/testutil/test_setup.go b/internal/testutil/test_setup.go index 3f7ff5708..e4ea7b897 100644 --- a/internal/testutil/test_setup.go +++ b/internal/testutil/test_setup.go @@ -34,6 +34,7 @@ import ( const ( integTestEnvVar = "INTEG_TESTS" smokeTestEnvVar = "SMOKE_TESTS" + NodejsGoof = "https://github.com/snyk-labs/nodejs-goof" ) func IntegTest(t *testing.T) *config.Config { diff --git a/licenses/github.com/davecgh/go-spew/spew/LICENSE b/licenses/github.com/davecgh/go-spew/spew/LICENSE new file mode 100644 index 000000000..bc52e96f2 --- /dev/null +++ b/licenses/github.com/davecgh/go-spew/spew/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012-2016 Dave Collins + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/licenses/github.com/pmezard/go-difflib/difflib/LICENSE b/licenses/github.com/pmezard/go-difflib/difflib/LICENSE new file mode 100644 index 000000000..c67dad612 --- /dev/null +++ b/licenses/github.com/pmezard/go-difflib/difflib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Patrick Mezard +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/github.com/stretchr/testify/assert/LICENSE b/licenses/github.com/stretchr/testify/assert/LICENSE new file mode 100644 index 000000000..4b0421cf9 --- /dev/null +++ b/licenses/github.com/stretchr/testify/assert/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.