Skip to content

Commit

Permalink
Apply skip-auto-install on NuGet/.Net projects (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
eranturgeman authored Nov 3, 2024
1 parent 8689ef6 commit 10e3822
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 17 deletions.
53 changes: 36 additions & 17 deletions commands/audit/sca/nuget/nuget.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const (
globalPackagesNotFoundErrorMessage = "could not find global packages path at:"
)

// BuildDependencyTree generates a temporary duplicate of the project to execute the 'install' command without impacting the original directory and establishing the JFrog configuration file for Artifactory resolution
// Generates a temporary duplicate of the project to execute the 'install' command without impacting the original directory and establishing the JFrog configuration file for Artifactory resolution
// Additionally, re-loads the project's Solution so the dependencies sources will be identified
func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps []string, err error) {
wd, err := os.Getwd()
Expand All @@ -55,8 +55,28 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.
return
}

// Creating a temporary copy of the project in order to run 'install' command without effecting the original directory + creating the jfrog config for artifactory resolution
tmpWd, err := fileutils.CreateTempDir()
installRequired, err := isInstallRequired(params, sol, params.SkipAutoInstall(), wd)
if err != nil {
return
}

var buildInfo *entities.BuildInfo
if installRequired {
buildInfo, err = restoreInTempDirAndGetBuildInfo(params, wd, exclusionPattern)
} else {
buildInfo, err = sol.BuildInfo("", log.Logger)
}

if err != nil {
return
}
dependencyTree, uniqueDeps = parseNugetDependencyTree(buildInfo)
return
}

func restoreInTempDirAndGetBuildInfo(params utils.AuditParams, wd string, exclusionPattern string) (buildInfo *entities.BuildInfo, err error) {
var tmpWd string
tmpWd, err = fileutils.CreateTempDir()
if err != nil {
err = fmt.Errorf("failed to create a temporary dir: %w", err)
return
Expand All @@ -65,36 +85,35 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.
err = errors.Join(err, fileutils.RemoveTempDir(tmpWd))
}()

// Exclude Visual Studio inner directorty since it is not neccessary for the scan process and may cause race condition.
// Exclude Visual Studio inner directory since it is not necessary for the scan process and may cause race condition.
err = biutils.CopyDir(wd, tmpWd, true, []string{sca.DotVsRepoSuffix})
if err != nil {
err = fmt.Errorf("failed copying project to temp dir: %w", err)
return
}

if isInstallRequired(params, sol) {
log.Info("Dependencies sources were not detected nor 'install' command provided. Running 'restore' command")
sol, err = runDotnetRestoreAndLoadSolution(params, tmpWd, exclusionPattern)
if err != nil {
return
}
}

buildInfo, err := sol.BuildInfo("", log.Logger)
log.Info("Dependencies sources were not detected nor 'install' command provided. Running 'restore' command")
sol, err := runDotnetRestoreAndLoadSolution(params, tmpWd, exclusionPattern)
if err != nil {
return
}
dependencyTree, uniqueDeps = parseNugetDependencyTree(buildInfo)
return
return sol.BuildInfo("", log.Logger)
}

// Verifies whether the execution of an 'install' command is necessary, either because the project isn't installed or because the user has specified an 'install' command
func isInstallRequired(params utils.AuditParams, sol solution.Solution) bool {
func isInstallRequired(params utils.AuditParams, sol solution.Solution, skipAutoInstall bool, curWd string) (bool, error) {
// If the user has specified an 'install' command, we proceed with executing the 'restore' command even if the project is already installed
// Additionally, if dependency sources were not identified during the construction of the Solution struct, the project will necessitate an 'install'
solDependencySourcesExists := len(sol.GetDependenciesSources()) > 0
solProjectsExists := len(sol.GetProjects()) > 0
return len(params.InstallCommandArgs()) > 0 || !solDependencySourcesExists || !solProjectsExists || params.IsCurationCmd()
installRequired := !solDependencySourcesExists || !solProjectsExists || params.IsCurationCmd()

if len(params.InstallCommandArgs()) > 0 {
return true, nil
} else if installRequired && skipAutoInstall {
return false, &biutils.ErrProjectNotInstalled{UninstalledDir: curWd}
}
return installRequired, nil
}

func runDotnetRestoreAndLoadSolution(params utils.AuditParams, tmpWd, exclusionPattern string) (sol solution.Solution, err error) {
Expand Down
75 changes: 75 additions & 0 deletions commands/audit/sca/nuget/nuget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
"os"
"path/filepath"
"strings"
"testing"

"github.com/jfrog/build-info-go/entities"
Expand Down Expand Up @@ -146,3 +147,77 @@ func TestRunDotnetRestoreAndLoadSolution(t *testing.T) {
assert.NotEmpty(t, sol.GetDependenciesSources())
}
}

// 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
successfulTreeBuiltExpected bool
}{
{
name: "nuget single 4.0 - installed | install not required",
testDir: filepath.Join("projects", "package-managers", "nuget", "single4.0"),
successfulTreeBuiltExpected: true,
},
{
name: "nuget single 5.0 - not installed | install required - install command",
testDir: filepath.Join("projects", "package-managers", "nuget", "single5.0"),
installCommand: "nuget restore", // todo test in ci with nuget restore
successfulTreeBuiltExpected: true,
},
{
name: "nuget single 5.0 - not installed | install required - install forbidden",
testDir: filepath.Join("projects", "package-managers", "nuget", "single5.0"),
successfulTreeBuiltExpected: false,
},
{
name: "nuget multi - not installed | install required - install command",
testDir: filepath.Join("projects", "package-managers", "nuget", "multi"),
installCommand: "nuget restore", // todo test in ci with nuget restore
successfulTreeBuiltExpected: true,
},
{
name: "nuget multi - not installed | install required - install forbidden",
testDir: filepath.Join("projects", "package-managers", "nuget", "multi"),
successfulTreeBuiltExpected: false,
},
{
name: "dotnet-single - not installed | install required - install forbidden",
testDir: filepath.Join("projects", "package-managers", "dotnet", "dotnet-single"),
successfulTreeBuiltExpected: false,
},
{
name: "dotnet-multi - not installed | install required - install forbidden",
testDir: filepath.Join("projects", "package-managers", "dotnet", "dotnet-multi"),
successfulTreeBuiltExpected: false,
},
}

for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
// Create and change directory to test workspace
_, cleanUp := sca.CreateTestWorkspace(t, test.testDir)
defer cleanUp()

params := (&xrayUtils2.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, &utils.ErrProjectNotInstalled{}, err)
} else {
assert.NotNil(t, dependencyTrees)
assert.NotNil(t, uniqueDeps)
assert.NoError(t, err)
}
})
}
}

0 comments on commit 10e3822

Please sign in to comment.