From 4d2090177a4dffb74e6ca9c51adc5d1e51890487 Mon Sep 17 00:00:00 2001 From: Omer Zidkoni <50792403+omerzi@users.noreply.github.com> Date: Wed, 5 Apr 2023 10:15:32 +0300 Subject: [PATCH 1/9] Upgrade jfrog-cli-core to 2.31.1 (#739) * Improve UI for scan command (#706) * Upgrade go version in go.mod to 1.20 (#732) * Fix lint issues found (#733) * Config transfer - ensure target not older than source (#721) * Update tests environment - nuget and dotnet to version 6 (#734) * Flatten audit graph (#736) * Use gradle-dep-tree with Audit (#719) --------- Co-authored-by: Sara Omari <114062096+sarao1310@users.noreply.github.com> Co-authored-by: Eyal Ben Moshe Co-authored-by: Michael Sverdlov Co-authored-by: Yahav Itzhak --- .github/workflows/analysis.yml | 4 +- .github/workflows/frogbot-scan-and-fix.yml | 2 +- .github/workflows/frogbot-scan-pr.yml | 2 +- .github/workflows/test.yml | 8 +- artifactory/commands/buildinfo/addgit.go | 7 +- artifactory/commands/generic/download.go | 45 ++- artifactory/commands/golang/archive.go | 4 +- artifactory/commands/gradle/gradle.go | 2 +- .../commands/utils/remoteurlchecker.go | 5 +- .../commands/utils/transferconfigbase.go | 13 + .../commands/utils/transferconfigbase_test.go | 58 ++-- artifactory/utils/buildinfoproperties.go | 9 - go.mod | 18 +- go.sum | 29 +- plugins/components/conversionlayer.go | 4 +- utils/config/config.go | 2 +- utils/coreutils/techutils.go | 4 +- utils/mvn/utils.go | 3 +- xray/audit/commonutils.go | 85 ++++++ xray/audit/commonutils_test.go | 132 +++++++++ xray/audit/java/gradle.go | 270 +++++++++++++++--- xray/audit/java/gradle_test.go | 190 ++++++++++-- xray/audit/java/javautils.go | 29 +- xray/audit/java/mvn.go | 7 +- xray/commands/audit/generic/auditmanager.go | 41 ++- xray/commands/audit/generic/generic.go | 2 +- xray/commands/scan/buildscan.go | 6 +- xray/commands/scan/scan.go | 2 +- .../gradle-example-ci-server/build.gradle | 4 - xray/formats/conversion.go | 177 ++++++++---- xray/formats/table.go | 148 +++++++--- xray/utils/resultstable.go | 54 ++-- xray/utils/resultstable_test.go | 2 +- xray/utils/resultwriter.go | 9 +- 34 files changed, 1096 insertions(+), 281 deletions(-) create mode 100644 xray/audit/commonutils_test.go diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index ec281c288..8f0bb8447 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -9,7 +9,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.19.x + go-version: 1.20.x - name: Static Code Analysis uses: dominikh/staticcheck-action@v1 with: @@ -22,7 +22,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.19.x + go-version: 1.20.x - name: Install gosec run: curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - name: Run gosec diff --git a/.github/workflows/frogbot-scan-and-fix.yml b/.github/workflows/frogbot-scan-and-fix.yml index 7ccfa3029..4a4c3f9a5 100644 --- a/.github/workflows/frogbot-scan-and-fix.yml +++ b/.github/workflows/frogbot-scan-and-fix.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.19.x + go-version: 1.20.x - uses: jfrog/frogbot@v2 env: diff --git a/.github/workflows/frogbot-scan-pr.yml b/.github/workflows/frogbot-scan-pr.yml index d19b0f178..d57c83e98 100644 --- a/.github/workflows/frogbot-scan-pr.yml +++ b/.github/workflows/frogbot-scan-pr.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.19.x + go-version: 1.20.x - uses: jfrog/frogbot@v2 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2141133fd..18c9bd8ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,17 +27,17 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.19.x + go-version: 1.20.x - name: Install NuGet uses: nuget/setup-nuget@v1 with: - nuget-version: 5.x + nuget-version: 6.x - name: Install dotnet uses: actions/setup-dotnet@v3 with: - dotnet-version: '3.x' + dotnet-version: '6.x' - name: Go Cache uses: actions/cache@v3 @@ -52,4 +52,4 @@ jobs: - name: Tests run: go test -v github.com/jfrog/jfrog-cli-core/v2/tests -timeout 0 -race - + diff --git a/artifactory/commands/buildinfo/addgit.go b/artifactory/commands/buildinfo/addgit.go index edafb2edc..a14fe27b1 100644 --- a/artifactory/commands/buildinfo/addgit.go +++ b/artifactory/commands/buildinfo/addgit.go @@ -209,7 +209,12 @@ func (config *BuildAddGitCommand) DoCollect(issuesConfig *IssuesConfiguration, l if errorutils.CheckError(err) != nil { return nil, err } - defer os.Chdir(wd) + defer func() { + e := os.Chdir(wd) + if err == nil { + err = errorutils.CheckError(e) + } + }() err = os.Chdir(config.dotGitPath) if errorutils.CheckError(err) != nil { return nil, err diff --git a/artifactory/commands/generic/download.go b/artifactory/commands/generic/download.go index 4b44c5a5c..31ce8a69f 100644 --- a/artifactory/commands/generic/download.go +++ b/artifactory/commands/generic/download.go @@ -62,7 +62,7 @@ func (dc *DownloadCommand) Run() error { return dc.download() } -func (dc *DownloadCommand) download() error { +func (dc *DownloadCommand) download() (err error) { // Init progress bar if needed if dc.progress != nil { dc.progress.InitProgressReaders() @@ -79,11 +79,12 @@ func (dc *DownloadCommand) download() error { return err } if toCollect && !dc.DryRun() { - buildName, err := dc.buildConfiguration.GetBuildName() + var buildName, buildNumber string + buildName, err = dc.buildConfiguration.GetBuildName() if err != nil { return err } - buildNumber, err := dc.buildConfiguration.GetBuildNumber() + buildNumber, err = dc.buildConfiguration.GetBuildNumber() if err != nil { return err } @@ -95,8 +96,9 @@ func (dc *DownloadCommand) download() error { var errorOccurred = false var downloadParamsArray []services.DownloadParams // Create DownloadParams for all File-Spec groups. + var downParams services.DownloadParams for i := 0; i < len(dc.Spec().Files); i++ { - downParams, err := getDownloadParams(dc.Spec().Get(i), dc.configuration) + downParams, err = getDownloadParams(dc.Spec().Get(i), dc.configuration) if err != nil { errorOccurred = true log.Error(err) @@ -116,13 +118,23 @@ func (dc *DownloadCommand) download() error { log.Error(err) } if summary != nil { - defer summary.ArtifactsDetailsReader.Close() + defer func() { + e := summary.ArtifactsDetailsReader.Close() + if err == nil { + err = e + } + }() // If 'detailed summary' was requested, then the reader should not be closed here. // It will be closed after it will be used to generate the summary. if dc.DetailedSummary() { dc.result.SetReader(summary.TransferDetailsReader) } else { - defer summary.TransferDetailsReader.Close() + defer func() { + e := summary.TransferDetailsReader.Close() + if err == nil { + err = e + } + }() } totalDownloaded = summary.TotalSucceeded totalFailed = summary.TotalFailed @@ -145,14 +157,21 @@ func (dc *DownloadCommand) download() error { dc.result.SetFailCount(0) return err } else if dc.SyncDeletesPath() != "" { - absSyncDeletesPath, err := filepath.Abs(dc.SyncDeletesPath()) + var absSyncDeletesPath string + absSyncDeletesPath, err = filepath.Abs(dc.SyncDeletesPath()) if err != nil { return errorutils.CheckError(err) } if _, err = os.Stat(absSyncDeletesPath); err == nil { // Unmarshal the local paths of the downloaded files from the results file reader - tmpRoot, err := createDownloadResultEmptyTmpReflection(summary.TransferDetailsReader) - defer fileutils.RemoveTempDir(tmpRoot) + var tmpRoot string + tmpRoot, err = createDownloadResultEmptyTmpReflection(summary.TransferDetailsReader) + defer func() { + e := fileutils.RemoveTempDir(tmpRoot) + if err == nil { + err = e + } + }() if err != nil { return err } @@ -169,11 +188,12 @@ func (dc *DownloadCommand) download() error { // Build Info if toCollect { - buildName, err := dc.buildConfiguration.GetBuildName() + var buildName, buildNumber string + buildName, err = dc.buildConfiguration.GetBuildName() if err != nil { return err } - buildNumber, err := dc.buildConfiguration.GetBuildNumber() + buildNumber, err = dc.buildConfiguration.GetBuildNumber() if err != nil { return err } @@ -296,6 +316,9 @@ func createLegalPath(root, path string) string { func createSyncDeletesWalkFunction(tempRoot string) gofrog.WalkFunc { return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } // Convert path to absolute path path, err = filepath.Abs(path) if errorutils.CheckError(err) != nil { diff --git a/artifactory/commands/golang/archive.go b/artifactory/commands/golang/archive.go index 787942931..e9c7ddabd 100644 --- a/artifactory/commands/golang/archive.go +++ b/artifactory/commands/golang/archive.go @@ -99,6 +99,9 @@ func archiveProject(writer io.Writer, dir, mod, version string) error { var files []File err := filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } relPath, err := filepath.Rel(dir, filePath) if err != nil { return err @@ -131,7 +134,6 @@ func archiveProject(writer io.Writer, dir, mod, version string) error { if goModInfo, err := os.Lstat(filepath.Join(filePath, "go.mod")); err == nil && !goModInfo.IsDir() { return filepath.SkipDir } - return nil } if info.Mode().IsRegular() { if !isVendoredPackage(slashPath) { diff --git a/artifactory/commands/gradle/gradle.go b/artifactory/commands/gradle/gradle.go index ba8eb7a6e..2964854c2 100644 --- a/artifactory/commands/gradle/gradle.go +++ b/artifactory/commands/gradle/gradle.go @@ -56,7 +56,7 @@ func (gc *GradleCommand) SetServerDetails(serverDetails *config.ServerDetails) * func (gc *GradleCommand) init() (vConfig *viper.Viper, err error) { // Read config - vConfig, err = utils.ReadGradleConfig(gc.configPath, nil) + vConfig, err = utils.ReadConfigFile(gc.configPath, utils.YAML) if err != nil { return } diff --git a/artifactory/commands/utils/remoteurlchecker.go b/artifactory/commands/utils/remoteurlchecker.go index d48c2d142..28faa19e0 100644 --- a/artifactory/commands/utils/remoteurlchecker.go +++ b/artifactory/commands/utils/remoteurlchecker.go @@ -18,10 +18,7 @@ import ( type RemoteUrlCheckStatus string const ( - longPropertyCheckName = "Remote repositories URL connectivity" - success RemoteUrlCheckStatus = "SUCCESS" - inProgress RemoteUrlCheckStatus = "IN_PROGRESS" - + longPropertyCheckName = "Remote repositories URL connectivity" remoteUrlCheckPollingTimeout = 30 * time.Minute remoteUrlCheckPollingInterval = 5 * time.Second remoteUrlCheckRetries = 3 diff --git a/artifactory/commands/utils/transferconfigbase.go b/artifactory/commands/utils/transferconfigbase.go index 9b9fb8286..7a26c4219 100644 --- a/artifactory/commands/utils/transferconfigbase.go +++ b/artifactory/commands/utils/transferconfigbase.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -73,10 +74,22 @@ func (tcb *TransferConfigBase) ValidateMinVersionAndDifferentServers() (string, if err != nil { return "", err } + targetArtifactoryVersion, err := tcb.TargetArtifactoryManager.GetVersion() + if err != nil { + return "", err + } + + // Validate minimal Artifactory version in the source server err = coreutils.ValidateMinimumVersion(coreutils.Artifactory, sourceArtifactoryVersion, minTransferConfigArtifactoryVersion) if err != nil { return "", err } + + // Validate that the target Artifactory server version is >= than the source Artifactory server version + if !version.NewVersion(targetArtifactoryVersion).AtLeast(sourceArtifactoryVersion) { + return "", errorutils.CheckErrorf("The source Artifactory version (%s) can't be higher than the target Artifactory version (%s).", sourceArtifactoryVersion, targetArtifactoryVersion) + } + // Avoid exporting and importing to the same server log.Info("Verifying source and target servers are different...") if tcb.SourceServerDetails.GetArtifactoryUrl() == tcb.TargetServerDetails.GetArtifactoryUrl() { diff --git a/artifactory/commands/utils/transferconfigbase_test.go b/artifactory/commands/utils/transferconfigbase_test.go index f9adfbf1b..9b7eb1af0 100644 --- a/artifactory/commands/utils/transferconfigbase_test.go +++ b/artifactory/commands/utils/transferconfigbase_test.go @@ -99,31 +99,55 @@ func TestIsDefaultCredentialsLocked(t *testing.T) { assert.Equal(t, 0, unlockCounter) } +var validateMinVersionAndDifferentServersCases = []struct { + testName string + sourceVersion string + targetVersion string + expectedError string +}{ + {testName: "Same version", sourceVersion: minTransferConfigArtifactoryVersion, targetVersion: minTransferConfigArtifactoryVersion, expectedError: ""}, + {testName: "Different versions", sourceVersion: "7.0.0", targetVersion: "7.0.1", expectedError: ""}, + {testName: "Low Artifactory version", sourceVersion: "6.0.0", targetVersion: "7.0.0", expectedError: "while this operation requires version"}, + {testName: "Source newer than target", sourceVersion: "7.0.1", targetVersion: "7.0.0", expectedError: "can't be higher than the target Artifactory version"}, +} + func TestValidateMinVersionAndDifferentServers(t *testing.T) { - var rtVersion string + var sourceRtVersion, targetRtVersion string // Create transfer config command - testServer, serverDetails, _ := commonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { - content, err := json.Marshal(VersionResponse{Version: rtVersion}) + sourceTestServer, sourceServerDetails, _ := commonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, _ *http.Request) { + content, err := json.Marshal(VersionResponse{Version: sourceRtVersion}) assert.NoError(t, err) _, err = w.Write(content) assert.NoError(t, err) }) - defer testServer.Close() - - // Test low Artifactory version - rtVersion = "6.0.0" - _, err := createTransferConfigBase(t, serverDetails, serverDetails).ValidateMinVersionAndDifferentServers() - assert.ErrorContains(t, err, "while this operation requires version") + defer sourceTestServer.Close() + targetTestServer, targetServerDetails, _ := commonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, _ *http.Request) { + content, err := json.Marshal(VersionResponse{Version: targetRtVersion}) + assert.NoError(t, err) + _, err = w.Write(content) + assert.NoError(t, err) + }) + defer targetTestServer.Close() - // Test same source and target Artifactory servers - rtVersion = minTransferConfigArtifactoryVersion - _, err = createTransferConfigBase(t, serverDetails, serverDetails).ValidateMinVersionAndDifferentServers() - assert.ErrorContains(t, err, "The source and target Artifactory servers are identical, but should be different.") + for _, testCase := range validateMinVersionAndDifferentServersCases { + t.Run(testCase.testName, func(t *testing.T) { + sourceRtVersion = testCase.sourceVersion + targetRtVersion = testCase.targetVersion + actualSourceVersion, err := createTransferConfigBase(t, sourceServerDetails, targetServerDetails).ValidateMinVersionAndDifferentServers() + if testCase.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, testCase.sourceVersion, actualSourceVersion) + } else { + assert.ErrorContains(t, err, testCase.expectedError) + } + }) + } - // Positive test - actualVersion, err := createTransferConfigBase(t, serverDetails, &config.ServerDetails{ArtifactoryUrl: "some-different-url"}).ValidateMinVersionAndDifferentServers() - assert.NoError(t, err) - assert.Equal(t, rtVersion, actualVersion) + t.Run("Same source and target servers", func(t *testing.T) { + sourceRtVersion = minTransferConfigArtifactoryVersion + _, err := createTransferConfigBase(t, sourceServerDetails, sourceServerDetails).ValidateMinVersionAndDifferentServers() + assert.ErrorContains(t, err, "The source and target Artifactory servers are identical, but should be different.") + }) } func TestGetSelectedRepositories(t *testing.T) { diff --git a/artifactory/utils/buildinfoproperties.go b/artifactory/utils/buildinfoproperties.go index 877588f54..7bfa73df2 100644 --- a/artifactory/utils/buildinfoproperties.go +++ b/artifactory/utils/buildinfoproperties.go @@ -155,15 +155,6 @@ func ReadConfigFile(configPath string, configType ConfigType) (config *viper.Vip return config, errorutils.CheckError(err) } -func ReadGradleConfig(path string, gradleConfigParams map[string]any) (config *viper.Viper, err error) { - if path == "" { - config = createDefaultConfigWithParams(YAML, Gradle.String(), gradleConfigParams) - } else { - config, err = ReadConfigFile(path, YAML) - } - return -} - func ReadMavenConfig(path string, mvnProps map[string]any) (config *viper.Viper, err error) { if path == "" { config = createDefaultConfigWithParams(YAML, Maven.String(), mvnProps) diff --git a/go.mod b/go.mod index c0e1e855f..e7a777977 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jfrog/jfrog-cli-core/v2 -go 1.19 +go 1.20 require ( github.com/buger/jsonparser v1.1.1 @@ -10,9 +10,9 @@ require ( github.com/google/uuid v1.3.0 github.com/gookit/color v1.5.3 github.com/jedib0t/go-pretty/v6 v6.4.6 - github.com/jfrog/build-info-go v1.9.0 + github.com/jfrog/build-info-go v1.9.1 github.com/jfrog/gofrog v1.2.5 - github.com/jfrog/jfrog-client-go v1.28.0 + github.com/jfrog/jfrog-client-go v1.28.1 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/owenrumney/go-sarif/v2 v2.1.3 @@ -33,9 +33,9 @@ require github.com/c-bata/go-prompt v0.2.5 // Should not be updated to 0.2.6 due require ( github.com/BurntSushi/toml v1.2.1 // indirect - github.com/CycloneDX/cyclonedx-go v0.7.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.7.1 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230331115716-d34776aa93ec // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.4 // indirect @@ -56,14 +56,14 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.11.4 // indirect - github.com/klauspost/cpuid/v2 v2.0.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect - github.com/minio/sha256-simd v1.0.1-0.20210617151322-99e45fae3395 // indirect + github.com/minio/sha256-simd v1.0.1-0.20230222114820-6096f891a77b // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect @@ -92,8 +92,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.26.1-0.20230126120919-2cca98d435ec +//replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230405060150-93ab1ed18406 -// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230316095417-a9f6b73206d7 +//replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230403064815-ea83b399ac8e // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.2.5-0.20221107113836-a4c9225c690e diff --git a/go.sum b/go.sum index ff1dd9f4b..5b55b9bd5 100644 --- a/go.sum +++ b/go.sum @@ -40,12 +40,13 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.7.0 h1:jNxp8hL7UpcvPDFXjY+Y1ibFtsW+e5zyF9QoSmhK/zg= -github.com/CycloneDX/cyclonedx-go v0.7.0/go.mod h1:W5Z9w8pTTL+t+yG3PCiFRGlr8PUlE0pGWzKSJbsyXkg= +github.com/CycloneDX/cyclonedx-go v0.7.1 h1:5w1SxjGm9MTMNTuRbEPyw21ObdbaagTWF/KfF0qHTRE= +github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230331115716-d34776aa93ec h1:eQusauqzE1cAFR5hGnwkuSmFxKoy3+j9/cVaDeYfjjs= +github.com/ProtonMail/go-crypto v0.0.0-20230331115716-d34776aa93ec/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -197,12 +198,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jfrog/build-info-go v1.9.0 h1:gLxBfp4C6pVz+bKTmsqwFGZAueVMuzGw+/M9HZgtGG4= -github.com/jfrog/build-info-go v1.9.0/go.mod h1:dQ8OKddrbgtO3jK9uLYoqmRGNEjuDuNXV0bSRdpeTCI= +github.com/jfrog/build-info-go v1.9.1 h1:PxLQ9aBPm2J3WKTkaxU0YmfVCdjHPbIM/WKmrV6686E= +github.com/jfrog/build-info-go v1.9.1/go.mod h1:n0qDS24PIUH/Dag/lwzWPcMXalu+c3mahjkRrDteKnA= github.com/jfrog/gofrog v1.2.5 h1:jCgJC0iGQ8bU7jCC+YEFJTNINyngApIrhd8BjZAVRIE= github.com/jfrog/gofrog v1.2.5/go.mod h1:o00tSRff6IapTgaCMuX1Cs9MH08Y1JqnsKgRtx91Gc4= -github.com/jfrog/jfrog-client-go v1.28.0 h1:PZzcoZZESgSWStd7WK71hpR4LxF1ih89r4XWzZr6Ng0= -github.com/jfrog/jfrog-client-go v1.28.0/go.mod h1:tBaVE+j1Dgo3BcGGBuFm0jbOmjNTaZadwo6jvFiv89Y= +github.com/jfrog/jfrog-client-go v1.28.1 h1:sBPtMg4UGkQ31CDJwFImIWwy6jEP/uarLO4F6y18Jis= +github.com/jfrog/jfrog-client-go v1.28.1/go.mod h1:XJhlPfi6iayIVc2SQ/RbztDQOnbnNatsUSQr7wbJ8Ag= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -212,8 +213,8 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= -github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -247,8 +248,8 @@ github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/minio/sha256-simd v1.0.1-0.20210617151322-99e45fae3395 h1:GpZ9VB5YQdXbVvgCeyqzBPYijxEMehMhax1fUpCuVSc= -github.com/minio/sha256-simd v1.0.1-0.20210617151322-99e45fae3395/go.mod h1:f+LTnn56dRz2YGVXAZIW3myTjkbJhfyRDELQpWRHXto= +github.com/minio/sha256-simd v1.0.1-0.20230222114820-6096f891a77b h1:kr87H4ULRbe6LQNF5f3A+nGY8TQLgckmdG9BLJ/QB18= +github.com/minio/sha256-simd v1.0.1-0.20230222114820-6096f891a77b/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= @@ -279,7 +280,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -392,6 +393,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -453,6 +455,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -504,6 +507,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -587,6 +591,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/plugins/components/conversionlayer.go b/plugins/components/conversionlayer.go index cbd913fde..9b1926e3e 100644 --- a/plugins/components/conversionlayer.go +++ b/plugins/components/conversionlayer.go @@ -85,14 +85,14 @@ func createEnvVarsSummary(cmd Command) string { if i > 0 { summary += "\n" } - summary = "\t" + env.Name + "\n" + summary += "\t" + env.Name + "\n" if env.Default != "" { summary += "\t\t[Default: " + env.Default + "]\n" } summary += "\t\t" + env.Description envVarsSummary = append(envVarsSummary, summary) } - return strings.Join(envVarsSummary[:], "\n\n") + return strings.Join(envVarsSummary[:], "\n") } func convertFlags(cmd Command) ([]cli.Flag, error) { diff --git a/utils/config/config.go b/utils/config/config.go index 3f9c3fefc..9370d2eaa 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -589,7 +589,7 @@ type MissionControlDetails struct { } func (serverDetails *ServerDetails) IsEmpty() bool { - return len(serverDetails.ServerId) == 0 + return len(serverDetails.ServerId) == 0 && serverDetails.Url == "" } func (serverDetails *ServerDetails) SetUser(username string) { diff --git a/utils/coreutils/techutils.go b/utils/coreutils/techutils.go index 3620902c0..8ea6bcf81 100644 --- a/utils/coreutils/techutils.go +++ b/utils/coreutils/techutils.go @@ -58,9 +58,9 @@ var technologiesData = map[Technology]TechData{ execCommand: "mvn", }, Gradle: { - indicators: []string{".gradle"}, + indicators: []string{".gradle", ".gradle.kts"}, ciSetupSupport: true, - packageDescriptor: "build.gradle", + packageDescriptor: "build.gradle, build.gradle.kts", }, Npm: { indicators: []string{"package.json", "package-lock.json", "npm-shrinkwrap.json"}, diff --git a/utils/mvn/utils.go b/utils/mvn/utils.go index ff69f6180..303f4cfc0 100644 --- a/utils/mvn/utils.go +++ b/utils/mvn/utils.go @@ -14,7 +14,8 @@ import ( "github.com/spf13/viper" ) -func RunMvn(vConfig *viper.Viper, buildArtifactsDetailsFile string, buildConf *utils.BuildConfiguration, goals []string, threads int, insecureTls, disableDeploy bool) error { +func RunMvn(vConfig *viper.Viper, buildArtifactsDetailsFile string, buildConf *utils.BuildConfiguration, + goals []string, threads int, insecureTls, disableDeploy bool) error { buildInfoService := utils.CreateBuildInfoService() buildName, err := buildConf.GetBuildName() if err != nil { diff --git a/xray/audit/commonutils.go b/xray/audit/commonutils.go index f568d111c..91548744e 100644 --- a/xray/audit/commonutils.go +++ b/xray/audit/commonutils.go @@ -134,3 +134,88 @@ func GetExecutableVersion(executable string) (version string, err error) { log.Debug(fmt.Sprintf("Used %q version: %s", executable, version)) return } + +// BuildImpactPathsForScanResponse builds the full impact paths for each vulnerability found in the scanResult argument, using the dependencyTrees argument. +// Returns the updated services.ScanResponse slice. +func BuildImpactPathsForScanResponse(scanResult []services.ScanResponse, dependencyTrees []*services.GraphNode) []services.ScanResponse { + for _, result := range scanResult { + if len(result.Vulnerabilities) > 0 { + buildVulnerabilitiesImpactPaths(result.Vulnerabilities, dependencyTrees) + } + if len(result.Violations) > 0 { + buildViolationsImpactPaths(result.Violations, dependencyTrees) + } + if len(result.Licenses) > 0 { + buildLicensesImpactPaths(result.Licenses, dependencyTrees) + } + } + return scanResult +} + +// Initialize map of issues to their components with empty impact paths +func fillImpactPathsMapWithIssues(issuesImpactPathsMap map[string]*services.Component, components map[string]services.Component) { + for dependencyName := range components { + emptyPathsComponent := &services.Component{ + ImpactPaths: [][]services.ImpactPathNode{}, + FixedVersions: components[dependencyName].FixedVersions, + Cpes: components[dependencyName].Cpes, + } + issuesImpactPathsMap[dependencyName] = emptyPathsComponent + } +} + +// Set the impact paths for each issue in the map +func buildImpactPaths(issuesImpactPathsMap map[string]*services.Component, dependencyTrees []*services.GraphNode) { + for _, dependency := range dependencyTrees { + setPathsForIssues(dependency, issuesImpactPathsMap, []services.ImpactPathNode{}) + } +} + +func buildVulnerabilitiesImpactPaths(vulnerabilities []services.Vulnerability, dependencyTrees []*services.GraphNode) { + issuesMap := make(map[string]*services.Component) + for _, vulnerability := range vulnerabilities { + fillImpactPathsMapWithIssues(issuesMap, vulnerability.Components) + } + buildImpactPaths(issuesMap, dependencyTrees) + for i := range vulnerabilities { + updateComponentsWithImpactPaths(vulnerabilities[i].Components, issuesMap) + } +} + +func buildViolationsImpactPaths(violations []services.Violation, dependencyTrees []*services.GraphNode) { + issuesMap := make(map[string]*services.Component) + for _, violation := range violations { + fillImpactPathsMapWithIssues(issuesMap, violation.Components) + } + buildImpactPaths(issuesMap, dependencyTrees) + for i := range violations { + updateComponentsWithImpactPaths(violations[i].Components, issuesMap) + } +} + +func buildLicensesImpactPaths(licenses []services.License, dependencyTrees []*services.GraphNode) { + issuesMap := make(map[string]*services.Component) + for _, license := range licenses { + fillImpactPathsMapWithIssues(issuesMap, license.Components) + } + buildImpactPaths(issuesMap, dependencyTrees) + for i := range licenses { + updateComponentsWithImpactPaths(licenses[i].Components, issuesMap) + } +} + +func updateComponentsWithImpactPaths(components map[string]services.Component, issuesMap map[string]*services.Component) { + for dependencyName := range components { + components[dependencyName] = *issuesMap[dependencyName] + } +} + +func setPathsForIssues(dependency *services.GraphNode, issuesImpactPathsMap map[string]*services.Component, pathFromRoot []services.ImpactPathNode) { + pathFromRoot = append(pathFromRoot, services.ImpactPathNode{ComponentId: dependency.Id}) + if _, exists := issuesImpactPathsMap[dependency.Id]; exists { + issuesImpactPathsMap[dependency.Id].ImpactPaths = append(issuesImpactPathsMap[dependency.Id].ImpactPaths, pathFromRoot) + } + for _, depChild := range dependency.Nodes { + setPathsForIssues(depChild, issuesImpactPathsMap, pathFromRoot) + } +} diff --git a/xray/audit/commonutils_test.go b/xray/audit/commonutils_test.go new file mode 100644 index 000000000..9d42b5d84 --- /dev/null +++ b/xray/audit/commonutils_test.go @@ -0,0 +1,132 @@ +package audit + +import ( + "github.com/jfrog/jfrog-client-go/xray/services" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSetPathsForIssues(t *testing.T) { + // Create a test dependency tree + rootNode := &services.GraphNode{Id: "root"} + childNode1 := &services.GraphNode{Id: "child1"} + childNode2 := &services.GraphNode{Id: "child2"} + childNode3 := &services.GraphNode{Id: "child3"} + childNode4 := &services.GraphNode{Id: "child4"} + childNode5 := &services.GraphNode{Id: "child5"} + rootNode.Nodes = []*services.GraphNode{childNode1, childNode2, childNode3} + childNode2.Nodes = []*services.GraphNode{childNode4} + childNode3.Nodes = []*services.GraphNode{childNode5} + + // Create a test issues map + issuesMap := make(map[string]*services.Component) + issuesMap["child1"] = &services.Component{ImpactPaths: [][]services.ImpactPathNode{}} + issuesMap["child4"] = &services.Component{ImpactPaths: [][]services.ImpactPathNode{}} + issuesMap["child5"] = &services.Component{ImpactPaths: [][]services.ImpactPathNode{}} + + // Call setPathsForIssues with the test data + setPathsForIssues(rootNode, issuesMap, []services.ImpactPathNode{}) + + // Check the results + assert.Equal(t, issuesMap["child1"].ImpactPaths[0][0].ComponentId, "root") + assert.Equal(t, issuesMap["child1"].ImpactPaths[0][1].ComponentId, "child1") + + assert.Equal(t, issuesMap["child4"].ImpactPaths[0][0].ComponentId, "root") + assert.Equal(t, issuesMap["child4"].ImpactPaths[0][1].ComponentId, "child2") + assert.Equal(t, issuesMap["child4"].ImpactPaths[0][2].ComponentId, "child4") + + assert.Equal(t, issuesMap["child5"].ImpactPaths[0][0].ComponentId, "root") + assert.Equal(t, issuesMap["child5"].ImpactPaths[0][1].ComponentId, "child3") + assert.Equal(t, issuesMap["child5"].ImpactPaths[0][2].ComponentId, "child5") +} + +func TestUpdateVulnerableComponent(t *testing.T) { + // Create test data + components := map[string]services.Component{ + "dependency1": { + FixedVersions: []string{"1.0.0"}, + ImpactPaths: [][]services.ImpactPathNode{}, + }, + } + dependencyName := "dependency1" + issuesMap := map[string]*services.Component{ + dependencyName: { + FixedVersions: []string{"1.0.0"}, + ImpactPaths: [][]services.ImpactPathNode{ + {{ComponentId: "dependency2"}}, + }, + }, + } + + updateComponentsWithImpactPaths(components, issuesMap) + + // Check the result + expected := services.Component{ + FixedVersions: []string{"1.0.0"}, + ImpactPaths: issuesMap[dependencyName].ImpactPaths, + } + assert.Equal(t, expected, components[dependencyName]) +} + +func TestBuildImpactPaths(t *testing.T) { + // create sample scan result and dependency trees + scanResult := []services.ScanResponse{ + { + Vulnerabilities: []services.Vulnerability{ + { + Components: map[string]services.Component{ + "dep1": { + FixedVersions: []string{"1.2.3"}, + Cpes: []string{"cpe:/o:vendor:product:1.2.3"}, + }, + }, + }, + }, + Violations: []services.Violation{ + { + Components: map[string]services.Component{ + "dep2": { + FixedVersions: []string{"4.5.6"}, + Cpes: []string{"cpe:/o:vendor:product:4.5.6"}, + }, + }, + }, + }, + Licenses: []services.License{ + { + Components: map[string]services.Component{ + "dep3": { + FixedVersions: []string{"7.8.9"}, + Cpes: []string{"cpe:/o:vendor:product:7.8.9"}, + }, + }, + }, + }, + }, + } + dependencyTrees := []*services.GraphNode{ + { + Id: "dep1", + Nodes: []*services.GraphNode{ + { + Id: "dep2", + Nodes: []*services.GraphNode{ + { + Id: "dep3", + Nodes: []*services.GraphNode{}, + }, + }, + }, + }, + }, + } + + scanResult = BuildImpactPathsForScanResponse(scanResult, dependencyTrees) + // assert that the components were updated with impact paths + expectedImpactPaths := [][]services.ImpactPathNode{{{ComponentId: "dep1"}}} + assert.Equal(t, expectedImpactPaths, scanResult[0].Vulnerabilities[0].Components["dep1"].ImpactPaths) + expectedImpactPaths = [][]services.ImpactPathNode{{{ComponentId: "dep1"}, {ComponentId: "dep2"}}} + assert.Equal(t, expectedImpactPaths, scanResult[0].Violations[0].Components["dep2"].ImpactPaths) + expectedImpactPaths = [][]services.ImpactPathNode{{{ComponentId: "dep1"}, {ComponentId: "dep2"}, {ComponentId: "dep3"}}} + assert.Equal(t, expectedImpactPaths, scanResult[0].Licenses[0].Components["dep3"].ImpactPaths) +} diff --git a/xray/audit/java/gradle.go b/xray/audit/java/gradle.go index 203a375af..145cebbe4 100644 --- a/xray/audit/java/gradle.go +++ b/xray/audit/java/gradle.go @@ -1,65 +1,269 @@ package java import ( + "encoding/base64" + "encoding/json" "fmt" + "github.com/jfrog/build-info-go/build" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - gradleutils "github.com/jfrog/jfrog-cli-core/v2/utils/gradle" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" + "os" + "os/exec" + "path/filepath" + "strings" ) -const gradlew = "gradlew" +const ( + remoteDepTreePath = "artifactory/oss-releases" + gradlew = "gradlew" + depTreeInitFile = "gradledeptree.init" + depTreeOutputFile = "gradledeptree.out" + depTreeInitScript = `initscript { + repositories { %s + mavenCentral() + } + dependencies { + classpath 'com.jfrog:gradle-dep-tree:2.2.0' + } +} -func buildGradleDependencyTree(excludeTestDeps, useWrapper, ignoreConfigFile bool, gradleConfigParams map[string]any) (dependencyTree []*services.GraphNode, err error) { - buildConfiguration, cleanBuild := createBuildConfiguration("audit-gradle") - defer cleanBuild(err) +allprojects { + repositories { %s + } + apply plugin: com.jfrog.GradleDepTree +}` + artifactoryRepository = ` + maven { + url "%s/%s" + credentials { + username = '%s' + password = '%s' + } + }` +) - err = runGradle(buildConfiguration, excludeTestDeps, useWrapper, ignoreConfigFile, gradleConfigParams) - if err != nil { - return +type depTreeManager struct { + dependenciesTree + server *config.ServerDetails + releasesRepo string + depsRepo string + useWrapper bool +} + +// dependenciesTree represents a map between dependencies to their children dependencies in multiple projects. +type dependenciesTree struct { + tree map[string][]dependenciesPaths +} + +// dependenciesPaths represents a map between dependencies to their children dependencies in a single project. +type dependenciesPaths struct { + Paths map[string]dependenciesPaths `json:"children"` +} + +// The gradle-dep-tree generates a JSON representation for the dependencies for each gradle build file in the project. +// parseDepTreeFiles iterates over those JSONs, and append them to the map of dependencies in dependenciesTree struct. +func (dtp *depTreeManager) parseDepTreeFiles(jsonFiles []byte) error { + outputFiles := strings.Split(strings.TrimSpace(string(jsonFiles)), "\n") + for _, path := range outputFiles { + tree, err := os.ReadFile(strings.TrimSpace(path)) + if err != nil { + return errorutils.CheckError(err) + } + + encodedFileName := path[strings.LastIndex(path, string(os.PathSeparator))+1:] + decodedFileName, err := base64.StdEncoding.DecodeString(encodedFileName) + if err != nil { + return errorutils.CheckError(err) + } + + if err = dtp.appendDependenciesPaths(tree, string(decodedFileName)); err != nil { + return errorutils.CheckError(err) + } } + return nil +} - dependencyTree, err = createGavDependencyTree(buildConfiguration) - return +func (dtp *depTreeManager) appendDependenciesPaths(jsonDepTree []byte, fileName string) error { + var deps dependenciesPaths + if err := json.Unmarshal(jsonDepTree, &deps); err != nil { + return errorutils.CheckError(err) + } + if dtp.tree == nil { + dtp.tree = make(map[string][]dependenciesPaths) + } + dtp.tree[fileName] = append(dtp.tree[fileName], deps) + return nil } -func runGradle(buildConfiguration *utils.BuildConfiguration, excludeTestDeps, useWrapper, ignoreConfigFile bool, gradleConfigParams map[string]any) (err error) { - tasks := "clean compileJava " - if !excludeTestDeps { - tasks += "compileTestJava " - } - tasks += "artifactoryPublish" - log.Debug(fmt.Sprintf("gradle command tasks: %v", tasks)) - configFilePath := "" - if !ignoreConfigFile { - var exists bool - configFilePath, exists, err = utils.GetProjectConfFilePath(utils.Gradle) +func buildGradleDependencyTree(useWrapper bool, server *config.ServerDetails, depsRepo, releasesRepo string) (dependencyTree []*services.GraphNode, err error) { + if (server != nil && server.IsEmpty()) || depsRepo == "" { + depsRepo, server, err = getGradleConfig() if err != nil { return } - if exists { - log.Debug("Using resolver config from", configFilePath) + } + + manager := &depTreeManager{ + server: server, + releasesRepo: releasesRepo, + depsRepo: depsRepo, + useWrapper: useWrapper, + } + + outputFileContent, err := manager.runGradleDepTree() + if err != nil { + return nil, err + } + return manager.getGraphFromDepTree(outputFileContent) +} + +func (dtp *depTreeManager) runGradleDepTree() (outputFileContent []byte, err error) { + // Create the script file in the repository + depTreeDir, err := dtp.createDepTreeScript() + if err != nil { + return + } + defer func() { + e := fileutils.RemoveTempDir(depTreeDir) + if err == nil { + err = e + } + }() + + if dtp.useWrapper { + dtp.useWrapper, err = isGradleWrapperExist() + if err != nil { + return } } - // Check whether gradle wrapper exists - if useWrapper { - useWrapper, err = isGradleWrapperExist() + + return dtp.execGradleDepTree(depTreeDir) +} + +func (dtp *depTreeManager) createDepTreeScript() (tmpDir string, err error) { + tmpDir, err = fileutils.CreateTempDir() + if err != nil { + return + } + depsRepo := "" + releasesRepo := "" + if dtp.server != nil { + releasesRepo, err = getDepTreeArtifactoryRepository(fmt.Sprintf("%s/%s", dtp.releasesRepo, remoteDepTreePath), dtp.server) if err != nil { return } - if gradleConfigParams == nil { - gradleConfigParams = make(map[string]any) + depsRepo, err = getDepTreeArtifactoryRepository(dtp.depsRepo, dtp.server) + if err != nil { + return } - gradleConfigParams["usewrapper"] = useWrapper } - // Read config - vConfig, err := utils.ReadGradleConfig(configFilePath, gradleConfigParams) + depTreeInitScript := fmt.Sprintf(depTreeInitScript, releasesRepo, depsRepo) + return tmpDir, errorutils.CheckError(os.WriteFile(filepath.Join(tmpDir, depTreeInitFile), []byte(depTreeInitScript), 0666)) +} + +func (dtp *depTreeManager) execGradleDepTree(depTreeDir string) (outputFileContent []byte, err error) { + gradleExecPath, err := build.GetGradleExecPath(dtp.useWrapper) if err != nil { - return err + err = errorutils.CheckError(err) + return + } + + outputFilePath := filepath.Join(depTreeDir, depTreeOutputFile) + tasks := []string{ + "clean", + "generateDepTrees", "-I", filepath.Join(depTreeDir, depTreeInitFile), + "-q", + fmt.Sprintf("-Dcom.jfrog.depsTreeOutputFile=%s", outputFilePath), + "-Dcom.jfrog.includeAllBuildFiles=true"} + log.Info("Running gradle dep tree command: ", gradleExecPath, tasks) + if output, err := exec.Command(gradleExecPath, tasks...).CombinedOutput(); err != nil { + return nil, errorutils.CheckErrorf("error running gradle-dep-tree: %s\n%s", err.Error(), string(output)) + } + defer func() { + e := errorutils.CheckError(os.Remove(outputFilePath)) + if err == nil { + err = e + } + }() + + outputFileContent, err = os.ReadFile(outputFilePath) + err = errorutils.CheckError(err) + return +} + +// Assuming we ran gradle-dep-tree, getGraphFromDepTree receives the content of the depTreeOutputFile as input +func (dtp *depTreeManager) getGraphFromDepTree(outputFileContent []byte) ([]*services.GraphNode, error) { + if err := dtp.parseDepTreeFiles(outputFileContent); err != nil { + return nil, err + } + var depsGraph []*services.GraphNode + for dependency, children := range dtp.tree { + directDependency := &services.GraphNode{ + Id: GavPackageTypeIdentifier + dependency, + Nodes: []*services.GraphNode{}, + } + for _, childPath := range children { + populateGradleDependencyTree(directDependency, childPath) + } + depsGraph = append(depsGraph, directDependency) + } + return depsGraph, nil +} + +func populateGradleDependencyTree(currNode *services.GraphNode, currNodeChildren dependenciesPaths) { + for gav, children := range currNodeChildren.Paths { + childNode := &services.GraphNode{ + Id: GavPackageTypeIdentifier + gav, + Nodes: []*services.GraphNode{}, + Parent: currNode, + } + if currNode.NodeHasLoop() { + return + } + populateGradleDependencyTree(childNode, children) + currNode.Nodes = append(currNode.Nodes, childNode) + } +} + +func getDepTreeArtifactoryRepository(remoteRepo string, server *config.ServerDetails) (string, error) { + pass := server.Password + user := server.User + if server.AccessToken != "" { + pass = server.AccessToken + } + if pass == "" && user == "" { + return "", fmt.Errorf("either username/password or access token must be set for %s", server.Url) + } + return fmt.Sprintf(artifactoryRepository, + strings.TrimSuffix(server.ArtifactoryUrl, "/"), + remoteRepo, + user, + pass), nil +} + +// getGradleConfig the remote repository and server details defined in the .jfrog/projects/gradle.yaml file, if configured. +func getGradleConfig() (string, *config.ServerDetails, error) { + var exists bool + configFilePath, exists, err := utils.GetProjectConfFilePath(utils.Gradle) + if err != nil || !exists { + return "", nil, err + } + log.Debug("Using resolver config from", configFilePath) + configContent, err := utils.ReadConfigFile(configFilePath, utils.YAML) + if err != nil { + return "", nil, err + } + var repository string + if configContent.IsSet("resolver.repo") { + repository = configContent.Get("resolver.repo").(string) } - return gradleutils.RunGradle(vConfig, tasks, "", buildConfiguration, 0, true) + server, err := utils.GetServerDetails(configContent) + return repository, server, err } // This function assumes that the Gradle wrapper is in the root directory. diff --git a/xray/audit/java/gradle_test.go b/xray/audit/java/gradle_test.go index 61901c547..bc7215bfb 100644 --- a/xray/audit/java/gradle_test.go +++ b/xray/audit/java/gradle_test.go @@ -1,6 +1,9 @@ package java import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "os" "path/filepath" "testing" @@ -17,11 +20,11 @@ func TestGradleTreesWithoutConfig(t *testing.T) { assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) // Run getModulesDependencyTrees - modulesDependencyTrees, err := buildGradleDependencyTree(false, true, true, nil) + modulesDependencyTrees, err := buildGradleDependencyTree(false, nil, "", "") if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { assert.Len(t, modulesDependencyTrees, 5) // Check module - module := audit.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.example.gradle:webservice:1.0") + module := audit.GetAndAssertNode(t, modulesDependencyTrees, "webservice") assert.Len(t, module.Nodes, 7) // Check direct dependency @@ -40,20 +43,20 @@ func TestGradleTreesWithConfig(t *testing.T) { assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) // Run getModulesDependencyTrees - modulesDependencyTrees, err := buildGradleDependencyTree(false, false, false, nil) + modulesDependencyTrees, err := buildGradleDependencyTree(true, nil, "", "") if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, modulesDependencyTrees, 3) + assert.Len(t, modulesDependencyTrees, 5) // Check module - module := audit.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test.gradle.publish:webservice:1.0-SNAPSHOT") - assert.Len(t, module.Nodes, 7) + module := audit.GetAndAssertNode(t, modulesDependencyTrees, "api") + assert.Len(t, module.Nodes, 4) // Check direct dependency - directDependency := audit.GetAndAssertNode(t, module.Nodes, "org.apache.wicket:wicket:1.3.7") + directDependency := audit.GetAndAssertNode(t, module.Nodes, "commons-lang:commons-lang:2.4") assert.Len(t, directDependency.Nodes, 1) // Check transitive dependency - audit.GetAndAssertNode(t, directDependency.Nodes, "org.slf4j:slf4j-api:1.4.2") + audit.GetAndAssertNode(t, directDependency.Nodes, "commons-io:commons-io:1.2") } } @@ -64,19 +67,13 @@ func TestGradleTreesExcludeTestDeps(t *testing.T) { assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) // Run getModulesDependencyTrees - modulesDependencyTrees, err := buildGradleDependencyTree(true, true, true, nil) + modulesDependencyTrees, err := buildGradleDependencyTree(true, nil, "", "") if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { assert.Len(t, modulesDependencyTrees, 5) - // Check module - module := audit.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.example.gradle:webservice:1.0") - assert.Len(t, module.Nodes, 6) // Check direct dependency - directDependency := audit.GetAndAssertNode(t, module.Nodes, "org.apache.wicket:wicket:1.3.7") - assert.Len(t, directDependency.Nodes, 1) - - // Check transitive dependency - audit.GetAndAssertNode(t, directDependency.Nodes, "org.slf4j:slf4j-api:1.4.2") + directDependency := audit.GetAndAssertNode(t, modulesDependencyTrees, "services") + assert.Empty(t, directDependency.Nodes) } } @@ -93,3 +90,162 @@ func TestIsGradleWrapperExist(t *testing.T) { assert.NoError(t, err) assert.True(t, isWrapperExist) } + +func TestGetDepTreeArtifactoryRepository(t *testing.T) { + tests := []struct { + name string + remoteRepo string + server *config.ServerDetails + expectedUrl string + expectedErr string + }{ + { + name: "WithAccessToken", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + AccessToken: "my-access-token", + }, + expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = ''\n\t\t\t\tpassword = 'my-access-token'\n\t\t\t}\n\t\t}", + expectedErr: "", + }, + { + name: "WithUsernameAndPassword", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + User: "my-username", + Password: "my-password", + }, + expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'my-username'\n\t\t\t\tpassword = 'my-password'\n\t\t\t}\n\t\t}", + expectedErr: "", + }, + { + name: "MissingCredentials", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + }, + expectedUrl: "", + expectedErr: "either username/password or access token must be set for https://myartifactory.com", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + url, err := getDepTreeArtifactoryRepository(test.remoteRepo, test.server) + if err != nil { + assert.Equal(t, test.expectedErr, err.Error()) + } else { + assert.Equal(t, test.expectedUrl, url) + } + }) + } +} + +func TestGetGraphFromDepTree(t *testing.T) { + // Create and change directory to test workspace + tempDirPath, cleanUp := audit.CreateTestWorkspace(t, "gradle-example-ci-server") + defer func() { + cleanUp() + }() + assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) + testCase := struct { + name string + expectedResult map[string]map[string]string + }{ + name: "ValidOutputFileContent", + expectedResult: map[string]map[string]string{ + GavPackageTypeIdentifier + "shared": {}, + GavPackageTypeIdentifier + filepath.Base(tempDirPath): {}, + GavPackageTypeIdentifier + "services": {}, + GavPackageTypeIdentifier + "webservice": { + GavPackageTypeIdentifier + "junit:junit:4.11": "", + GavPackageTypeIdentifier + "commons-io:commons-io:1.2": "", + GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0": "", + GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", + GavPackageTypeIdentifier + "commons-collections:commons-collections:3.2": "", + }, + GavPackageTypeIdentifier + "api": { + GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", + GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", + }, + }, + } + + manager := &depTreeManager{} + outputFileContent, err := manager.runGradleDepTree() + assert.NoError(t, err) + result, err := (&depTreeManager{}).getGraphFromDepTree(outputFileContent) + assert.NoError(t, err) + for _, dependency := range result { + depChild, exists := testCase.expectedResult[dependency.Id] + assert.True(t, exists) + assert.Equal(t, len(depChild), len(dependency.Nodes)) + } +} + +func TestCreateDepTreeScript(t *testing.T) { + tmpDir, err := fileutils.CreateTempDir() + assert.NoError(t, err) + defer func() { + assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) + }() + currDir, err := os.Getwd() + assert.NoError(t, err) + assert.NoError(t, os.Chdir(tmpDir)) + defer func() { + assert.NoError(t, os.Chdir(currDir)) + }() + manager := &depTreeManager{} + tmpDir, err = manager.createDepTreeScript() + assert.NoError(t, err) + defer func() { + assert.NoError(t, os.Remove(filepath.Join(tmpDir, depTreeInitFile))) + }() + content, err := os.ReadFile(filepath.Join(tmpDir, depTreeInitFile)) + assert.NoError(t, err) + assert.Equal(t, fmt.Sprintf(depTreeInitScript, "", ""), string(content)) + manager.depsRepo = "deps-repo" + manager.releasesRepo = "release-repo" + manager.server = &config.ServerDetails{ + ArtifactoryUrl: "https://myartifactory.com/artifactory", + AccessToken: "my-access-token", + } + tmpDir, err = manager.createDepTreeScript() + assert.NoError(t, err) + expectedInitScript := `initscript { + repositories { + maven { + url "https://myartifactory.com/artifactory/release-repo/artifactory/oss-releases" + credentials { + username = '' + password = 'my-access-token' + } + } + mavenCentral() + } + dependencies { + classpath 'com.jfrog:gradle-dep-tree:2.2.0' + } +} + +allprojects { + repositories { + maven { + url "https://myartifactory.com/artifactory/deps-repo" + credentials { + username = '' + password = 'my-access-token' + } + } + } + apply plugin: com.jfrog.GradleDepTree +}` + content, err = os.ReadFile(filepath.Join(tmpDir, depTreeInitFile)) + assert.NoError(t, err) + assert.Equal(t, expectedInitScript, string(content)) +} diff --git a/xray/audit/java/javautils.go b/xray/audit/java/javautils.go index e2293e788..856939a05 100644 --- a/xray/audit/java/javautils.go +++ b/xray/audit/java/javautils.go @@ -1,6 +1,7 @@ package java import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "strconv" "time" @@ -23,23 +24,19 @@ type DependencyTreeParams struct { ExcludeTestDeps bool UseWrapper bool JavaProps map[string]any + Server *config.ServerDetails + DepsRepo string + ReleasesRepo string } -func createBuildConfiguration(buildName string) (*artifactoryUtils.BuildConfiguration, func(err error)) { +func createBuildConfiguration(buildName string) (*artifactoryUtils.BuildConfiguration, func() error) { buildConfiguration := artifactoryUtils.NewBuildConfiguration(buildName, strconv.FormatInt(time.Now().Unix(), 10), "", "") - return buildConfiguration, func(err error) { - buildName, err := buildConfiguration.GetBuildName() - if err != nil { - return - } + return buildConfiguration, func() error { buildNumber, err := buildConfiguration.GetBuildNumber() if err != nil { - return - } - err = artifactoryUtils.RemoveBuildDir(buildName, buildNumber, buildConfiguration.GetProject()) - if err != nil { - return + return err } + return artifactoryUtils.RemoveBuildDir(buildName, buildNumber, buildConfiguration.GetProject()) } } @@ -138,7 +135,15 @@ func BuildDependencyTree(params *DependencyTreeParams) (modules []*services.Grap if params.Tool == coreutils.Maven { return buildMvnDependencyTree(params.InsecureTls, params.IgnoreConfigFile, params.UseWrapper, params.JavaProps) } - return buildGradleDependencyTree(params.ExcludeTestDeps, params.UseWrapper, params.IgnoreConfigFile, params.JavaProps) + server := &config.ServerDetails{} + depsRepo := "" + releaseRepo := "" + if params.IgnoreConfigFile { + server = params.Server + depsRepo = params.DepsRepo + releaseRepo = params.ReleasesRepo + } + return buildGradleDependencyTree(params.UseWrapper, server, depsRepo, releaseRepo) } type dependencyMultimap struct { diff --git a/xray/audit/java/mvn.go b/xray/audit/java/mvn.go index b23f4288e..682bd5946 100644 --- a/xray/audit/java/mvn.go +++ b/xray/audit/java/mvn.go @@ -12,7 +12,12 @@ import ( func buildMvnDependencyTree(insecureTls, ignoreConfigFile, useWrapper bool, mvnProps map[string]any) (modules []*services.GraphNode, err error) { buildConfiguration, cleanBuild := createBuildConfiguration("audit-mvn") - defer cleanBuild(err) + defer func() { + e := cleanBuild() + if err == nil { + err = e + } + }() err = runMvn(buildConfiguration, insecureTls, ignoreConfigFile, useWrapper, mvnProps) if err != nil { diff --git a/xray/commands/audit/generic/auditmanager.go b/xray/commands/audit/generic/auditmanager.go index 01c2ff16c..183a7bd99 100644 --- a/xray/commands/audit/generic/auditmanager.go +++ b/xray/commands/audit/generic/auditmanager.go @@ -1,7 +1,6 @@ package audit import ( - "errors" "fmt" "github.com/jfrog/jfrog-cli-core/v2/xray/audit/java" "github.com/jfrog/jfrog-client-go/auth" @@ -29,11 +28,13 @@ type Params struct { xrayGraphScanParams services.XrayGraphScanParams serverDetails *config.ServerDetails progress ioUtils.ProgressMgr + dependencyTrees []*services.GraphNode ignoreConfigFile bool excludeTestDeps bool insecureTls bool useWrapper bool depsRepo string + releasesRepo string requirementsFile string technologies []string workingDirs []string @@ -157,6 +158,11 @@ func (params *Params) SetDepsRepo(depsRepo string) *Params { return params } +func (params *Params) SetReleasesRepo(releasesRepo string) *Params { + params.releasesRepo = releasesRepo + return params +} + func (params *Params) SetInstallFunc(installFunc func(tech string) error) *Params { params.installFunc = installFunc return params @@ -208,7 +214,7 @@ func auditMultipleWorkingDirs(params *Params) (results []services.ScanResponse, } if errorList.Len() > 0 { - err = errorutils.CheckError(errors.New(errorList.String())) + err = errorutils.CheckErrorf(errorList.String()) } return @@ -240,46 +246,50 @@ func doAudit(params *Params) (results []services.ScanResponse, isMultipleRoot bo errorList.WriteString(fmt.Sprintf("'%s' audit command failed:\n%s\n", tech, e.Error())) continue } + techResults = audit.BuildImpactPathsForScanResponse(techResults, params.dependencyTrees) results = append(results, techResults...) isMultipleRoot = len(dependencyTrees) > 1 } if errorList.Len() > 0 { - err = errors.New(errorList.String()) + err = errorutils.CheckErrorf(errorList.String()) } return } -func getTechDependencyTree(params *Params, tech coreutils.Technology) (dependencyTrees []*services.GraphNode, e error) { +func getTechDependencyTree(params *Params, tech coreutils.Technology) (dependencyTrees []*services.GraphNode, err error) { if params.progress != nil { params.progress.SetHeadlineMsg(fmt.Sprintf("Calculating %v dependencies", tech.ToFormal())) } switch tech { case coreutils.Maven, coreutils.Gradle: - dependencyTrees, e = getJavaDependencyTree(params, tech) + dependencyTrees, err = getJavaDependencyTree(params, tech) case coreutils.Npm: - dependencyTrees, e = npm.BuildDependencyTree(params.args) + dependencyTrees, err = npm.BuildDependencyTree(params.args) case coreutils.Yarn: - dependencyTrees, e = yarn.BuildDependencyTree() + dependencyTrees, err = yarn.BuildDependencyTree() case coreutils.Go: - dependencyTrees, e = _go.BuildDependencyTree(params.serverDetails, params.depsRepo) + dependencyTrees, err = _go.BuildDependencyTree(params.serverDetails, params.depsRepo) case coreutils.Pipenv, coreutils.Pip, coreutils.Poetry: - dependencyTrees, e = python.BuildDependencyTree(&python.AuditPython{ + dependencyTrees, err = python.BuildDependencyTree(&python.AuditPython{ Server: params.serverDetails, Tool: pythonutils.PythonTool(tech), RemotePypiRepo: params.depsRepo, PipRequirementsFile: params.requirementsFile}) case coreutils.Nuget: - dependencyTrees, e = nuget.BuildDependencyTree() + dependencyTrees, err = nuget.BuildDependencyTree() default: - e = errorutils.CheckError(fmt.Errorf("%s is currently not supported", string(tech))) + err = errorutils.CheckErrorf("%s is currently not supported", string(tech)) + return } - - return dependencyTrees, e + // Save the full dependencyTree to build impact paths for vulnerable dependencies + params.dependencyTrees = dependencyTrees + // Flatten the graph to speed up the ScanGraph request + return services.FlattenGraph(dependencyTrees), err } func getJavaDependencyTree(params *Params, tech coreutils.Technology) ([]*services.GraphNode, error) { var javaProps map[string]any - if params.DepsRepo() != "" { + if params.DepsRepo() != "" && tech == coreutils.Maven { javaProps = createJavaProps(params.DepsRepo(), params.ServerDetails()) } return java.BuildDependencyTree(&java.DependencyTreeParams{ @@ -289,6 +299,9 @@ func getJavaDependencyTree(params *Params, tech coreutils.Technology) ([]*servic ExcludeTestDeps: params.excludeTestDeps, UseWrapper: params.useWrapper, JavaProps: javaProps, + Server: params.serverDetails, + DepsRepo: params.depsRepo, + ReleasesRepo: params.releasesRepo, }) } diff --git a/xray/commands/audit/generic/generic.go b/xray/commands/audit/generic/generic.go index 536395cdb..026086f2b 100644 --- a/xray/commands/audit/generic/generic.go +++ b/xray/commands/audit/generic/generic.go @@ -138,7 +138,7 @@ func (auditCmd *GenericAuditCommand) Run() (err error) { auditCmd.IncludeVulnerabilities, auditCmd.IncludeLicenses, isMultipleRootProject, - auditCmd.PrintExtendedTable, + auditCmd.PrintExtendedTable, false, ) if err != nil { return diff --git a/xray/commands/scan/buildscan.go b/xray/commands/scan/buildscan.go index 5b4e72dae..853eba9b0 100644 --- a/xray/commands/scan/buildscan.go +++ b/xray/commands/scan/buildscan.go @@ -128,19 +128,19 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS if bsc.outputFormat != xrutils.Table { // Print the violations and/or vulnerabilities as part of one JSON. - err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, false, false, false, bsc.printExtendedTable) + err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, false, false, false, bsc.printExtendedTable, true) } else { // Print two different tables for violations and vulnerabilities (if needed) // If "No Xray Fail build policy...." error received, no need to print violations if !noFailBuildPolicy { - err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, false, false, false, bsc.printExtendedTable) + err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, false, false, false, bsc.printExtendedTable, true) if err != nil { return false, err } } if bsc.includeVulnerabilities { - err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, true, false, false, bsc.printExtendedTable) + err = xrutils.PrintScanResults(scanResponse, nil, bsc.outputFormat, true, false, false, bsc.printExtendedTable, true) if err != nil { return false, err } diff --git a/xray/commands/scan/scan.go b/xray/commands/scan/scan.go index 02ff9afe1..d14ecf778 100644 --- a/xray/commands/scan/scan.go +++ b/xray/commands/scan/scan.go @@ -231,7 +231,7 @@ func (scanCmd *ScanCommand) Run() (err error) { scanCmd.includeVulnerabilities, scanCmd.includeLicenses, true, - scanCmd.printExtendedTable, + scanCmd.printExtendedTable, true, ) if err != nil { return err diff --git a/xray/commands/testdata/gradle-example-ci-server/build.gradle b/xray/commands/testdata/gradle-example-ci-server/build.gradle index 4136548d9..79619705d 100644 --- a/xray/commands/testdata/gradle-example-ci-server/build.gradle +++ b/xray/commands/testdata/gradle-example-ci-server/build.gradle @@ -31,7 +31,3 @@ allprojects { version = '1.0' status = 'integration' } - -// Setting this property to true will make the artifactoryPublish task -// skip this module (in our case, the root module): -artifactoryPublish.skip = true diff --git a/xray/formats/conversion.go b/xray/formats/conversion.go index 43f9efbf0..69d6cdd0f 100644 --- a/xray/formats/conversion.go +++ b/xray/formats/conversion.go @@ -4,89 +4,166 @@ import ( "strings" ) -func ConvertToVulnerabilityTableRow(rows []VulnerabilityOrViolationRow) (tableRows []VulnerabilityTableRow) { +func ConvertToVulnerabilityTableRow(rows []VulnerabilityOrViolationRow) (tableRows []vulnerabilityTableRow) { for i := range rows { - tableRows = append(tableRows, VulnerabilityTableRow{ - Severity: rows[i].Severity, - SeverityNumValue: rows[i].SeverityNumValue, - ImpactedDependencyName: rows[i].ImpactedDependencyName, - ImpactedDependencyVersion: rows[i].ImpactedDependencyVersion, - ImpactedDependencyType: rows[i].ImpactedDependencyType, - FixedVersions: strings.Join(rows[i].FixedVersions, "\n"), - DirectDependencies: ConvertToComponentTableRow(rows[i].Components), - Cves: ConvertToCveTableRow(rows[i].Cves), - IssueId: rows[i].IssueId, + tableRows = append(tableRows, vulnerabilityTableRow{ + severity: rows[i].Severity, + severityNumValue: rows[i].SeverityNumValue, + impactedDependencyName: rows[i].ImpactedDependencyName, + impactedDependencyVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + fixedVersions: strings.Join(rows[i].FixedVersions, "\n"), + directDependencies: convertToComponentTableRow(rows[i].Components), + cves: convertToCveTableRow(rows[i].Cves), + issueId: rows[i].IssueId, }) } return } -func ConvertToLicenseViolationTableRow(rows []LicenseViolationRow) (tableRows []LicenseViolationTableRow) { +func ConvertToVulnerabilityScanTableRow(rows []VulnerabilityOrViolationRow) (tableRows []vulnerabilityScanTableRow) { for i := range rows { - tableRows = append(tableRows, LicenseViolationTableRow{ - LicenseKey: rows[i].LicenseKey, - Severity: rows[i].Severity, - SeverityNumValue: rows[i].SeverityNumValue, - ImpactedDependencyName: rows[i].ImpactedDependencyName, - ImpactedDependencyVersion: rows[i].ImpactedDependencyVersion, - ImpactedDependencyType: rows[i].ImpactedDependencyType, - DirectDependencies: ConvertToComponentTableRow(rows[i].Components), + tableRows = append(tableRows, vulnerabilityScanTableRow{ + severity: rows[i].Severity, + severityNumValue: rows[i].SeverityNumValue, + impactedPackageName: rows[i].ImpactedDependencyName, + impactedPackageVersion: rows[i].ImpactedDependencyVersion, + ImpactedPackageType: rows[i].ImpactedDependencyType, + fixedVersions: strings.Join(rows[i].FixedVersions, "\n"), + directPackages: convertToComponentScanTableRow(rows[i].Components), + cves: convertToCveTableRow(rows[i].Cves), + issueId: rows[i].IssueId, + }) + } + return +} + +func ConvertToLicenseViolationTableRow(rows []LicenseViolationRow) (tableRows []licenseViolationTableRow) { + for i := range rows { + tableRows = append(tableRows, licenseViolationTableRow{ + licenseKey: rows[i].LicenseKey, + severity: rows[i].Severity, + severityNumValue: rows[i].SeverityNumValue, + impactedDependencyName: rows[i].ImpactedDependencyName, + impactedDependencyVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentTableRow(rows[i].Components), + }) + } + return +} + +func ConvertToLicenseViolationScanTableRow(rows []LicenseViolationRow) (tableRows []licenseViolationScanTableRow) { + for i := range rows { + tableRows = append(tableRows, licenseViolationScanTableRow{ + licenseKey: rows[i].LicenseKey, + severity: rows[i].Severity, + severityNumValue: rows[i].SeverityNumValue, + impactedPackageName: rows[i].ImpactedDependencyName, + impactedPackageVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentScanTableRow(rows[i].Components), + }) + } + return +} + +func ConvertToLicenseTableRow(rows []LicenseRow) (tableRows []licenseTableRow) { + for i := range rows { + tableRows = append(tableRows, licenseTableRow{ + licenseKey: rows[i].LicenseKey, + impactedDependencyName: rows[i].ImpactedDependencyName, + impactedDependencyVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentTableRow(rows[i].Components), }) } return } -func ConvertToLicenseTableRow(rows []LicenseRow) (tableRows []LicenseTableRow) { +func ConvertToLicenseScanTableRow(rows []LicenseRow) (tableRows []licenseScanTableRow) { for i := range rows { - tableRows = append(tableRows, LicenseTableRow{ - LicenseKey: rows[i].LicenseKey, - ImpactedDependencyName: rows[i].ImpactedDependencyName, - ImpactedDependencyVersion: rows[i].ImpactedDependencyVersion, - ImpactedDependencyType: rows[i].ImpactedDependencyType, - DirectDependencies: ConvertToComponentTableRow(rows[i].Components), + tableRows = append(tableRows, licenseScanTableRow{ + licenseKey: rows[i].LicenseKey, + impactedPackageName: rows[i].ImpactedDependencyName, + impactedPackageVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentScanTableRow(rows[i].Components), }) } return } -func ConvertToOperationalRiskViolationTableRow(rows []OperationalRiskViolationRow) (tableRows []OperationalRiskViolationTableRow) { +func ConvertToOperationalRiskViolationTableRow(rows []OperationalRiskViolationRow) (tableRows []operationalRiskViolationTableRow) { for i := range rows { - tableRows = append(tableRows, OperationalRiskViolationTableRow{ + tableRows = append(tableRows, operationalRiskViolationTableRow{ Severity: rows[i].Severity, - SeverityNumValue: rows[i].SeverityNumValue, - ImpactedDependencyName: rows[i].ImpactedDependencyName, - ImpactedDependencyVersion: rows[i].ImpactedDependencyVersion, - ImpactedDependencyType: rows[i].ImpactedDependencyType, - DirectDependencies: ConvertToComponentTableRow(rows[i].Components), - IsEol: rows[i].IsEol, - Cadence: rows[i].Cadence, + severityNumValue: rows[i].SeverityNumValue, + impactedDependencyName: rows[i].ImpactedDependencyName, + impactedDependencyVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentTableRow(rows[i].Components), + isEol: rows[i].IsEol, + cadence: rows[i].Cadence, Commits: rows[i].Commits, - Committers: rows[i].Committers, - NewerVersions: rows[i].NewerVersions, - LatestVersion: rows[i].LatestVersion, - RiskReason: rows[i].RiskReason, - EolMessage: rows[i].EolMessage, + committers: rows[i].Committers, + newerVersions: rows[i].NewerVersions, + latestVersion: rows[i].LatestVersion, + riskReason: rows[i].RiskReason, + eolMessage: rows[i].EolMessage, + }) + } + return +} + +func ConvertToOperationalRiskViolationScanTableRow(rows []OperationalRiskViolationRow) (tableRows []operationalRiskViolationScanTableRow) { + for i := range rows { + tableRows = append(tableRows, operationalRiskViolationScanTableRow{ + Severity: rows[i].Severity, + severityNumValue: rows[i].SeverityNumValue, + impactedPackageName: rows[i].ImpactedDependencyName, + impactedPackageVersion: rows[i].ImpactedDependencyVersion, + impactedDependencyType: rows[i].ImpactedDependencyType, + directDependencies: convertToComponentScanTableRow(rows[i].Components), + isEol: rows[i].IsEol, + cadence: rows[i].Cadence, + commits: rows[i].Commits, + committers: rows[i].Committers, + newerVersions: rows[i].NewerVersions, + latestVersion: rows[i].LatestVersion, + riskReason: rows[i].RiskReason, + eolMessage: rows[i].EolMessage, + }) + } + return +} + +func convertToComponentTableRow(rows []ComponentRow) (tableRows []directDependenciesTableRow) { + for i := range rows { + tableRows = append(tableRows, directDependenciesTableRow{ + name: rows[i].Name, + version: rows[i].Version, }) } return } -func ConvertToComponentTableRow(rows []ComponentRow) (tableRows []DirectDependenciesTableRow) { +func convertToComponentScanTableRow(rows []ComponentRow) (tableRows []directPackagesTableRow) { for i := range rows { - tableRows = append(tableRows, DirectDependenciesTableRow{ - Name: rows[i].Name, - Version: rows[i].Version, + tableRows = append(tableRows, directPackagesTableRow{ + name: rows[i].Name, + version: rows[i].Version, }) } return } -func ConvertToCveTableRow(rows []CveRow) (tableRows []CveTableRow) { +func convertToCveTableRow(rows []CveRow) (tableRows []cveTableRow) { for i := range rows { - tableRows = append(tableRows, CveTableRow{ - Id: rows[i].Id, - CvssV2: rows[i].CvssV2, - CvssV3: rows[i].CvssV3, + tableRows = append(tableRows, cveTableRow{ + id: rows[i].Id, + cvssV2: rows[i].CvssV2, + cvssV3: rows[i].CvssV3, }) } return diff --git a/xray/formats/table.go b/xray/formats/table.go index 355ed2b47..04fddca5f 100644 --- a/xray/formats/table.go +++ b/xray/formats/table.go @@ -5,60 +5,118 @@ package formats // Use the conversion methods in this package to convert from the API structs to the table structs. // Used for vulnerabilities and security violations -type VulnerabilityTableRow struct { - Severity string `col-name:"Severity"` - SeverityNumValue int // For sorting - DirectDependencies []DirectDependenciesTableRow `embed-table:"true"` - ImpactedDependencyName string `col-name:"Impacted\nDependency\nName"` - ImpactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` - FixedVersions string `col-name:"Fixed\nVersions"` - ImpactedDependencyType string `col-name:"Type"` - Cves []CveTableRow `embed-table:"true"` - IssueId string `col-name:"Issue ID" extended:"true"` +type vulnerabilityTableRow struct { + severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directDependencies []directDependenciesTableRow `embed-table:"true"` + impactedDependencyName string `col-name:"Impacted\nDependency\nName"` + impactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` + fixedVersions string `col-name:"Fixed\nVersions"` + impactedDependencyType string `col-name:"Type"` + cves []cveTableRow `embed-table:"true"` + issueId string `col-name:"Issue ID" extended:"true"` } -type LicenseTableRow struct { - LicenseKey string `col-name:"License"` - DirectDependencies []DirectDependenciesTableRow `embed-table:"true"` - ImpactedDependencyName string `col-name:"Impacted\nDependency"` - ImpactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` - ImpactedDependencyType string `col-name:"Type"` +type vulnerabilityScanTableRow struct { + severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directPackages []directPackagesTableRow `embed-table:"true"` + impactedPackageName string `col-name:"Impacted\nPackage\nName"` + impactedPackageVersion string `col-name:"Impacted\nPackage\nVersion"` + fixedVersions string `col-name:"Fixed\nVersions"` + ImpactedPackageType string `col-name:"Type"` + cves []cveTableRow `embed-table:"true"` + issueId string `col-name:"Issue ID" extended:"true"` } -type LicenseViolationTableRow struct { - LicenseKey string `col-name:"License"` - Severity string `col-name:"Severity"` - SeverityNumValue int // For sorting - DirectDependencies []DirectDependenciesTableRow `embed-table:"true"` - ImpactedDependencyName string `col-name:"Impacted\nDependency"` - ImpactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` - ImpactedDependencyType string `col-name:"Type"` +type licenseTableRow struct { + licenseKey string `col-name:"License"` + directDependencies []directDependenciesTableRow `embed-table:"true"` + impactedDependencyName string `col-name:"Impacted\nDependency"` + impactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` + impactedDependencyType string `col-name:"Type"` } -type OperationalRiskViolationTableRow struct { - Severity string `col-name:"Severity"` - SeverityNumValue int // For sorting - DirectDependencies []DirectDependenciesTableRow `embed-table:"true"` - ImpactedDependencyName string `col-name:"Impacted\nDependency"` - ImpactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` - ImpactedDependencyType string `col-name:"Type"` - RiskReason string `col-name:"Risk\nReason"` - IsEol string `col-name:"Is\nEnd\nOf\nLife" extended:"true"` - EolMessage string `col-name:"End\nOf\nLife\nMessage" extended:"true"` - Cadence string `col-name:"Cadence" extended:"true"` +type licenseScanTableRow struct { + licenseKey string `col-name:"License"` + directDependencies []directPackagesTableRow `embed-table:"true"` + impactedPackageName string `col-name:"Impacted\nPackage"` + impactedPackageVersion string `col-name:"Impacted\nPackage\nVersion"` + impactedDependencyType string `col-name:"Type"` +} + +type licenseViolationTableRow struct { + licenseKey string `col-name:"License"` + severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directDependencies []directDependenciesTableRow `embed-table:"true"` + impactedDependencyName string `col-name:"Impacted\nDependency"` + impactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` + impactedDependencyType string `col-name:"Type"` +} + +type licenseViolationScanTableRow struct { + licenseKey string `col-name:"License"` + severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directDependencies []directPackagesTableRow `embed-table:"true"` + impactedPackageName string `col-name:"Impacted\nPackage"` + impactedPackageVersion string `col-name:"Impacted\nPackage\nVersion"` + impactedDependencyType string `col-name:"Type"` +} + +type operationalRiskViolationTableRow struct { + Severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directDependencies []directDependenciesTableRow `embed-table:"true"` + impactedDependencyName string `col-name:"Impacted\nDependency"` + impactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` + impactedDependencyType string `col-name:"Type"` + riskReason string `col-name:"Risk\nReason"` + isEol string `col-name:"Is\nEnd\nOf\nLife" extended:"true"` + eolMessage string `col-name:"End\nOf\nLife\nMessage" extended:"true"` + cadence string `col-name:"Cadence" extended:"true"` Commits string `col-name:"Commits" extended:"true"` - Committers string `col-name:"Committers" extended:"true"` - NewerVersions string `col-name:"Newer\nVersions" extended:"true"` - LatestVersion string `col-name:"Latest\nVersion" extended:"true"` + committers string `col-name:"Committers" extended:"true"` + newerVersions string `col-name:"Newer\nVersions" extended:"true"` + latestVersion string `col-name:"Latest\nVersion" extended:"true"` +} + +type operationalRiskViolationScanTableRow struct { + Severity string `col-name:"Severity"` + // For sorting + severityNumValue int + directDependencies []directPackagesTableRow `embed-table:"true"` + impactedPackageName string `col-name:"Impacted\nPackage"` + impactedPackageVersion string `col-name:"Impacted\nPackage\nVersion"` + impactedDependencyType string `col-name:"Type"` + riskReason string `col-name:"Risk\nReason"` + isEol string `col-name:"Is\nEnd\nOf\nLife" extended:"true"` + eolMessage string `col-name:"End\nOf\nLife\nMessage" extended:"true"` + cadence string `col-name:"Cadence" extended:"true"` + commits string `col-name:"Commits" extended:"true"` + committers string `col-name:"Committers" extended:"true"` + newerVersions string `col-name:"Newer\nVersions" extended:"true"` + latestVersion string `col-name:"Latest\nVersion" extended:"true"` +} + +type directDependenciesTableRow struct { + name string `col-name:"Direct\nDependency"` + version string `col-name:"Direct\nDependency\nVersion"` } -type DirectDependenciesTableRow struct { - Name string `col-name:"Direct\nDependency"` - Version string `col-name:"Direct\nDependency\nVersion"` +type directPackagesTableRow struct { + name string `col-name:"Direct\nPackage"` + version string `col-name:"Direct\nPackage\nVersion"` } -type CveTableRow struct { - Id string `col-name:"CVE"` - CvssV2 string `col-name:"CVSS\nv2" extended:"true"` - CvssV3 string `col-name:"CVSS\nv3" extended:"true"` +type cveTableRow struct { + id string `col-name:"CVE"` + cvssV2 string `col-name:"CVSS\nv2" extended:"true"` + cvssV3 string `col-name:"CVSS\nv3" extended:"true"` } diff --git a/xray/utils/resultstable.go b/xray/utils/resultstable.go index b2e5b8c17..1aaa14766 100644 --- a/xray/utils/resultstable.go +++ b/xray/utils/resultstable.go @@ -28,23 +28,37 @@ const ( // In case multipleRoots is true, the field Component will show the root of each impact path, otherwise it will show the root's child. // In case one (or more) of the violations contains the field FailBuild set to true, CliError with exit code 3 will be returned. // Set printExtended to true to print fields with 'extended' tag. -func PrintViolationsTable(violations []services.Violation, multipleRoots, printExtended bool) error { +// If the scan argument is set to true, print the scan tables. +func PrintViolationsTable(violations []services.Violation, multipleRoots, printExtended, scan bool) error { securityViolationsRows, licenseViolationsRows, operationalRiskViolationsRows, err := prepareViolations(violations, multipleRoots, true, true) if err != nil { return err } - - // Print tables - err = coreutils.PrintTable(formats.ConvertToVulnerabilityTableRow(securityViolationsRows), "Security Violations", "No security violations were found", printExtended) - if err != nil { - return err - } - err = coreutils.PrintTable(formats.ConvertToLicenseViolationTableRow(licenseViolationsRows), "License Compliance Violations", "No license compliance violations were found", printExtended) - if err != nil { - return err - } - if len(operationalRiskViolationsRows) > 0 { - return coreutils.PrintTable(formats.ConvertToOperationalRiskViolationTableRow(operationalRiskViolationsRows), "Operational Risk Violations", "No operational risk violations were found", printExtended) + // Print tables, if scan is true; print the scan tables. + if scan { + err = coreutils.PrintTable(formats.ConvertToVulnerabilityScanTableRow(securityViolationsRows), "Security Violations", "No security violations were found", printExtended) + if err != nil { + return err + } + err = coreutils.PrintTable(formats.ConvertToLicenseViolationScanTableRow(licenseViolationsRows), "License Compliance Violations", "No license compliance violations were found", printExtended) + if err != nil { + return err + } + if len(operationalRiskViolationsRows) > 0 { + return coreutils.PrintTable(formats.ConvertToOperationalRiskViolationScanTableRow(operationalRiskViolationsRows), "Operational Risk Violations", "No operational risk violations were found", printExtended) + } + } else { + err = coreutils.PrintTable(formats.ConvertToVulnerabilityTableRow(securityViolationsRows), "Security Violations", "No security violations were found", printExtended) + if err != nil { + return err + } + err = coreutils.PrintTable(formats.ConvertToLicenseViolationTableRow(licenseViolationsRows), "License Compliance Violations", "No license compliance violations were found", printExtended) + if err != nil { + return err + } + if len(operationalRiskViolationsRows) > 0 { + return coreutils.PrintTable(formats.ConvertToOperationalRiskViolationTableRow(operationalRiskViolationsRows), "Operational Risk Violations", "No operational risk violations were found", printExtended) + } } return nil } @@ -152,12 +166,17 @@ func prepareViolations(violations []services.Violation, multipleRoots, isTable, // Set multipleRoots to true in case the given vulnerabilities array contains (or may contain) results of several projects or files (like in binary scan). // In case multipleRoots is true, the field Component will show the root of each impact path, otherwise it will show the root's child. // Set printExtended to true to print fields with 'extended' tag. -func PrintVulnerabilitiesTable(vulnerabilities []services.Vulnerability, multipleRoots, printExtended bool) error { +// If the scan argument is set to true, print the scan tables. +func PrintVulnerabilitiesTable(vulnerabilities []services.Vulnerability, multipleRoots, printExtended, scan bool) error { vulnerabilitiesRows, err := prepareVulnerabilities(vulnerabilities, multipleRoots, true, true) if err != nil { return err } + if scan { + return coreutils.PrintTable(formats.ConvertToVulnerabilityScanTableRow(vulnerabilitiesRows), "Vulnerabilities", "✨ No vulnerabilities were found ✨", printExtended) + } + return coreutils.PrintTable(formats.ConvertToVulnerabilityTableRow(vulnerabilitiesRows), "Vulnerabilities", "✨ No vulnerabilities were found ✨", printExtended) } @@ -214,12 +233,15 @@ func prepareVulnerabilities(vulnerabilities []services.Vulnerability, multipleRo // Set multipleRoots to true in case the given licenses array contains (or may contain) results of several projects or files (like in binary scan). // In case multipleRoots is true, the field Component will show the root of each impact path, otherwise it will show the root's child. // Set printExtended to true to print fields with 'extended' tag. -func PrintLicensesTable(licenses []services.License, printExtended bool) error { +// If the scan argument is set to true, print the scan tables. +func PrintLicensesTable(licenses []services.License, printExtended, scan bool) error { licensesRows, err := PrepareLicenses(licenses) if err != nil { return err } - + if scan { + return coreutils.PrintTable(formats.ConvertToLicenseScanTableRow(licensesRows), "Licenses", "No licenses were found", printExtended) + } return coreutils.PrintTable(formats.ConvertToLicenseTableRow(licensesRows), "Licenses", "No licenses were found", printExtended) } diff --git a/xray/utils/resultstable_test.go b/xray/utils/resultstable_test.go index 3c9ce8848..2c061e5d4 100644 --- a/xray/utils/resultstable_test.go +++ b/xray/utils/resultstable_test.go @@ -22,7 +22,7 @@ func TestPrintViolationsTable(t *testing.T) { } for _, test := range tests { - err := PrintViolationsTable(test.violations, false, true) + err := PrintViolationsTable(test.violations, false, true, false) assert.NoError(t, err) if CheckIfFailBuild([]services.ScanResponse{{Violations: test.violations}}) { err = NewFailBuildError() diff --git a/xray/utils/resultwriter.go b/xray/utils/resultwriter.go index d09ac56ac..1db90fc02 100644 --- a/xray/utils/resultwriter.go +++ b/xray/utils/resultwriter.go @@ -40,7 +40,8 @@ type sarifProperties struct { // PrintScanResults prints Xray scan results in the given format. // Note that errors are printed only on SimpleJson format. -func PrintScanResults(results []services.ScanResponse, errors []formats.SimpleJsonError, format OutputFormat, includeVulnerabilities, includeLicenses, isMultipleRoots, printExtended bool) error { +// If the scan argument is set to true, print the scan tables. +func PrintScanResults(results []services.ScanResponse, errors []formats.SimpleJsonError, format OutputFormat, includeVulnerabilities, includeLicenses, isMultipleRoots, printExtended, scan bool) error { switch format { case Table: var err error @@ -53,15 +54,15 @@ func PrintScanResults(results []services.ScanResponse, errors []formats.SimpleJs log.Output("The full scan results are available here: " + resultsPath) } if includeVulnerabilities { - err = PrintVulnerabilitiesTable(vulnerabilities, isMultipleRoots, printExtended) + err = PrintVulnerabilitiesTable(vulnerabilities, isMultipleRoots, printExtended, scan) } else { - err = PrintViolationsTable(violations, isMultipleRoots, printExtended) + err = PrintViolationsTable(violations, isMultipleRoots, printExtended, scan) } if err != nil { return err } if includeLicenses { - err = PrintLicensesTable(licenses, printExtended) + err = PrintLicensesTable(licenses, printExtended, scan) } return err case SimpleJson: From 9b4006e743b6b277fafa4a72e238d44835076b76 Mon Sep 17 00:00:00 2001 From: Eyal Ben Moshe Date: Thu, 13 Apr 2023 12:52:11 +0300 Subject: [PATCH 2/9] Added the Frogbot badge to the README (#745) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6321606c8..aefc6aefe 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ +
+ +# jfrog-cli-core +[![Scanned by Frogbot](https://raw.github.com/jfrog/frogbot/master/images/frogbot-badge.svg)](https://github.com/jfrog/frogbot#readme) + +
+ | Branch | Status | |:------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| | master | [![Test](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml?query=branch%3Amaster) [![Static Analysis](https://github.com/jfrog/jfrog-cli-core/actions/workflows/analysis.yml/badge.svg?branch=master)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/analysis.yml) | | dev | [![Test](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml/badge.svg?branch=dev)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml?query=branch%3Adev) [![Static Analysis](https://github.com/jfrog/jfrog-cli-core/actions/workflows/analysis.yml/badge.svg?branch=dev)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/analysis.yml) | -| v1 | [![Test](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml/badge.svg?branch=v1)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml?query=branch%3Av1) | -| dev-v1 | [![Test](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml/badge.svg?branch=dev-v1)](https://github.com/jfrog/jfrog-cli-core/actions/workflows/test.yml?query=branch%3Adev-v1) | - -# jfrog-cli-core ## General From 47a0f3f5973802a24767478b02672879ad82148f Mon Sep 17 00:00:00 2001 From: Omer Zidkoni Date: Wed, 12 Jul 2023 14:25:38 +0300 Subject: [PATCH 3/9] Upgrade jfrog-cli-core to 2.38.0 --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 7ab5f356e..f88563ffd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/jfrog/build-info-go v1.9.6 github.com/jfrog/gofrog v1.3.0 - github.com/jfrog/jfrog-client-go v1.31.0 + github.com/jfrog/jfrog-client-go v1.31.1 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/owenrumney/go-sarif/v2 v2.1.3 @@ -94,7 +94,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230705083849-6fd087a5e228 +// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230705083849-6fd087a5e228 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230518114837-fe6a826d5001 diff --git a/go.sum b/go.sum index 07dfff302..6c6d6437a 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,8 @@ github.com/jfrog/build-info-go v1.9.6 h1:lCJ2j5uXAlJsSwDe5J8WD7Co1f/hUlZvMfwfb5A github.com/jfrog/build-info-go v1.9.6/go.mod h1:GbuFS+viHCKZYx9nWHYu7ab1DgQkFdtVN3BJPUNb2D4= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= -github.com/jfrog/jfrog-client-go v1.28.1-0.20230705083849-6fd087a5e228 h1:bkOkjb6sVqo6Jgw9eYSH58jIFPOJvFvwt+jIXvef7QM= -github.com/jfrog/jfrog-client-go v1.28.1-0.20230705083849-6fd087a5e228/go.mod h1:qEJxoe68sUtqHJ1YhXv/7pKYP/9p1D5tJrruzJKYeoI= +github.com/jfrog/jfrog-client-go v1.31.1 h1:lmunA5ZpRsrWTXgEGvnvVPIfwEqB3gn6+eVNpV2VBzU= +github.com/jfrog/jfrog-client-go v1.31.1/go.mod h1:qEJxoe68sUtqHJ1YhXv/7pKYP/9p1D5tJrruzJKYeoI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= From 40e7d2dc1460386ef236c6f3ea1e95904ca01c6e Mon Sep 17 00:00:00 2001 From: Tal Arian Date: Sun, 16 Jul 2023 12:26:38 +0300 Subject: [PATCH 4/9] Remove Secrets Scanner Sacanners Names config (#862) --- xray/audit/jas/secretsscanner.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xray/audit/jas/secretsscanner.go b/xray/audit/jas/secretsscanner.go index c249afff6..c93bd582b 100644 --- a/xray/audit/jas/secretsscanner.go +++ b/xray/audit/jas/secretsscanner.go @@ -3,6 +3,9 @@ package jas import ( "errors" "fmt" + "os" + "path/filepath" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" @@ -10,13 +13,10 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/owenrumney/go-sarif/v2/sarif" "gopkg.in/yaml.v2" - "os" - "path/filepath" ) const ( secretsScanCommand = "sec" - secretsScannersNames = "tokens, entropy" secretsScannerType = "secrets-scan" secScanFailureMessage = "failed to run secrets scan. Cause: %s" ) @@ -100,7 +100,6 @@ type secretsScanConfiguration struct { Roots []string `yaml:"roots"` Output string `yaml:"output"` Type string `yaml:"type"` - Scanners string `yaml:"scanners"` SkippedDirs []string `yaml:"skipped-folders"` } @@ -116,7 +115,6 @@ func (s *SecretScanManager) createConfigFile() error { Roots: []string{currentDir}, Output: s.resultsFileName, Type: secretsScannerType, - Scanners: secretsScannersNames, SkippedDirs: skippedDirs, }, }, From 6b129f11d604375e862d8737a437f5c925f3f69c Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:48:09 +0300 Subject: [PATCH 5/9] Merge remote-tracking branch 'upstream/dev' (#955) --- artifactory/utils/dependenciesutils.go | 2 +- utils/usage/usage.go | 27 ------------ utils/usage/usage_test.go | 58 -------------------------- xray/utils/analyzermanager.go | 4 +- 4 files changed, 3 insertions(+), 88 deletions(-) diff --git a/artifactory/utils/dependenciesutils.go b/artifactory/utils/dependenciesutils.go index 9591c532b..91fa38cd1 100644 --- a/artifactory/utils/dependenciesutils.go +++ b/artifactory/utils/dependenciesutils.go @@ -57,7 +57,7 @@ func DownloadAnalyzerManagerIfNeeded() error { downloadUrl := artDetails.ArtifactoryUrl + remotePath remoteFileDetails, _, err := client.GetRemoteFileDetails(downloadUrl, &httpClientDetails) if err != nil { - return errors.New("couldn't get remote file details for " + downloadUrl) + return fmt.Errorf("couldn't get remote file details for %s: %s", downloadUrl, err.Error()) } analyzerManagerDir, err := xrayutils.GetAnalyzerManagerDirAbsolutePath() if err != nil { diff --git a/utils/usage/usage.go b/utils/usage/usage.go index 96b6ae28d..30f40f18c 100644 --- a/utils/usage/usage.go +++ b/utils/usage/usage.go @@ -2,8 +2,6 @@ package usage import ( "fmt" - xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" - "golang.org/x/sync/errgroup" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -93,14 +91,6 @@ func (ur *UsageReporter) Report(features ...ReportFeature) { return }) } - if ur.sendToXray { - ur.reportWaitGroup.Go(func() (err error) { - if err = ur.reportToXray(features...); err != nil { - err = fmt.Errorf("xray, %s", err.Error()) - } - return - }) - } if ur.sendToArtifactory { ur.reportWaitGroup.Go(func() (err error) { if err = ur.reportToArtifactory(features...); err != nil { @@ -131,23 +121,6 @@ func (ur *UsageReporter) reportToEcosystem(features ...ReportFeature) (err error return ecosysusage.SendEcosystemUsageReports(reports...) } -func (ur *UsageReporter) reportToXray(features ...ReportFeature) (err error) { - if ur.serverDetails.XrayUrl == "" { - err = errorutils.CheckErrorf("Xray Url is not set.") - return - } - serviceManager, err := xrayutils.CreateXrayServiceManager(ur.serverDetails) - if err != nil { - return - } - events := ur.convertAttributesToXrayEvents(features...) - if len(events) == 0 { - err = errorutils.CheckErrorf("Nothing to send.") - return - } - return xrayusage.SendXrayUsageEvents(*serviceManager, events...) -} - func (ur *UsageReporter) reportToArtifactory(features ...ReportFeature) (err error) { if ur.serverDetails.ArtifactoryUrl == "" { err = errorutils.CheckErrorf("Artifactory Url is not set..") diff --git a/utils/usage/usage_test.go b/utils/usage/usage_test.go index d67ceef25..657f19ba1 100644 --- a/utils/usage/usage_test.go +++ b/utils/usage/usage_test.go @@ -159,64 +159,6 @@ func createArtifactoryUsageHandler(t *testing.T, productName, commandName string } } -func TestReportXrayUsage(t *testing.T) { - const productName = "test-product" - const commandName = "test-command" - const clientName = "test-client" - - server := httptest.NewServer(createXrayUsageHandler(t, productName, commandName, clientName)) - defer server.Close() - serverDetails := &config.ServerDetails{XrayUrl: server.URL + "/"} - - reporter := NewUsageReporter(productName, serverDetails).SetSendToEcosystem(false).SetSendToArtifactory(false) - - reporter.Report(ReportFeature{ - FeatureId: commandName, - ClientId: clientName, - }) - assert.NoError(t, reporter.WaitForResponses()) -} - -func TestReportXrayError(t *testing.T) { - reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToEcosystem(false).SetSendToArtifactory(false) - reporter.Report(ReportFeature{ - FeatureId: "", - }) - assert.Error(t, reporter.WaitForResponses()) - - server := httptest.NewServer(create404UsageHandler(t)) - defer server.Close() - reporter = NewUsageReporter("", &config.ServerDetails{ArtifactoryUrl: server.URL + "/"}).SetSendToEcosystem(false).SetSendToArtifactory(false) - reporter.Report(ReportFeature{ - FeatureId: "", - }) - assert.Error(t, reporter.WaitForResponses()) -} - -func createXrayUsageHandler(t *testing.T, productId, commandName, clientId string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/api/v1/system/version" { - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(`{"xray_version":"6.9.0"}`)) - assert.NoError(t, err) - return - } - if r.RequestURI == "/api/v1/usage/events/send" { - // Check request - buf := new(bytes.Buffer) - _, err := buf.ReadFrom(r.Body) - assert.NoError(t, err) - featureId := xrayusage.GetExpectedXrayEventName(productId, commandName) - assert.Equal(t, fmt.Sprintf(`[{"data":{"clientId":"%s"},"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`, clientId, productId, featureId), buf.String()) - - // Send response OK - w.WriteHeader(http.StatusOK) - _, err = w.Write([]byte("{}")) - assert.NoError(t, err) - } - } -} - func TestReportEcosystemUsageError(t *testing.T) { // No features reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToArtifactory(false).SetSendToXray(false) diff --git a/xray/utils/analyzermanager.go b/xray/utils/analyzermanager.go index 51087cae5..50a4cbcfd 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -23,7 +23,7 @@ const ( EntitlementsMinVersion = "3.66.5" ApplicabilityFeatureId = "contextual_analysis" AnalyzerManagerZipName = "analyzerManager.zip" - defaultAnalyzerManagerVersion = "1.2.4.2000151" + defaultAnalyzerManagerVersion = "1.3.2.2005632" minAnalyzerManagerVersionForSast = "1.3" analyzerManagerDownloadPath = "xsc-gen-exe-analyzer-manager-local/v1" analyzerManagerDirName = "analyzerManager" @@ -99,7 +99,7 @@ func (am *AnalyzerManager) Exec(configFile, scanCommand, workingDir string, serv } cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, am.MultiScanId) defer func() { - if !cmd.ProcessState.Exited() { + if cmd.ProcessState != nil && !cmd.ProcessState.Exited() { if killProcessError := cmd.Process.Kill(); errorutils.CheckError(killProcessError) != nil { err = errors.Join(err, killProcessError) } From 597ddcdcce8d3a38a1654a37661620ce6e41ebc5 Mon Sep 17 00:00:00 2001 From: yahavi Date: Wed, 11 Oct 2023 10:26:12 +0300 Subject: [PATCH 6/9] Upgrade jfrog-cli-core to 2.45.1 From 45beee8fdff0ba05436d350748ba6952ba68a270 Mon Sep 17 00:00:00 2001 From: yahavi Date: Wed, 18 Oct 2023 10:46:25 +0300 Subject: [PATCH 7/9] Upgrade jfrog-cli-core to 2.45.3 From 5e828b6d7b52c9dac9b036462e0ea225361532e7 Mon Sep 17 00:00:00 2001 From: Eran Turgeman <81029514+eranturgeman@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:52:51 +0200 Subject: [PATCH 8/9] Upgrade jfrog-cli-core to 2.47.12 (#1119) --- artifactory/commands/npm/npmcommand.go | 14 +++++ artifactory/commands/npm/npmcommand_test.go | 40 ++++++++++++ common/project/projectconfig.go | 7 +-- tests/testdata/npm-project/helloworld.js | 6 ++ tests/testdata/npm-project/package.json | 14 +++++ utils/java/deptreemanager.go | 4 ++ utils/java/mvn.go | 67 +++++++++++++++------ utils/java/mvn_test.go | 5 +- 8 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 tests/testdata/npm-project/helloworld.js create mode 100644 tests/testdata/npm-project/package.json diff --git a/artifactory/commands/npm/npmcommand.go b/artifactory/commands/npm/npmcommand.go index 9832b6db3..f3d477c97 100644 --- a/artifactory/commands/npm/npmcommand.go +++ b/artifactory/commands/npm/npmcommand.go @@ -390,3 +390,17 @@ func filterFlags(splitArgs []string) []string { func (nc *NpmCommand) GetRepo() string { return nc.repo } + +// Creates an .npmrc file in the project's directory in order to configure the provided Artifactory server as a resolution server +func SetArtifactoryAsResolutionServer(serverDetails *config.ServerDetails, depsRepo string) (clearResolutionServerFunc func() error, err error) { + npmCmd := NewNpmInstallCommand().SetServerDetails(serverDetails) + if err = npmCmd.PreparePrerequisites(depsRepo); err != nil { + return + } + if err = npmCmd.CreateTempNpmrc(); err != nil { + return + } + clearResolutionServerFunc = npmCmd.RestoreNpmrcFunc() + log.Info(fmt.Sprintf("Resolving dependencies from '%s' from repo '%s'", serverDetails.Url, depsRepo)) + return +} diff --git a/artifactory/commands/npm/npmcommand_test.go b/artifactory/commands/npm/npmcommand_test.go index 170e78904..57898f2b9 100644 --- a/artifactory/commands/npm/npmcommand_test.go +++ b/artifactory/commands/npm/npmcommand_test.go @@ -2,10 +2,15 @@ package npm import ( "fmt" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/version" + commonTests "github.com/jfrog/jfrog-cli-core/v2/common/tests" + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" testsUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "net/http" "os" + "path/filepath" "strings" "testing" ) @@ -97,3 +102,38 @@ func TestSetNpmConfigAuthEnv(t *testing.T) { }) } } + +func TestSetArtifactoryAsResolutionServer(t *testing.T) { + tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + + npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-project") + err := biutils.CopyDir(npmProjectPath, tmpDir, false, nil) + assert.NoError(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir) + defer chdirCallback() + + // Prepare mock server + testServer, serverDetails, _ := commonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/system/version" { + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("{\"version\" : \"7.75.4\"}")) + assert.NoError(t, err) + } + }) + defer testServer.Close() + + depsRepo := "my-rt-resolution-repo" + + clearResolutionServerFunc, err := SetArtifactoryAsResolutionServer(serverDetails, depsRepo) + assert.NoError(t, err) + assert.NotNil(t, clearResolutionServerFunc) + defer func() { + assert.NoError(t, clearResolutionServerFunc()) + }() + + assert.FileExists(t, filepath.Join(tmpDir, ".npmrc")) +} diff --git a/common/project/projectconfig.go b/common/project/projectconfig.go index c6bf9d129..89bc25309 100644 --- a/common/project/projectconfig.go +++ b/common/project/projectconfig.go @@ -1,6 +1,7 @@ package project import ( + "errors" "fmt" "os" "path/filepath" @@ -128,11 +129,7 @@ func GetProjectConfFilePath(projectType ProjectType) (confFilePath string, exist func GetRepoConfigByPrefix(configFilePath, prefix string, vConfig *viper.Viper) (repoConfig *RepositoryConfig, err error) { defer func() { if err != nil { - err = fmt.Errorf("%s\nPlease run 'jf %s-config' with your %s repository information", - err.Error(), - vConfig.GetString("type"), - prefix, - ) + err = errors.Join(err, fmt.Errorf("please run 'jf %s-config' with your %s repository information", vConfig.GetString("type"), prefix)) } }() if !vConfig.IsSet(prefix) { diff --git a/tests/testdata/npm-project/helloworld.js b/tests/testdata/npm-project/helloworld.js new file mode 100644 index 000000000..858d2c9c9 --- /dev/null +++ b/tests/testdata/npm-project/helloworld.js @@ -0,0 +1,6 @@ +var http = require('http'); + +http.createServer(function(req, res) { + res.writeHead(200, {'Content-Type' : 'text/plain'}); + res.end('Hello World\n'); +}).listen(1337, '127.0.0.1'); diff --git a/tests/testdata/npm-project/package.json b/tests/testdata/npm-project/package.json new file mode 100644 index 000000000..742cd470e --- /dev/null +++ b/tests/testdata/npm-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "npm-example", + "version": "0.0.3", + "scripts": { + "start": "node helloworld" + }, + "dependencies": { + "chokidar": "^2.0.3", + "send": "^0.16.2" + }, + "devDependencies": { + "debug": "^4.1.1" + } +} diff --git a/utils/java/deptreemanager.go b/utils/java/deptreemanager.go index ef8fa0724..071e2192d 100644 --- a/utils/java/deptreemanager.go +++ b/utils/java/deptreemanager.go @@ -125,3 +125,7 @@ func getArtifactoryAuthFromServer(server *config.ServerDetails) (string, string, } return username, password, nil } + +func (dtm *DepTreeManager) GetDepsRepo() string { + return dtm.depsRepo +} diff --git a/utils/java/mvn.go b/utils/java/mvn.go index 5431a4408..56deee17c 100644 --- a/utils/java/mvn.go +++ b/utils/java/mvn.go @@ -61,39 +61,39 @@ func NewMavenDepTreeManager(params *DepTreeParams, cmdName MavenDepTreeCmd, isDe func buildMavenDependencyTree(params *DepTreeParams, isDepTreeInstalled bool) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps []string, err error) { manager := NewMavenDepTreeManager(params, Tree, isDepTreeInstalled) - outputFilePaths, err := manager.RunMavenDepTree() + outputFilePaths, clearMavenDepTreeRun, err := manager.RunMavenDepTree() if err != nil { + if clearMavenDepTreeRun != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + } return } + + defer func() { + err = errors.Join(err, clearMavenDepTreeRun()) + }() + dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFilePaths) return } -func (mdt *MavenDepTreeManager) RunMavenDepTree() (string, error) { - // Create a temp directory for all the files that are required for the maven-dep-tree run - depTreeExecDir, err := fileutils.CreateTempDir() +// Runs maven-dep-tree according to cmdName. Returns the plugin output along with a function pointer to revert the plugin side effects. +// If a non-nil clearMavenDepTreeRun pointer is returnes it means we had no error during the entire function execution +func (mdt *MavenDepTreeManager) RunMavenDepTree() (depTreeOutput string, clearMavenDepTreeRun func() error, err error) { + // depTreeExecDir is a temp directory for all the files that are required for the maven-dep-tree run + depTreeExecDir, clearMavenDepTreeRun, err := mdt.CreateTempDirWithSettingsXmlIfNeeded() if err != nil { - return "", err - } - defer func() { - err = errors.Join(err, fileutils.RemoveTempDir(depTreeExecDir)) - }() - - // Create a settings.xml file that sets the dependency resolution from the given server and repository - if mdt.depsRepo != "" { - if err = mdt.createSettingsXmlWithConfiguredArtifactory(depTreeExecDir); err != nil { - return "", err - } + return } if err = mdt.installMavenDepTreePlugin(depTreeExecDir); err != nil { - return "", err + return } - depTreeOutput, err := mdt.execMavenDepTree(depTreeExecDir) + depTreeOutput, err = mdt.execMavenDepTree(depTreeExecDir) if err != nil { - return "", err + return } - return depTreeOutput, nil + return } func (mdt *MavenDepTreeManager) installMavenDepTreePlugin(depTreeExecDir string) error { @@ -170,6 +170,14 @@ func (mdt *MavenDepTreeManager) RunMvnCmd(goals []string) (cmdOutput []byte, err return } +func (mdt *MavenDepTreeManager) GetSettingsXmlPath() string { + return mdt.settingsXmlPath +} + +func (mdt *MavenDepTreeManager) SetSettingsXmlPath(settingsXmlPath string) { + mdt.settingsXmlPath = settingsXmlPath +} + func removeMavenConfig() (func() error, error) { mavenConfigExists, err := fileutils.IsFileExists(mavenConfigPath, false) if err != nil { @@ -205,3 +213,24 @@ func (mdt *MavenDepTreeManager) createSettingsXmlWithConfiguredArtifactory(path return errorutils.CheckError(os.WriteFile(mdt.settingsXmlPath, []byte(settingsXmlContent), 0600)) } + +// Creates a temporary directory. +// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temprary directory. +func (mdt *MavenDepTreeManager) CreateTempDirWithSettingsXmlIfNeeded() (tempDirPath string, clearMavenDepTreeRun func() error, err error) { + tempDirPath, err = fileutils.CreateTempDir() + if err != nil { + return + } + + clearMavenDepTreeRun = func() error { return fileutils.RemoveTempDir(tempDirPath) } + + // Create a settings.xml file that sets the dependency resolution from the given server and repository + if mdt.depsRepo != "" { + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDirPath) + } + if err != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + clearMavenDepTreeRun = nil + } + return +} diff --git a/utils/java/mvn_test.go b/utils/java/mvn_test.go index ea3fe1f76..fb141a3d9 100644 --- a/utils/java/mvn_test.go +++ b/utils/java/mvn_test.go @@ -224,10 +224,13 @@ func TestRunProjectsCmd(t *testing.T) { _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example")) defer cleanUp() mvnDepTreeManager := NewMavenDepTreeManager(&DepTreeParams{}, Projects, false) - output, err := mvnDepTreeManager.RunMavenDepTree() + output, clearMavenDepTreeRun, err := mvnDepTreeManager.RunMavenDepTree() assert.NoError(t, err) + assert.NotNil(t, clearMavenDepTreeRun) + pomPathOccurrences := strings.Count(output, "pomPath") assert.Equal(t, 4, pomPathOccurrences) + assert.NoError(t, clearMavenDepTreeRun()) } func TestRemoveMavenConfig(t *testing.T) { From def18c58b32128e7b27151f20bd5e5114ba995f0 Mon Sep 17 00:00:00 2001 From: Robi Nino Date: Mon, 18 Mar 2024 16:38:29 +0200 Subject: [PATCH 9/9] Promoted version to 2.49.0 --- go.mod | 8 ++++---- go.sum | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 6ae768550..5d9f3fc81 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 github.com/jedib0t/go-pretty/v6 v6.5.4 - github.com/jfrog/build-info-go v1.9.23 + github.com/jfrog/build-info-go v1.9.24 github.com/jfrog/gofrog v1.6.3 - github.com/jfrog/jfrog-client-go v1.37.1 + github.com/jfrog/jfrog-client-go v1.38.0 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c @@ -96,8 +96,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240318124620-6267b7025659 +// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev -replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240311081927-91b472899d7d +// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.3.3-0.20231223133729-ef57bd08cedc diff --git a/go.sum b/go.sum index a9fb9a01b..ee7d37364 100644 --- a/go.sum +++ b/go.sum @@ -83,12 +83,12 @@ github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/ github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/jfrog/build-info-go v1.8.9-0.20240311081927-91b472899d7d h1:zYom4uvjHfO1ECk5aCTg6BTDKPVJZ/ddim9F+hzbmI8= -github.com/jfrog/build-info-go v1.8.9-0.20240311081927-91b472899d7d/go.mod h1:CaCKqcg3V2W9/ZysE4ZvXZMgsvunclhjrTTQQGp3CzM= +github.com/jfrog/build-info-go v1.9.24 h1:MjT+4bYecbNQ+dbLczg0lkE5DoLAhdyrF0cRXtnEJqI= +github.com/jfrog/build-info-go v1.9.24/go.mod h1:CaCKqcg3V2W9/ZysE4ZvXZMgsvunclhjrTTQQGp3CzM= github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240318124620-6267b7025659 h1:0tytbHFWEyDJrxu+PHN41nDQipUE8rDwldsl4kvC2qM= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240318124620-6267b7025659/go.mod h1:NB8tYFgkWtn+wHsKC+aYC75aLnS6yW81d8JAFTBxsi0= +github.com/jfrog/jfrog-client-go v1.38.0 h1:0QP4/dSmJe0oYUrAqzoPDpGdJHcrOeq9mycnb0pSxqQ= +github.com/jfrog/jfrog-client-go v1.38.0/go.mod h1:EHRLxpu0pIT7+ulYDNQ7IeieYBHMQeEPr8CoBHoJzQY= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=