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: