diff --git a/cli/docs/flags.go b/cli/docs/flags.go index bf9643e7..c9173fb0 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -118,6 +118,7 @@ const ( RequirementsFile = "requirements-file" WorkingDirs = "working-dirs" OutputDir = "output-dir" + SkipAutoInstall = "skip-auto-install" // Unique curation flags CurationOutput = "curation-format" @@ -154,7 +155,7 @@ var commandFlags = map[string][]string{ url, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps, useWrapperAudit, DepType, RequirementsFile, Fail, ExtendedTable, WorkingDirs, ExclusionsAudit, Mvn, Gradle, Npm, Pnpm, Yarn, Go, Nuget, Pip, Pipenv, Poetry, MinSeverity, FixableOnly, ThirdPartyContextualAnalysis, Threads, - Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, SecretValidation, OutputDir, + Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, SecretValidation, OutputDir, SkipAutoInstall, }, CurationAudit: { CurationOutput, WorkingDirs, Threads, RequirementsFile, @@ -229,8 +230,9 @@ var flagsMap = map[string]components.Flag{ "Set to false if you wish to not use the gradle or maven wrapper.", components.WithBoolDefaultValue(true), ), - WorkingDirs: components.NewStringFlag(WorkingDirs, "A comma-separated list of relative working directories, to determine audit targets locations."), - OutputDir: components.NewStringFlag(OutputDir, "Target directory to save partial results to.", components.SetHiddenStrFlag()), + WorkingDirs: components.NewStringFlag(WorkingDirs, "A comma-separated list of relative working directories, to determine audit targets locations."), + OutputDir: components.NewStringFlag(OutputDir, "Target directory to save partial results to.", components.SetHiddenStrFlag()), + SkipAutoInstall: components.NewBoolFlag(SkipAutoInstall, "Set to true to skip auto-install of dependencies in un-built modules. Currently supported for Yarn and NPM only.", components.SetHiddenBoolFlag()), ExclusionsAudit: components.NewStringFlag( Exclusions, "List of exclusions separated by semicolons, utilized to skip sub-projects from undergoing an audit. These exclusions may incorporate the * and ? wildcards.", diff --git a/cli/scancommands.go b/cli/scancommands.go index fcd620e3..a84747cd 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -477,7 +477,8 @@ func CreateAuditCmd(c *components.Context) (*audit.AuditCommand, error) { SetMinSeverityFilter(minSeverity). SetFixableOnly(c.GetBoolFlagValue(flags.FixableOnly)). SetThirdPartyApplicabilityScan(c.GetBoolFlagValue(flags.ThirdPartyContextualAnalysis)). - SetScansResultsOutputDir(scansOutputDir) + SetScansResultsOutputDir(scansOutputDir). + SetSkipAutoInstall(c.GetBoolFlagValue(flags.SkipAutoInstall)) if c.GetStringFlagValue(flags.Watches) != "" { auditCmd.SetWatches(splitByCommaAndTrim(c.GetStringFlagValue(flags.Watches))) diff --git a/commands/audit/sca/npm/npm.go b/commands/audit/sca/npm/npm.go index e596f37d..6cb84df3 100644 --- a/commands/audit/sca/npm/npm.go +++ b/commands/audit/sca/npm/npm.go @@ -3,7 +3,6 @@ package npm import ( "errors" "fmt" - biutils "github.com/jfrog/build-info-go/build/utils" buildinfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/npm" @@ -48,7 +47,7 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTrees []*xrayUtils }() // Calculate npm dependencies - dependenciesMap, err := biutils.CalculateDependenciesMap(npmExecutablePath, currentDir, packageInfo.BuildInfoModuleId(), treeDepsParam, log.Logger) + dependenciesMap, err := biutils.CalculateDependenciesMap(npmExecutablePath, currentDir, packageInfo.BuildInfoModuleId(), treeDepsParam, log.Logger, params.SkipAutoInstall()) if err != nil { log.Info("Used npm version:", npmVersion.GetVersion()) return diff --git a/commands/audit/sca/npm/npm_test.go b/commands/audit/sca/npm/npm_test.go index def8bfe4..93bc9649 100644 --- a/commands/audit/sca/npm/npm_test.go +++ b/commands/audit/sca/npm/npm_test.go @@ -2,18 +2,19 @@ package npm import ( "encoding/json" - "os" - "path/filepath" - "testing" - + bibuildutils "github.com/jfrog/build-info-go/build/utils" + buildinfo "github.com/jfrog/build-info-go/entities" + biutils "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" - - biutils "github.com/jfrog/build-info-go/build/utils" - buildinfo "github.com/jfrog/build-info-go/entities" - "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "strings" + "testing" ) func TestParseNpmDependenciesList(t *testing.T) { @@ -25,7 +26,7 @@ func TestParseNpmDependenciesList(t *testing.T) { var dependencies []buildinfo.Dependency err = json.Unmarshal(dependenciesJson, &dependencies) assert.NoError(t, err) - packageInfo := &biutils.PackageInfo{Name: "npmexmaple", Version: "0.1.0"} + packageInfo := &bibuildutils.PackageInfo{Name: "npmexmaple", Version: "0.1.0"} looseEnvifyJsTokens := []*xrayUtils.GraphNode{{Id: "npm://loose-envify:1.4.0", Nodes: []*xrayUtils.GraphNode{{Id: "npm://js-tokens:4.0.0"}}}} expectedTree := &xrayUtils.GraphNode{ Id: "npm://npmexmaple:0.1.0", @@ -122,3 +123,66 @@ func TestIgnoreScripts(t *testing.T) { _, _, err := BuildDependencyTree(params) assert.NoError(t, err) } + +// This test checks that the tree construction is skipped when the project is not installed and the user prohibited installation +func TestSkipBuildDepTreeWhenInstallForbidden(t *testing.T) { + testCases := []struct { + name string + testDir string + installCommand string + shouldBeInstalled bool + successfulTreeBuiltExpected bool + }{ + { + name: "not installed | install required - install command", + testDir: filepath.Join("projects", "package-managers", "npm", "npm-no-lock"), + installCommand: "npm install", + shouldBeInstalled: false, + successfulTreeBuiltExpected: true, + }, + { + name: "not installed | install required - install forbidden", + testDir: filepath.Join("projects", "package-managers", "npm", "npm-no-lock"), + shouldBeInstalled: false, + successfulTreeBuiltExpected: false, + }, + { + name: "installed | install not required", + testDir: filepath.Join("projects", "package-managers", "npm", "npm-project"), + shouldBeInstalled: true, + successfulTreeBuiltExpected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + dirPath, cleanUp := sca.CreateTestWorkspace(t, test.testDir) + defer cleanUp() + + exists, err := fileutils.IsFileExists(filepath.Join(dirPath, "package-lock.json"), false) + assert.NoError(t, err) + + if !test.shouldBeInstalled && exists { + err = os.Remove(filepath.Join(dirPath, "package-lock.json")) + assert.NoError(t, err) + } + + params := (&utils.AuditBasicParams{}).SetSkipAutoInstall(true) + if test.installCommand != "" { + splitInstallCommand := strings.Split(test.installCommand, " ") + params = params.SetInstallCommandName(splitInstallCommand[0]).SetInstallCommandArgs(splitInstallCommand[1:]) + } + dependencyTrees, uniqueDeps, err := BuildDependencyTree(params) + if !test.successfulTreeBuiltExpected { + assert.Nil(t, dependencyTrees) + assert.Nil(t, uniqueDeps) + assert.Error(t, err) + assert.IsType(t, &biutils.ErrProjectNotInstalled{}, err) + } else { + assert.NotNil(t, dependencyTrees) + assert.NotNil(t, uniqueDeps) + assert.NoError(t, err) + } + }) + } +} diff --git a/commands/audit/sca/yarn/yarn.go b/commands/audit/sca/yarn/yarn.go index c59c4074..94df520e 100644 --- a/commands/audit/sca/yarn/yarn.go +++ b/commands/audit/sca/yarn/yarn.go @@ -3,12 +3,13 @@ package yarn import ( "errors" "fmt" + biutils "github.com/jfrog/build-info-go/utils" "path/filepath" "golang.org/x/exp/maps" "github.com/jfrog/build-info-go/build" - biutils "github.com/jfrog/build-info-go/build/utils" + bibuildutils "github.com/jfrog/build-info-go/build/utils" "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/yarn" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -46,17 +47,17 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTrees []*xrayUtils if err != nil { return } - executablePath, err := biutils.GetYarnExecutable() + executablePath, err := bibuildutils.GetYarnExecutable() if errorutils.CheckError(err) != nil { return } - packageInfo, err := biutils.ReadPackageInfoFromPackageJsonIfExists(currentDir, nil) + packageInfo, err := bibuildutils.ReadPackageInfoFromPackageJsonIfExists(currentDir, nil) if errorutils.CheckError(err) != nil { return } - installRequired, err := isInstallRequired(currentDir, params.InstallCommandArgs()) + installRequired, err := isInstallRequired(currentDir, params.InstallCommandArgs(), params.SkipAutoInstall()) if err != nil { return } @@ -70,7 +71,7 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTrees []*xrayUtils } // Calculate Yarn dependencies - dependenciesMap, root, err := biutils.GetYarnDependencies(executablePath, currentDir, packageInfo, log.Logger) + dependenciesMap, root, err := bibuildutils.GetYarnDependencies(executablePath, currentDir, packageInfo, log.Logger) if err != nil { return } @@ -89,7 +90,7 @@ func configureYarnResolutionServerAndRunInstall(params utils.AuditParams, curWd, return runYarnInstallAccordingToVersion(curWd, yarnExecPath, params.InstallCommandArgs()) } - executableYarnVersion, err := biutils.GetVersion(yarnExecPath, curWd) + executableYarnVersion, err := bibuildutils.GetVersion(yarnExecPath, curWd) if err != nil { return } @@ -136,19 +137,23 @@ func configureYarnResolutionServerAndRunInstall(params utils.AuditParams, curWd, return runYarnInstallAccordingToVersion(curWd, yarnExecPath, params.InstallCommandArgs()) } -func isInstallRequired(currentDir string, installCommandArgs []string) (installRequired bool, err error) { +// We verify the project's installation status by examining the presence of the yarn.lock file and the presence of an installation command provided by the user. +// If install command was provided - we install +// If yarn.lock is missing, we should install unless the user has explicitly disabled auto-install. In this case we return an error +// Notice!: If alterations are made manually in the package.json file, it necessitates a manual update to the yarn.lock file as well. +func isInstallRequired(currentDir string, installCommandArgs []string, skipAutoInstall bool) (installRequired bool, err error) { yarnLockExits, err := fileutils.IsFileExists(filepath.Join(currentDir, yarn.YarnLockFileName), false) if err != nil { err = fmt.Errorf("failed to check the existence of '%s' file: %s", filepath.Join(currentDir, yarn.YarnLockFileName), err.Error()) return } - // We verify the project's installation status by examining the presence of the yarn.lock file and the presence of an installation command provided by the user. - // Notice!: If alterations are made manually in the package.json file, it necessitates a manual update to the yarn.lock file as well. - if len(installCommandArgs) > 0 || !yarnLockExits { - installRequired = true + if len(installCommandArgs) > 0 { + return true, nil + } else if !yarnLockExits && skipAutoInstall { + return false, &biutils.ErrProjectNotInstalled{UninstalledDir: currentDir} } - return + return !yarnLockExits, nil } // Executes the user-defined 'install' command; if absent, defaults to running an 'install' command with specific flags suited to the current yarn version. @@ -162,7 +167,7 @@ func runYarnInstallAccordingToVersion(curWd, yarnExecPath string, installCommand } installCommandArgs = []string{"install"} - executableVersionStr, err := biutils.GetVersion(yarnExecPath, curWd) + executableVersionStr, err := bibuildutils.GetVersion(yarnExecPath, curWd) if err != nil { return } @@ -200,13 +205,13 @@ func runYarnInstallAccordingToVersion(curWd, yarnExecPath string, installCommand } // Parse the dependencies into a Xray dependency tree format -func parseYarnDependenciesMap(dependencies map[string]*biutils.YarnDependency, rootXrayId string) (*xrayUtils.GraphNode, []string) { +func parseYarnDependenciesMap(dependencies map[string]*bibuildutils.YarnDependency, rootXrayId string) (*xrayUtils.GraphNode, []string) { treeMap := make(map[string]xray.DepTreeNode) for _, dependency := range dependencies { xrayDepId := getXrayDependencyId(dependency) var subDeps []string for _, subDepPtr := range dependency.Details.Dependencies { - subDeps = append(subDeps, getXrayDependencyId(dependencies[biutils.GetYarnDependencyKeyFromLocator(subDepPtr.Locator)])) + subDeps = append(subDeps, getXrayDependencyId(dependencies[bibuildutils.GetYarnDependencyKeyFromLocator(subDepPtr.Locator)])) } if len(subDeps) > 0 { treeMap[xrayDepId] = xray.DepTreeNode{Children: subDeps} @@ -216,6 +221,6 @@ func parseYarnDependenciesMap(dependencies map[string]*biutils.YarnDependency, r return graph, maps.Keys(uniqDeps) } -func getXrayDependencyId(yarnDependency *biutils.YarnDependency) string { +func getXrayDependencyId(yarnDependency *bibuildutils.YarnDependency) string { return utils.NpmPackageTypeIdentifier + yarnDependency.Name() + ":" + yarnDependency.Details.Version } diff --git a/commands/audit/sca/yarn/yarn_test.go b/commands/audit/sca/yarn/yarn_test.go index faad4d64..023e93c8 100644 --- a/commands/audit/sca/yarn/yarn_test.go +++ b/commands/audit/sca/yarn/yarn_test.go @@ -1,25 +1,29 @@ package yarn import ( + "errors" "github.com/jfrog/build-info-go/build" - biutils "github.com/jfrog/build-info-go/build/utils" - utils2 "github.com/jfrog/build-info-go/utils" + bibuildutils "github.com/jfrog/build-info-go/build/utils" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" + "os" "path/filepath" + "strings" "testing" ) func TestParseYarnDependenciesList(t *testing.T) { - yarnDependencies := map[string]*biutils.YarnDependency{ - "pack1@npm:1.0.0": {Value: "pack1@npm:1.0.0", Details: biutils.YarnDepDetails{Version: "1.0.0", Dependencies: []biutils.YarnDependencyPointer{{Locator: "pack4@npm:4.0.0"}}}}, - "pack2@npm:2.0.0": {Value: "pack2@npm:2.0.0", Details: biutils.YarnDepDetails{Version: "2.0.0", Dependencies: []biutils.YarnDependencyPointer{{Locator: "pack4@npm:4.0.0"}, {Locator: "pack5@npm:5.0.0"}}}}, - "@jfrog/pack3@npm:3.0.0": {Value: "@jfrog/pack3@npm:3.0.0", Details: biutils.YarnDepDetails{Version: "3.0.0", Dependencies: []biutils.YarnDependencyPointer{{Locator: "pack1@virtual:c192f6b3b32cd5d11a443144e162ec3bc#npm:1.0.0"}, {Locator: "pack2@npm:2.0.0"}}}}, - "pack4@npm:4.0.0": {Value: "pack4@npm:4.0.0", Details: biutils.YarnDepDetails{Version: "4.0.0"}}, - "pack5@npm:5.0.0": {Value: "pack5@npm:5.0.0", Details: biutils.YarnDepDetails{Version: "5.0.0", Dependencies: []biutils.YarnDependencyPointer{{Locator: "pack2@npm:2.0.0"}}}}, + yarnDependencies := map[string]*bibuildutils.YarnDependency{ + "pack1@npm:1.0.0": {Value: "pack1@npm:1.0.0", Details: bibuildutils.YarnDepDetails{Version: "1.0.0", Dependencies: []bibuildutils.YarnDependencyPointer{{Locator: "pack4@npm:4.0.0"}}}}, + "pack2@npm:2.0.0": {Value: "pack2@npm:2.0.0", Details: bibuildutils.YarnDepDetails{Version: "2.0.0", Dependencies: []bibuildutils.YarnDependencyPointer{{Locator: "pack4@npm:4.0.0"}, {Locator: "pack5@npm:5.0.0"}}}}, + "@jfrog/pack3@npm:3.0.0": {Value: "@jfrog/pack3@npm:3.0.0", Details: bibuildutils.YarnDepDetails{Version: "3.0.0", Dependencies: []bibuildutils.YarnDependencyPointer{{Locator: "pack1@virtual:c192f6b3b32cd5d11a443144e162ec3bc#npm:1.0.0"}, {Locator: "pack2@npm:2.0.0"}}}}, + "pack4@npm:4.0.0": {Value: "pack4@npm:4.0.0", Details: bibuildutils.YarnDepDetails{Version: "4.0.0"}}, + "pack5@npm:5.0.0": {Value: "pack5@npm:5.0.0", Details: bibuildutils.YarnDepDetails{Version: "5.0.0", Dependencies: []bibuildutils.YarnDependencyPointer{{Locator: "pack2@npm:2.0.0"}}}}, } rootXrayId := utils.NpmPackageTypeIdentifier + "@jfrog/pack3:3.0.0" @@ -57,8 +61,8 @@ func TestIsInstallRequired(t *testing.T) { tempDirPath, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() yarnProjectPath := filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "yarn", "yarn-project") - assert.NoError(t, utils2.CopyDir(yarnProjectPath, tempDirPath, true, nil)) - installRequired, err := isInstallRequired(tempDirPath, []string{}) + assert.NoError(t, biutils.CopyDir(yarnProjectPath, tempDirPath, true, nil)) + installRequired, err := isInstallRequired(tempDirPath, []string{}, false) assert.NoError(t, err) assert.True(t, installRequired) @@ -66,17 +70,24 @@ func TestIsInstallRequired(t *testing.T) { assert.NoError(t, err) assert.False(t, isTempDirEmpty) - executablePath, err := biutils.GetYarnExecutable() + executablePath, err := bibuildutils.GetYarnExecutable() assert.NoError(t, err) // We provide a user defined 'install' command and expect to get 'true' as an answer - installRequired, err = isInstallRequired(tempDirPath, []string{"yarn", "install"}) + installRequired, err = isInstallRequired(tempDirPath, []string{"yarn", "install"}, false) assert.NoError(t, err) assert.True(t, installRequired) + // We specifically state that we should skip install even if the project is not installed + installRequired, err = isInstallRequired(tempDirPath, []string{}, true) + assert.False(t, installRequired) + assert.Error(t, err) + var projectNotInstalledErr *biutils.ErrProjectNotInstalled + assert.True(t, errors.As(err, &projectNotInstalledErr)) + // We install the project so yarn.lock will be created and expect to get 'false' as an answer assert.NoError(t, build.RunYarnCommand(executablePath, tempDirPath, "install")) - installRequired, err = isInstallRequired(tempDirPath, []string{}) + installRequired, err = isInstallRequired(tempDirPath, []string{}, false) assert.NoError(t, err) assert.False(t, installRequired) } @@ -92,20 +103,104 @@ func executeRunYarnInstallAccordingToVersionAndVerifyInstallation(t *testing.T, tempDirPath, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() yarnProjectPath := filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "yarn", "yarn-project") - assert.NoError(t, utils2.CopyDir(yarnProjectPath, tempDirPath, true, nil)) + assert.NoError(t, biutils.CopyDir(yarnProjectPath, tempDirPath, true, nil)) isTempDirEmpty, err := fileutils.IsDirEmpty(tempDirPath) assert.NoError(t, err) assert.False(t, isTempDirEmpty) - executablePath, err := biutils.GetYarnExecutable() + executablePath, err := bibuildutils.GetYarnExecutable() assert.NoError(t, err) err = runYarnInstallAccordingToVersion(tempDirPath, executablePath, params) assert.NoError(t, err) // Checking the installation worked - we expect to get a 'false' answer when checking whether the project is installed - installRequired, err := isInstallRequired(tempDirPath, []string{}) + installRequired, err := isInstallRequired(tempDirPath, []string{}, false) assert.NoError(t, err) assert.False(t, installRequired) } + +// This test checks that the tree construction is skipped when the project is not installed and the user prohibited installation +func TestSkipBuildDepTreeWhenInstallForbidden(t *testing.T) { + testCases := []struct { + name string + testDir string + installCommand string + shouldBeInstalled bool + successfulTreeBuiltExpected bool + }{ + { + name: "yarn V1 - not installed | install required - install command", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v1"), + installCommand: "yarn install", + shouldBeInstalled: false, + successfulTreeBuiltExpected: true, + }, + { + name: "yarn V1 - not installed | install required - install forbidden", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v1"), + shouldBeInstalled: false, + successfulTreeBuiltExpected: false, + }, + { + name: "yarn V2 - not installed | install required - install forbidden", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v2"), + shouldBeInstalled: false, + successfulTreeBuiltExpected: false, + }, + { + name: "yarn V3 - not installed | install required - install forbidden", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v3"), + shouldBeInstalled: false, + successfulTreeBuiltExpected: false, + }, + { + name: "yarn V1 - installed | install not required", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v1"), + shouldBeInstalled: true, + successfulTreeBuiltExpected: true, + }, + { + name: "yarn V3 - installed | install not required", + testDir: filepath.Join("projects", "package-managers", "yarn", "yarn-v3"), + shouldBeInstalled: true, + successfulTreeBuiltExpected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + // Create and change directory to test workspace + dirPath, cleanUp := sca.CreateTestWorkspace(t, test.testDir) + defer cleanUp() + + expectedLockFilePath := filepath.Join(dirPath, "yarn.lock") + exists, err := fileutils.IsFileExists(expectedLockFilePath, false) + assert.NoError(t, err) + + if !test.shouldBeInstalled && exists { + err = os.Remove(filepath.Join(dirPath, "yarn.lock")) + assert.NoError(t, err) + } + + params := (&utils.AuditBasicParams{}).SetSkipAutoInstall(true) + if test.installCommand != "" { + splitInstallCommand := strings.Split(test.installCommand, " ") + params = params.SetInstallCommandName(splitInstallCommand[0]).SetInstallCommandArgs(splitInstallCommand[1:]) + } + + dependencyTrees, uniqueDeps, err := BuildDependencyTree(params) + if !test.successfulTreeBuiltExpected { + assert.Nil(t, dependencyTrees) + assert.Nil(t, uniqueDeps) + assert.Error(t, err) + assert.IsType(t, &biutils.ErrProjectNotInstalled{}, err) + } else { + assert.NotNil(t, dependencyTrees) + assert.NotNil(t, uniqueDeps) + assert.NoError(t, err) + } + }) + } +} diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 38054d23..847b7732 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/build-info-go/utils/pythonutils" "github.com/jfrog/jfrog-cli-security/commands/audit/sca/conan" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" @@ -76,6 +77,11 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner // Get the dependency tree for the technology in the working directory. treeResult, bdtErr := buildDependencyTree(scan, auditParams) if bdtErr != nil { + var projectNotInstalledErr *biutils.ErrProjectNotInstalled + if errors.As(bdtErr, &projectNotInstalledErr) { + log.Warn(bdtErr.Error()) + continue + } err = errors.Join(err, fmt.Errorf("audit command in '%s' failed:\n%s", scan.Target, bdtErr.Error())) continue } @@ -306,7 +312,7 @@ func getCurationCacheFolderAndLogMsg(params xrayutils.AuditParams, tech techutil return logMessage, curationCacheFolder, err } -func SetResolutionRepoIfExists(params utils.AuditParams, tech techutils.Technology) (serverDetails *config.ServerDetails, err error) { +func SetResolutionRepoInAuditParamsIfExists(params utils.AuditParams, tech techutils.Technology) (serverDetails *config.ServerDetails, err error) { if serverDetails, err = params.ServerDetails(); err != nil { return } @@ -374,13 +380,13 @@ func buildDependencyTree(scan *utils.ScaScanResult, params *AuditParams) (*Depen if err := os.Chdir(scan.Target); err != nil { return nil, errorutils.CheckError(err) } - serverDetails, err := SetResolutionRepoIfExists(params.AuditBasicParams, scan.Technology) + serverDetails, err := SetResolutionRepoInAuditParamsIfExists(params.AuditBasicParams, scan.Technology) if err != nil { return nil, err } treeResult, techErr := GetTechDependencyTree(params.AuditBasicParams, serverDetails, scan.Technology) if techErr != nil { - return nil, fmt.Errorf("failed while building '%s' dependency tree:\n%s", scan.Technology, techErr.Error()) + return nil, fmt.Errorf("failed while building '%s' dependency tree:\n%w", scan.Technology, techErr) } if treeResult.FlatTree == nil || len(treeResult.FlatTree.Nodes) == 0 { return nil, errorutils.CheckErrorf("no dependencies were found. Please try to build your project and re-run the audit command") diff --git a/commands/curation/curationaudit.go b/commands/curation/curationaudit.go index e95b2188..780a317f 100644 --- a/commands/curation/curationaudit.go +++ b/commands/curation/curationaudit.go @@ -369,7 +369,7 @@ func (ca *CurationAuditCommand) getAuditParamsByTech(tech techutils.Technology) func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map[string]*CurationReport) error { params := ca.getAuditParamsByTech(tech) - serverDetails, err := audit.SetResolutionRepoIfExists(params, tech) + serverDetails, err := audit.SetResolutionRepoInAuditParamsIfExists(params, tech) if err != nil { return err } diff --git a/go.mod b/go.mod index 228b11f1..1f82e24c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/beevik/etree v1.4.0 github.com/google/go-github/v56 v56.0.0 github.com/gookit/color v1.5.4 - github.com/jfrog/build-info-go v1.10.1 + github.com/jfrog/build-info-go v1.10.2 github.com/jfrog/froggit-go v1.16.1 github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-apps-config v1.0.1 diff --git a/go.sum b/go.sum index d858d1cc..904f2f20 100644 --- a/go.sum +++ b/go.sum @@ -890,8 +890,8 @@ github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+ github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.10.1 h1:5nLrpFjbV2zuBdmJXW2nybAz5vyu+qDkOtR7v0ehi8s= -github.com/jfrog/build-info-go v1.10.1/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/build-info-go v1.10.2 h1:RCCBsahRNYOm3W7Z9tAL/ixBLzrOzm4mTgI2N6jvqsw= +github.com/jfrog/build-info-go v1.10.2/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/froggit-go v1.16.1 h1:FBIM1qevX/ag9unfmpGzfmZ36D8ulOJ+DPTSFUk3l5U= github.com/jfrog/froggit-go v1.16.1/go.mod h1:TEJSzgiV+3D/GVGE8Y6j46ut1jrBLD1FL6WdMdKwwCE= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= diff --git a/utils/auditbasicparams.go b/utils/auditbasicparams.go index df3a23fd..0c5660fc 100644 --- a/utils/auditbasicparams.go +++ b/utils/auditbasicparams.go @@ -40,6 +40,7 @@ type AuditParams interface { Exclusions() []string SetIsRecursiveScan(isRecursiveScan bool) *AuditBasicParams IsRecursiveScan() bool + SkipAutoInstall() bool } type AuditBasicParams struct { @@ -63,6 +64,7 @@ type AuditBasicParams struct { dependenciesForApplicabilityScan []string exclusions []string isRecursiveScan bool + skipAutoInstall bool } func (abp *AuditBasicParams) DirectDependencies() *[]string { @@ -98,6 +100,11 @@ func (abp *AuditBasicParams) SetUseJas(useJas bool) *AuditBasicParams { return abp } +func (abp *AuditBasicParams) SetSkipAutoInstall(skipAutoInstall bool) *AuditBasicParams { + abp.skipAutoInstall = skipAutoInstall + return abp +} + func (abp *AuditBasicParams) UseJas() bool { return abp.useJas } @@ -253,3 +260,7 @@ func (abp *AuditBasicParams) SetIsRecursiveScan(isRecursiveScan bool) *AuditBasi func (abp *AuditBasicParams) IsRecursiveScan() bool { return abp.isRecursiveScan } + +func (abp *AuditBasicParams) SkipAutoInstall() bool { + return abp.skipAutoInstall +}