Skip to content
Open
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
29 changes: 28 additions & 1 deletion grype/presenter/cyclonedx/presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Presenter struct {
document models.Document
src source.Description
format cyclonedx.BOMFileFormat
version cyclonedx.SpecVersion
sbom *sbom.SBOM
}

Expand All @@ -28,6 +29,7 @@ func NewJSONPresenter(pb models.PresenterConfig) *Presenter {
document: pb.Document,
src: pb.SBOM.Source,
sbom: pb.SBOM,
version: cyclonedx.SpecVersion1_6,
format: cyclonedx.BOMFileFormatJSON,
}
}
Expand All @@ -39,6 +41,31 @@ func NewXMLPresenter(pb models.PresenterConfig) *Presenter {
document: pb.Document,
src: pb.SBOM.Source,
sbom: pb.SBOM,
version: cyclonedx.SpecVersion1_6,
format: cyclonedx.BOMFileFormatXML,
}
}

// NewJSONPresenterv1_5 is a *Presenter constructor
func NewJSONPresenterv1_5(pb models.PresenterConfig) *Presenter {
return &Presenter{
id: pb.ID,
document: pb.Document,
src: pb.SBOM.Source,
sbom: pb.SBOM,
version: cyclonedx.SpecVersion1_5,
format: cyclonedx.BOMFileFormatJSON,
}
}

// NewXMLPresenterv1_5 is a *Presenter constructor
func NewXMLPresenterv1_5(pb models.PresenterConfig) *Presenter {
return &Presenter{
id: pb.ID,
document: pb.Document,
src: pb.SBOM.Source,
sbom: pb.SBOM,
version: cyclonedx.SpecVersion1_5,
format: cyclonedx.BOMFileFormatXML,
}
}
Expand Down Expand Up @@ -74,5 +101,5 @@ func (p *Presenter) Present(output io.Writer) error {
enc.SetPretty(true)
enc.SetEscapeHTML(false)

return enc.EncodeVersion(cyclonedxBOM, cyclonedxBOM.SpecVersion)
return enc.EncodeVersion(cyclonedxBOM, p.version)
}
72 changes: 47 additions & 25 deletions internal/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,74 @@ import (
"strings"
)

