Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(create): support multiple --product flags #216

Merged
merged 2 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ to preserve the original file, specify it using the --file flag:
}
opts.documentPath = args[i]
case 1:
if opts.Product != "" && opts.Product != args[i] {
if len(opts.Products) != 1 && len(args) != 1 {
return errors.New("product can only be specified once")
}
opts.Product = args[i]
opts.Products = append(opts.Products, args[i])
case 2:
if opts.Vulnerability != "" && opts.Vulnerability != args[i] {
return errors.New("vulnerability can only be specified once")
Expand Down
10 changes: 7 additions & 3 deletions internal/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,14 @@ Examples:
for i := range args {
switch i {
case 0:
if opts.Product != "" && opts.Product != args[i] {
return errors.New("product can only be specified once")
if len(opts.Products) > 0 && args[i] != "" {
return errors.New("multiple products can only be specified using the --product flag")
}
opts.Product = args[i]
// Specifying multiple products through args is not supported as we can't tell how many products are provided:
// e.g the second argument could be a vulnerability or a status instead of a product, for example.
// When using args only the first one is considered a product.
// To specify multiple products, use the --product flag multiple times instead.
opts.Products = append(opts.Products, args[i])
case 1:
if opts.Vulnerability != "" && opts.Vulnerability != args[i] {
return errors.New("vulnerability can only be specified once")
Expand Down
39 changes: 22 additions & 17 deletions internal/cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type vexStatementOptions struct {
ImpactStatement string
Vulnerability string
ActionStatement string
Product string
Products []string
Subcomponents []string
}

Expand All @@ -80,8 +80,12 @@ func (so *vexStatementOptions) Validate() error {
so.ActionStatement = ""
}

if so.Product == "" {
return errors.New("a required product id is needed to generate a valid VEX statement")
if len(so.Products) == 0 {
return errors.New("a minimum of one product id is needed to generate a valid VEX statement")
}

if len(so.Products) > 1 && len(so.Subcomponents) > 0 {
return errors.New("subcomponent(s) cannot be defined when specifying multiple products because it's unclear which subcomponent(s) belong to which products")
}

if so.Vulnerability == "" {
Expand Down Expand Up @@ -115,12 +119,12 @@ func (so *vexStatementOptions) AddFlags(cmd *cobra.Command) {
"vulnerability to add to the statement (eg CVE-2023-12345)",
)

cmd.PersistentFlags().StringVarP(
&so.Product,
cmd.PersistentFlags().StringSliceVarP(
&so.Products,
productLongFlag,
"p",
"",
"main identifier of the product, a package URL or another IRI",
[]string{},
"list of products to list in the statement, at least one is required (main identifier of the product, a package URL or another IRI)",
)

cmd.PersistentFlags().StringVarP(
Expand Down Expand Up @@ -177,16 +181,8 @@ func (so *vexStatementOptions) ToStatement() vex.Statement {
Vulnerability: vex.Vulnerability{
Name: vex.VulnerabilityID(so.Vulnerability),
},
Timestamp: &t,
LastUpdated: nil,
Products: []vex.Product{
{
Component: vex.Component{
ID: so.Product,
},
Subcomponents: []vex.Subcomponent{},
},
},
Timestamp: &t,
LastUpdated: nil,
Status: vex.Status(so.Status),
StatusNotes: so.StatusNotes,
Justification: vex.Justification(so.Justification),
Expand All @@ -199,6 +195,15 @@ func (so *vexStatementOptions) ToStatement() vex.Statement {
s.ActionStatementTimestamp = &t
}

for _, id := range so.Products {
s.Products = append(s.Products, vex.Product{
Component: vex.Component{
ID: id,
},
Subcomponents: []vex.Subcomponent{},
})
}

for _, sc := range so.Subcomponents {
s.Products[0].Subcomponents = append(s.Products[0].Subcomponents, vex.Subcomponent{
Component: vex.Component{ID: sc},
Expand Down
26 changes: 17 additions & 9 deletions internal/cmd/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,51 +54,59 @@ func TestVexStatementOptionsValidate(t *testing.T) {
},
"empty product": {
vexStatementOptions{
Status: string(vex.StatusUnderInvestigation),
Product: "",
Status: string(vex.StatusUnderInvestigation),
Products: []string{},
}, true,
},
"empty vulnerability": {
vexStatementOptions{
Status: string(vex.StatusUnderInvestigation),
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "",
}, true,
},
"empty status": {
vexStatementOptions{
Status: "",
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
}, true,
},
"invalid status": {
vexStatementOptions{
Status: "cheese",
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
}, true,
},
"justification on non-not-affected": {
vexStatementOptions{
Status: string(vex.StatusUnderInvestigation),
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
Justification: "justification goes here",
}, true,
},
"impact statement on non-not-affected": {
vexStatementOptions{
Status: string(vex.StatusUnderInvestigation),
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
ImpactStatement: "buffer underrun is never run under",
}, true,
},
"can't associate a subcomponent when multiple products are defined": {
vexStatementOptions{
Status: string(vex.StatusNotAffected),
Products: []string{"pkg:oci/alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", "pkg:oci/busybox@sha256:c6ab5a1a2bc330f3f9616b20c7fba7716cadd941514cf80f8d7c3da8af6b0946"},
Subcomponents: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
}, true,
},
"ok": {
vexStatementOptions{
Status: string(vex.StatusUnderInvestigation),
Product: "pkg:golang/fmt",
Products: []string{"pkg:golang/fmt"},
Vulnerability: "CVE-2014-12345678",
}, false,
},
Expand All @@ -114,7 +122,7 @@ func TestAddOptionsValidate(t *testing.T) {
stubOpts := vexStatementOptions{
Status: "fixed",
Vulnerability: "CVE-2014-1234678",
Product: "pkg:generic/[email protected]",
Products: []string{"pkg:generic/[email protected]"},
}

// create a test directory and a file in it
Expand Down
Loading