Skip to content

Commit b0415ad

Browse files
committed
JGC-480 - Add --format flag to sbom-enrich
1 parent b3269a8 commit b0415ad

7 files changed

Lines changed: 165 additions & 24 deletions

File tree

cli/gitcommands.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"strings"
66

77
"github.com/jfrog/froggit-go/vcsutils"
8-
outputFormat "github.com/jfrog/jfrog-cli-core/v2/common/format"
98
"github.com/jfrog/jfrog-cli-core/v2/common/progressbar"
109
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
1110
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
@@ -14,6 +13,7 @@ import (
1413
gitContributorsDocs "github.com/jfrog/jfrog-cli-security/cli/docs/git/contributors"
1514
"github.com/jfrog/jfrog-cli-security/commands/git/audit"
1615
"github.com/jfrog/jfrog-cli-security/commands/git/contributors"
16+
"github.com/jfrog/jfrog-cli-security/utils/formats"
1717
"github.com/jfrog/jfrog-cli-security/utils/xsc"
1818
"github.com/jfrog/jfrog-client-go/utils/errorutils"
1919
"github.com/jfrog/jfrog-client-go/utils/log"
@@ -56,7 +56,7 @@ func GitAuditCmd(c *components.Context) error {
5656
}
5757
gitAuditCmd.SetServerDetails(serverDetails).SetXrayVersion(xrayVersion).SetXscVersion(xscVersion)
5858
// Set violations params
59-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
59+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
6060
if err != nil {
6161
return err
6262
}

cli/scancommands.go

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/jfrog/jfrog-cli-security/commands/sast_server"
4343
"github.com/jfrog/jfrog-cli-security/commands/source_mcp"
4444
"github.com/jfrog/jfrog-cli-security/sca/bom/indexer"
45+
"github.com/jfrog/jfrog-cli-security/utils/formats"
4546
"github.com/jfrog/jfrog-cli-security/utils/xray"
4647

4748
"github.com/jfrog/jfrog-cli-security/commands/audit"
@@ -56,8 +57,10 @@ import (
5657
"github.com/jfrog/jfrog-cli-security/utils/xsc"
5758
)
5859

59-
const DockerScanCmdHiddenName = "dockerscan"
60-
const SkipCurationAfterFailureEnv = "JFROG_CLI_SKIP_CURATION_AFTER_FAILURE"
60+
const (
61+
DockerScanCmdHiddenName = "dockerscan"
62+
SkipCurationAfterFailureEnv = "JFROG_CLI_SKIP_CURATION_AFTER_FAILURE"
63+
)
6164

6265
func getAuditAndScansCommands() []components.Command {
6366
return []components.Command{
@@ -71,13 +74,14 @@ func getAuditAndScansCommands() []components.Command {
7174
Action: ScanCmd,
7275
},
7376
{
74-
Name: "sbom-enrich",
75-
Aliases: []string{"se"},
76-
Flags: flags.GetCommandFlags(flags.Enrich),
77-
Description: enrichDocs.GetDescription(),
78-
Arguments: enrichDocs.GetArguments(),
79-
Category: securityCategory,
80-
Action: EnrichCmd,
77+
Name: "sbom-enrich",
78+
Aliases: []string{"se"},
79+
Flags: flags.GetCommandFlags(flags.Enrich),
80+
Description: enrichDocs.GetDescription(),
81+
Arguments: enrichDocs.GetArguments(),
82+
Category: securityCategory,
83+
SupportedFormats: []outputFormat.OutputFormat{outputFormat.Json, outputFormat.Table},
84+
Action: EnrichCmd,
8185
},
8286
{
8387
Name: "malicious-scan",
@@ -211,7 +215,6 @@ func getAuditAndScansCommands() []components.Command {
211215
}
212216

213217
func SourceMcpCmd(c *components.Context) error {
214-
215218
serverDetails, err := CreateServerDetailsFromFlags(c)
216219
if err != nil {
217220
return err
@@ -266,11 +269,16 @@ func EnrichCmd(c *components.Context) error {
266269
if err != nil {
267270
return err
268271
}
269-
EnrichCmd := enrich.NewEnrichCommand().
272+
format, err := c.GetOutputFormat()
273+
if err != nil {
274+
return err
275+
}
276+
enrichCmd := enrich.NewEnrichCommand().
270277
SetServerDetails(serverDetails).
271278
SetThreads(threads).
272-
SetSpec(specFile)
273-
return commandsCommon.Exec(EnrichCmd)
279+
SetSpec(specFile).
280+
SetOutputFormat(format)
281+
return commandsCommon.Exec(enrichCmd)
274282
}
275283

276284
func MaliciousScanCmd(c *components.Context) error {
@@ -281,7 +289,7 @@ func MaliciousScanCmd(c *components.Context) error {
281289
if err = validateConnectionInputs(serverDetails); err != nil {
282290
return err
283291
}
284-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
292+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
285293
if err != nil {
286294
return err
287295
}
@@ -318,7 +326,7 @@ func ScanCmd(c *components.Context) error {
318326
if err != nil {
319327
return err
320328
}
321-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
329+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
322330
if err != nil {
323331
return err
324332
}
@@ -437,7 +445,7 @@ func BuildScan(c *components.Context) error {
437445
if err != nil {
438446
return err
439447
}
440-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
448+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
441449
if err != nil {
442450
return err
443451
}
@@ -526,7 +534,7 @@ func CreateAuditCmd(c *components.Context) (string, string, *coreConfig.ServerDe
526534
if err != nil {
527535
return "", "", nil, nil, err
528536
}
529-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
537+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
530538
if err != nil {
531539
return "", "", nil, nil, err
532540
}
@@ -789,7 +797,7 @@ func DockerScan(c *components.Context, image string) error {
789797
if err != nil {
790798
return err
791799
}
792-
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
800+
format, err := formats.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
793801
if err != nil {
794802
return err
795803
}

commands/enrich/enrich.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"encoding/xml"
55
"errors"
66
"fmt"
7+
"io"
78
"os"
89
"os/exec"
910
"path/filepath"
11+
"text/tabwriter"
1012

13+
coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format"
1114
"github.com/jfrog/jfrog-cli-security/utils/results/output"
1215
"github.com/jfrog/jfrog-client-go/utils/errorutils"
1316

@@ -31,14 +34,22 @@ import (
3134
orderedJson "github.com/virtuald/go-ordered-json"
3235
)
3336

34-
type FileContext func(string) parallel.TaskFunc
35-
type indexFileHandlerFunc func(file string)
37+
type (
38+
FileContext func(string) parallel.TaskFunc
39+
indexFileHandlerFunc func(file string)
40+
)
3641

3742
type EnrichCommand struct {
3843
serverDetails *config.ServerDetails
3944
spec *spec.SpecFiles
4045
threads int
4146
progress ioUtils.ProgressMgr
47+
outputFormat coreformat.OutputFormat
48+
}
49+
50+
func (enrichCmd *EnrichCommand) SetOutputFormat(format coreformat.OutputFormat) *EnrichCommand {
51+
enrichCmd.outputFormat = format
52+
return enrichCmd
4253
}
4354

4455
func (enrichCmd *EnrichCommand) SetProgress(progress ioUtils.ProgressMgr) {
@@ -180,7 +191,6 @@ func (enrichCmd *EnrichCommand) Run() (err error) {
180191
if err = enrichCmd.progress.Quit(); err != nil {
181192
return err
182193
}
183-
184194
}
185195

186196
fileCollectingErr := fileCollectingErrorsQueue.GetError()
@@ -192,6 +202,13 @@ func (enrichCmd *EnrichCommand) Run() (err error) {
192202
return errorutils.CheckError(scanResults.GetErrors())
193203
}
194204

205+
if enrichCmd.outputFormat == coreformat.Table {
206+
if err = enrichCmd.printVulnerabilitiesTable(scanResults, os.Stdout); err != nil {
207+
return
208+
}
209+
log.Info("Enrich process completed successfully.")
210+
return
211+
}
195212
isXml, err := isXML(scanResults.Targets)
196213
if err != nil {
197214
return
@@ -217,6 +234,25 @@ func (enrichCmd *EnrichCommand) CommandName() string {
217234
return "xr_enrich"
218235
}
219236

237+
func (enrichCmd *EnrichCommand) printVulnerabilitiesTable(cmdResults *results.SecurityCommandResults, w io.Writer) error {
238+
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
239+
fmt.Fprintln(tw, "COMPONENT\tCVE-ID")
240+
for _, xrayResult := range cmdResults.GetScaScansXrayResults() {
241+
for _, vuln := range xrayResult.Vulnerabilities {
242+
cveID := ""
243+
if len(vuln.Cves) > 0 {
244+
cveID = vuln.Cves[0].Id
245+
}
246+
for component := range vuln.Components {
247+
if _, err := fmt.Fprintf(tw, "%s\t%s\n", component, cveID); err != nil {
248+
return err
249+
}
250+
}
251+
}
252+
}
253+
return tw.Flush()
254+
}
255+
220256
func (enrichCmd *EnrichCommand) prepareScanTasks(fileProducer, indexedFileProducer parallel.Runner, cmdResults *results.SecurityCommandResults, fileCollectingErrorsQueue *clientutils.ErrorsQueue, xrayVersion string) {
221257
go func() {
222258
defer fileProducer.Done()

commands/enrich/enrich_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package enrich
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format"
9+
"github.com/jfrog/jfrog-cli-security/utils"
10+
"github.com/jfrog/jfrog-cli-security/utils/results"
11+
"github.com/jfrog/jfrog-client-go/xray/services"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func makeCmdResultsWithVulns(vulns []services.Vulnerability) *results.SecurityCommandResults {
17+
cmdResults := results.NewCommandResults(utils.SBOM)
18+
target := cmdResults.NewScanResults(results.ScanTarget{Target: "test.json", Name: "test.json"})
19+
target.ScaScanResults(0, services.ScanResponse{Vulnerabilities: vulns})
20+
return cmdResults
21+
}
22+
23+
func TestPrintVulnerabilitiesTable_WithFindings(t *testing.T) {
24+
cmdResults := makeCmdResultsWithVulns([]services.Vulnerability{
25+
{
26+
Cves: []services.Cve{{Id: "CVE-2021-1234"}},
27+
Components: map[string]services.Component{"pkg:npm/lodash@4.17.11": {}},
28+
},
29+
{
30+
Cves: []services.Cve{{Id: "CVE-2020-9999"}},
31+
Components: map[string]services.Component{"pkg:npm/minimist@1.2.5": {}},
32+
},
33+
})
34+
35+
cmd := &EnrichCommand{outputFormat: coreformat.Table}
36+
var buf bytes.Buffer
37+
err := cmd.printVulnerabilitiesTable(cmdResults, &buf)
38+
require.NoError(t, err)
39+
40+
out := buf.String()
41+
assert.Contains(t, out, "COMPONENT")
42+
assert.Contains(t, out, "CVE-ID")
43+
assert.Contains(t, out, "pkg:npm/lodash@4.17.11")
44+
assert.Contains(t, out, "CVE-2021-1234")
45+
assert.Contains(t, out, "pkg:npm/minimist@1.2.5")
46+
assert.Contains(t, out, "CVE-2020-9999")
47+
}
48+
49+
func TestPrintVulnerabilitiesTable_Empty(t *testing.T) {
50+
cmdResults := makeCmdResultsWithVulns(nil)
51+
52+
cmd := &EnrichCommand{outputFormat: coreformat.Table}
53+
var buf bytes.Buffer
54+
err := cmd.printVulnerabilitiesTable(cmdResults, &buf)
55+
require.NoError(t, err)
56+
57+
out := buf.String()
58+
assert.Contains(t, out, "COMPONENT")
59+
assert.Contains(t, out, "CVE-ID")
60+
// no data rows
61+
lines := strings.Split(strings.TrimSpace(out), "\n")
62+
assert.Len(t, lines, 1)
63+
}
64+
65+
func TestPrintVulnerabilitiesTable_NoCves(t *testing.T) {
66+
cmdResults := makeCmdResultsWithVulns([]services.Vulnerability{
67+
{
68+
Cves: nil,
69+
Components: map[string]services.Component{"pkg:go/golang.org/x/net@v0.0.0-20210226": {}},
70+
},
71+
})
72+
73+
cmd := &EnrichCommand{outputFormat: coreformat.Table}
74+
var buf bytes.Buffer
75+
err := cmd.printVulnerabilitiesTable(cmdResults, &buf)
76+
require.NoError(t, err)
77+
78+
out := buf.String()
79+
assert.Contains(t, out, "pkg:go/golang.org/x/net@v0.0.0-20210226")
80+
// CVE-ID column is blank but row is present
81+
assert.True(t, strings.Count(out, "\n") >= 2)
82+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
github.com/jfrog/gofrog v1.7.6
1717
github.com/jfrog/jfrog-apps-config v1.0.1
1818
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260423195010-d7aa2c437305
19-
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260427010241-873f53d940b3
19+
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260428135824-dbef60cb4319
2020
github.com/jfrog/jfrog-client-go v1.55.1-0.20260428070955-750b933dc5c7
2121
github.com/magiconair/properties v1.8.10
2222
github.com/owenrumney/go-sarif/v3 v3.2.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260423195010-d7aa2c437305 h1:w
173173
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260423195010-d7aa2c437305/go.mod h1:6QJFQvde/CLnFeIIFOvm/6QuQr8OT1QWiTJAkQ+1Mnc=
174174
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260427010241-873f53d940b3 h1:LdLQQmhOMUfU+3x7wbtB7kY/Dd2LXKHz7CCUpHWn7uM=
175175
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260427010241-873f53d940b3/go.mod h1:qpD7einonjqskDTEyqeG3NzAbZO6se0s0Pet0ObBQ3I=
176+
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260428135824-dbef60cb4319 h1:3q0lNklwvW7icWAKR4cmEmwi3RNZEWtXRTuXOTFva64=
177+
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260428135824-dbef60cb4319/go.mod h1:qpD7einonjqskDTEyqeG3NzAbZO6se0s0Pet0ObBQ3I=
176178
github.com/jfrog/jfrog-client-go v1.55.1-0.20260428070955-750b933dc5c7 h1:MvHnFczVntYB/USj7/RRANvdWbTUcwEvXcIGr7lOyTc=
177179
github.com/jfrog/jfrog-client-go v1.55.1-0.20260428070955-750b933dc5c7/go.mod h1:sCE06+GngPoyrGO0c+vmhgMoVSP83UMNiZnIuNPzU8U=
178180
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=

utils/formats/output_format.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package formats
2+
3+
import (
4+
outputFormat "github.com/jfrog/jfrog-cli-core/v2/common/format"
5+
)
6+
7+
func GetOutputFormat(format string) (f outputFormat.OutputFormat, err error) {
8+
f = outputFormat.Table
9+
if format != "" {
10+
f, err = outputFormat.ParseOutputFormat(format, outputFormat.All)
11+
}
12+
return
13+
}

0 commit comments

Comments
 (0)