const (
UnknownFormat Format = "unknown"
JSONFormat Format = "json"
TableFormat Format = "table"
CycloneDXFormat Format = "cyclonedx"
CycloneDXJSON Format = "cyclonedx-json"
CycloneDXXML Format = "cyclonedx-xml"
SarifFormat Format = "sarif"
TemplateFormat Format = "template"
var (
UnknownFormat = Format{name: "unknown", version: ""}
JSONFormat = Format{name: "json", version: ""}
TableFormat = Format{name: "table", version: ""}
CycloneDXFormat = Format{name: "cyclonedx", version: ""}
CycloneDXJSON = Format{name: "cyclonedx-json", version: ""}
CycloneDXXML = Format{name: "cyclonedx-xml", version: ""}
CycloneDXFormatv1_5 = Format{name: "cyclonedx", version: "1.5"}
CycloneDXJSONv1_5 = Format{name: "cyclonedx-json", version: "1.5"}
CycloneDXXMLv1_5 = Format{name: "cyclonedx-xml", version: "1.5"}
SarifFormat = Format{name: "sarif", version: ""}
TemplateFormat = Format{name: "template", version: ""}

// DEPRECATED <-- TODO: remove in v1.0
EmbeddedVEXJSON Format = "embedded-cyclonedx-vex-json"
EmbeddedVEXXML Format = "embedded-cyclonedx-vex-xml"
EmbeddedVEXJSON = Format{name: "embedded-cyclonedx-vex-json", version: ""}
EmbeddedVEXXML = Format{name: "embedded-cyclonedx-vex-xml", version: ""}
)

// Format is a dedicated type to represent a specific kind of presenter output format.
type Format string
type Format struct {
name string
version string
}

func (f Format) String() string {
return string(f)
if f.version != "" {
return f.name + "@" + f.version
}
return f.name
}

// Parse returns the presenter.format specified by the given user input.
func Parse(userInput string) Format {
switch strings.ToLower(userInput) {
parts := strings.SplitN(userInput, "@", 2)
version := ""

if len(parts) > 1 {
version = parts[1]
}

result := UnknownFormat
switch strings.ToLower(parts[0]) {
case "":
return TableFormat
result = TableFormat
case strings.ToLower(JSONFormat.String()):
return JSONFormat
result = JSONFormat
case strings.ToLower(TableFormat.String()):
return TableFormat
result = TableFormat
case strings.ToLower(SarifFormat.String()):
return SarifFormat
result = SarifFormat
case strings.ToLower(TemplateFormat.String()):
return TemplateFormat
result = TemplateFormat
case strings.ToLower(CycloneDXFormat.String()):
return CycloneDXFormat
result = CycloneDXFormat
case strings.ToLower(CycloneDXJSON.String()):
return CycloneDXJSON
result = CycloneDXJSON
case strings.ToLower(CycloneDXXML.String()):
return CycloneDXXML
result = CycloneDXXML
case strings.ToLower(EmbeddedVEXJSON.String()):
return CycloneDXJSON
result = CycloneDXJSON
case strings.ToLower(EmbeddedVEXXML.String()):
return CycloneDXFormat
result = CycloneDXFormat
default:
return UnknownFormat
result = UnknownFormat
}

result.version = version
return result
}

// AvailableFormats is a list of presenter format options available to users.
Expand All @@ -60,6 +80,8 @@ var AvailableFormats = []Format{
TableFormat,
CycloneDXFormat,
CycloneDXJSON,
CycloneDXFormatv1_5,
CycloneDXJSONv1_5,
SarifFormat,
TemplateFormat,
}
Expand Down
6 changes: 6 additions & 0 deletions internal/format/presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ func GetPresenter(format Format, c PresentationConfig, pb models.PresenterConfig
return cyclonedx.NewJSONPresenter(pb)
case CycloneDXXML:
return cyclonedx.NewXMLPresenter(pb)
case CycloneDXFormatv1_5:
return cyclonedx.NewXMLPresenterv1_5(pb)
case CycloneDXJSONv1_5:
return cyclonedx.NewJSONPresenterv1_5(pb)
case CycloneDXXMLv1_5:
return cyclonedx.NewXMLPresenterv1_5(pb)
case SarifFormat:
return sarif.NewPresenter(pb)
case TemplateFormat:
Expand Down
3 changes: 3 additions & 0 deletions internal/format/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ type scanResultStreamWriter struct {
// Write the provided result to the data stream
func (w *scanResultStreamWriter) Write(s models.PresenterConfig) error {
pres := GetPresenter(w.format, w.cfg, s)
if pres == nil {
return fmt.Errorf("invalid format: %s", w.format)
}
if err := pres.Present(w.out); err != nil {
return fmt.Errorf("unable to encode result: %w", err)
}
Expand Down
22 changes: 11 additions & 11 deletions internal/format/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func Test_newSBOMMultiWriter(t *testing.T) {
testName := func(options []scanResultWriterDescription, err bool) string {
var out []string
for _, opt := range options {
out = append(out, string(opt.Format)+"="+opt.Path)
out = append(out, opt.Format.String()+"="+opt.Path)
}
errs := ""
if err {
Expand All @@ -68,7 +68,7 @@ func Test_newSBOMMultiWriter(t *testing.T) {
{
outputs: []scanResultWriterDescription{
{
Format: "table",
Format: Format{name: "table", version: ""},
Path: "",
},
},
Expand All @@ -81,7 +81,7 @@ func Test_newSBOMMultiWriter(t *testing.T) {
{
outputs: []scanResultWriterDescription{
{
Format: "json",
Format: Format{name: "json", version: ""},
},
},
expected: []writerConfig{
Expand All @@ -93,7 +93,7 @@ func Test_newSBOMMultiWriter(t *testing.T) {
{
outputs: []scanResultWriterDescription{
{
Format: "json",
Format: Format{name: "json", version: ""},
Path: "test-2.json",
},
},
Expand All @@ -107,11 +107,11 @@ func Test_newSBOMMultiWriter(t *testing.T) {
{
outputs: []scanResultWriterDescription{
{
Format: "json",
Format: Format{name: "json", version: ""},
Path: "test-3/1.json",
},
{
Format: "spdx-json",
Format: Format{name: "spdx-json", version: ""},
Path: "test-3/2.json",
},
},
Expand All @@ -129,10 +129,10 @@ func Test_newSBOMMultiWriter(t *testing.T) {
{
outputs: []scanResultWriterDescription{
{
Format: "text",
Format: Format{name: "text", version: ""},
},
{
Format: "spdx-json",
Format: Format{name: "spdx-json", version: ""},
Path: "test-4.json",
},
},
Expand Down Expand Up @@ -171,13 +171,13 @@ func Test_newSBOMMultiWriter(t *testing.T) {
for i, e := range test.expected {
switch w := mw.writers[i].(type) {
case *scanResultStreamWriter:
assert.Equal(t, string(w.format), e.format)
assert.Equal(t, w.format.String(), e.format)
assert.NotNil(t, w.out)
if e.file != "" {
assert.FileExists(t, tmp+e.file)
}
case *scanResultPublisher:
assert.Equal(t, string(w.format), e.format)
assert.Equal(t, w.format.String(), e.format)
default:
t.Fatalf("unknown writer type: %T", w)
}
Expand Down Expand Up @@ -211,7 +211,7 @@ func Test_newSBOMWriterDescription(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := newWriterDescription("table", tt.path, PresentationConfig{})
o := newWriterDescription(Format{name: "table", version: ""}, tt.path, PresentationConfig{})
assert.Equal(t, tt.expected, o.Path)
})
}
Expand Down