make cyclonedx presenter generally reusable (for grype)

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2020-11-16 13:59:15 -05:00
parent f46de19c6b
commit 4b45c42f5a
No known key found for this signature in database
GPG Key ID: 5CB45AE22BAB7EA7
6 changed files with 62 additions and 61 deletions

View File

@ -4,8 +4,7 @@ import (
"encoding/xml" "encoding/xml"
"time" "time"
"github.com/anchore/syft/internal" "github.com/anchore/syft/syft/source"
"github.com/anchore/syft/internal/version"
) )
// Source: https://cyclonedx.org/ext/bom-descriptor/ // Source: https://cyclonedx.org/ext/bom-descriptor/
@ -35,15 +34,34 @@ type BdComponent struct {
} }
// NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details. // NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details.
func NewBomDescriptor() *BomDescriptor { func NewBomDescriptor(name, version string, srcMetadata source.Metadata) *BomDescriptor {
versionInfo := version.FromBuild() descriptor := BomDescriptor{
return &BomDescriptor{
XMLName: xml.Name{}, XMLName: xml.Name{},
Timestamp: time.Now().Format(time.RFC3339), Timestamp: time.Now().Format(time.RFC3339),
Tool: &BdTool{ Tool: &BdTool{
Vendor: "anchore", Vendor: "anchore",
Name: internal.ApplicationName, Name: name,
Version: versionInfo.Version, Version: version,
}, },
} }
switch srcMetadata.Scheme {
case source.ImageScheme:
descriptor.Component = &BdComponent{
Component: Component{
Type: "container",
Name: srcMetadata.ImageMetadata.UserInput,
Version: srcMetadata.ImageMetadata.Digest,
},
}
case source.DirectoryScheme:
descriptor.Component = &BdComponent{
Component: Component{
Type: "file",
Name: srcMetadata.Path,
},
}
}
return &descriptor
} }

View File

@ -3,9 +3,11 @@ package cyclonedx
import ( import (
"encoding/xml" "encoding/xml"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/distro" "github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -22,19 +24,19 @@ type Document struct {
BomDescriptor *BomDescriptor `xml:"bd:metadata"` // The BOM descriptor extension BomDescriptor *BomDescriptor `xml:"bd:metadata"` // The BOM descriptor extension
} }
// NewDocument returns an empty CycloneDX Document object.
func NewDocument() Document {
return Document{
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
XMLNsBd: "http://cyclonedx.org/schema/ext/bom-descriptor/1.0",
Version: 1,
SerialNumber: uuid.New().URN(),
}
}
// NewDocumentFromCatalog returns a CycloneDX Document object populated with the catalog contents. // NewDocumentFromCatalog returns a CycloneDX Document object populated with the catalog contents.
func NewDocumentFromCatalog(catalog *pkg.Catalog, d distro.Distro) Document { func NewDocument(catalog *pkg.Catalog, d distro.Distro, srcMetadata source.Metadata) Document {
bom := NewDocument() versionInfo := version.FromBuild()
doc := Document{
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
XMLNsBd: "http://cyclonedx.org/schema/ext/bom-descriptor/1.0",
Version: 1,
SerialNumber: uuid.New().URN(),
BomDescriptor: NewBomDescriptor(internal.ApplicationName, versionInfo.Version, srcMetadata),
}
// attach components
for p := range catalog.Enumerate() { for p := range catalog.Enumerate() {
component := Component{ component := Component{
Type: "library", // TODO: this is not accurate Type: "library", // TODO: this is not accurate
@ -51,10 +53,8 @@ func NewDocumentFromCatalog(catalog *pkg.Catalog, d distro.Distro) Document {
if len(licenses) > 0 { if len(licenses) > 0 {
component.Licenses = &licenses component.Licenses = &licenses
} }
bom.Components = append(bom.Components, component) doc.Components = append(doc.Components, component)
} }
bom.BomDescriptor = NewBomDescriptor() return doc
return bom
} }

View File

@ -5,7 +5,6 @@ package cyclonedx
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"github.com/anchore/syft/syft/distro" "github.com/anchore/syft/syft/distro"
@ -22,39 +21,17 @@ type Presenter struct {
} }
// NewPresenter creates a CycloneDX presenter from the given Catalog and Locations objects. // NewPresenter creates a CycloneDX presenter from the given Catalog and Locations objects.
func NewPresenter(catalog *pkg.Catalog, s source.Metadata, d distro.Distro) *Presenter { func NewPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata, d distro.Distro) *Presenter {
return &Presenter{ return &Presenter{
catalog: catalog, catalog: catalog,
srcMetadata: s, srcMetadata: srcMetadata,
distro: d, distro: d,
} }
} }
// Present writes the CycloneDX report to the given io.Writer. // Present writes the CycloneDX report to the given io.Writer.
func (pres *Presenter) Present(output io.Writer) error { func (pres *Presenter) Present(output io.Writer) error {
bom := NewDocumentFromCatalog(pres.catalog, pres.distro) bom := NewDocument(pres.catalog, pres.distro, pres.srcMetadata)
switch pres.srcMetadata.Scheme {
case source.DirectoryScheme:
bom.BomDescriptor.Component = &BdComponent{
Component: Component{
Type: "file",
Name: pres.srcMetadata.Path,
Version: "",
},
}
case source.ImageScheme:
// TODO: can we use the tags a bit better?
bom.BomDescriptor.Component = &BdComponent{
Component: Component{
Type: "container",
Name: pres.srcMetadata.ImageMetadata.UserInput,
Version: pres.srcMetadata.ImageMetadata.Digest,
},
}
default:
return fmt.Errorf("unsupported source: %T", pres.srcMetadata.Scheme)
}
encoder := xml.NewEncoder(output) encoder := xml.NewEncoder(output)
encoder.Indent("", " ") encoder.Indent("", " ")

View File

@ -177,7 +177,7 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
dmp := diffmatchpatch.New() dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true) diffs := dmp.DiffMain(string(actual), string(expected), true)
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
} }
} }

View File

@ -1,8 +1,23 @@
package json package json
import "github.com/anchore/syft/syft/distro"
// Distribution provides information about a detected Linux Distribution // Distribution provides information about a detected Linux Distribution
type Distribution struct { type Distribution struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
IDLike string `json:"idLike"` IDLike string `json:"idLike"`
} }
func NewDistribution(d distro.Distro) Distribution {
distroName := d.Name()
if distroName == "UnknownDistroType" {
distroName = ""
}
return Distribution{
Name: distroName,
Version: d.FullVersion(),
IDLike: d.IDLike,
}
}

View File

@ -21,19 +21,10 @@ func NewDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d distro.Dis
return Document{}, nil return Document{}, nil
} }
distroName := d.Name()
if distroName == "UnknownDistroType" {
distroName = ""
}
doc := Document{ doc := Document{
Artifacts: make([]Artifact, 0), Artifacts: make([]Artifact, 0),
Source: src, Source: src,
Distro: Distribution{ Distro: NewDistribution(d),
Name: distroName,
Version: d.FullVersion(),
IDLike: d.IDLike,
},
Descriptor: Descriptor{ Descriptor: Descriptor{
Name: internal.ApplicationName, Name: internal.ApplicationName,
Version: version.FromBuild().Version, Version: version.FromBuild().Version,