Add package URL support to the CycloneDX presenter (#164)

* add package URL support to the CycloneDX presenter

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* wrap license tags with licenses

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman
2020-08-30 21:40:19 -04:00
committed by GitHub
parent eda0f8c774
commit 8a4886ec0e
27 changed files with 669 additions and 116 deletions
+1
View File
@@ -14,6 +14,7 @@ type Component struct {
Version string `xml:"version"` // Required; The version of the component as defined by the project
Description string `xml:"description,omitempty"` // A description of the component
Licenses *[]License `xml:"licenses>license"` // A node describing zero or more license names, SPDX license IDs or expressions
PackageURL string `xml:"purl,omitempty"` // Specifies the package-url (PackageURL). The purl, if specified, must be valid and conform to the specification defined at: https://github.com/package-url/purl-spec
// TODO: scope, hashes, copyright, cpe, purl, swid, modified, pedigree, externalReferences
// TODO: add user-defined parameters for syft-specific values (image layer index, cataloger, location path, etc.)
}
+7 -4
View File
@@ -3,6 +3,8 @@ package cyclonedx
import (
"encoding/xml"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/google/uuid"
)
@@ -31,13 +33,14 @@ func NewDocument() Document {
}
// NewDocumentFromCatalog returns a CycloneDX Document object populated with the catalog contents.
func NewDocumentFromCatalog(catalog *pkg.Catalog) Document {
func NewDocumentFromCatalog(catalog *pkg.Catalog, d distro.Distro) Document {
bom := NewDocument()
for p := range catalog.Enumerate() {
component := Component{
Type: "library", // TODO: this is not accurate
Name: p.Name,
Version: p.Version,
Type: "library", // TODO: this is not accurate
Name: p.Name,
Version: p.Version,
PackageURL: p.PackageURL(d),
}
var licenses []License
for _, licenseName := range p.Licenses {
+6 -2
View File
@@ -8,6 +8,8 @@ import (
"fmt"
"io"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
)
@@ -16,19 +18,21 @@ import (
type Presenter struct {
catalog *pkg.Catalog
scope scope.Scope
distro distro.Distro
}
// NewPresenter creates a CycloneDX presenter from the given Catalog and Scope objects.
func NewPresenter(catalog *pkg.Catalog, s scope.Scope) *Presenter {
func NewPresenter(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) *Presenter {
return &Presenter{
catalog: catalog,
scope: s,
distro: d,
}
}
// Present writes the CycloneDX report to the given io.Writer.
func (pres *Presenter) Present(output io.Writer) error {
bom := NewDocumentFromCatalog(pres.catalog)
bom := NewDocumentFromCatalog(pres.catalog, pres.distro)
srcObj := pres.scope.Source()
+56 -8
View File
@@ -3,6 +3,7 @@ package cyclonedx
import (
"bytes"
"flag"
"github.com/anchore/syft/syft/distro"
"regexp"
"testing"
@@ -22,16 +23,21 @@ func TestCycloneDxDirsPresenter(t *testing.T) {
// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
Name: "package1",
Version: "1.0.1",
Type: pkg.DebPkg,
FoundBy: "the-cataloger-1",
Source: []file.Reference{
{Path: "/some/path/pkg1"},
},
Metadata: pkg.DpkgMetadata{
Package: "package1",
Version: "1.0.1",
Architecture: "amd64",
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Name: "package2",
Version: "2.0.1",
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
@@ -42,13 +48,24 @@ func TestCycloneDxDirsPresenter(t *testing.T) {
"MIT",
"Apache-v2",
},
Metadata: pkg.DpkgMetadata{
Package: "package2",
Version: "1.0.2",
Architecture: "amd64",
},
})
s, err := scope.NewScopeFromDir("/some/path")
if err != nil {
t.Fatal(err)
}
pres := NewPresenter(catalog, s)
d, err := distro.NewDistro(distro.Ubuntu, "20.04")
if err != nil {
t.Fatal(err)
}
pres := NewPresenter(catalog, s, d)
// run presenter
err = pres.Present(&buffer)
@@ -84,30 +101,61 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
Name: "package1",
Version: "1.0.1",
Source: []file.Reference{
*img.SquashedTree().File("/somefile-1.txt"),
},
Type: pkg.DebPkg,
Type: pkg.RpmPkg,
FoundBy: "the-cataloger-1",
Metadata: pkg.RpmMetadata{
Name: "package1",
Epoch: 0,
Arch: "x86_64",
Release: "1",
Version: "1.0.1",
SourceRpm: "package1-1.0.1-1.src.rpm",
Size: 12406784,
License: "MIT",
Vendor: "",
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Name: "package2",
Version: "2.0.1",
Source: []file.Reference{
*img.SquashedTree().File("/somefile-2.txt"),
},
Type: pkg.DebPkg,
Type: pkg.RpmPkg,
FoundBy: "the-cataloger-2",
Licenses: []string{
"MIT",
"Apache-v2",
},
Metadata: pkg.RpmMetadata{
Name: "package2",
Epoch: 0,
Arch: "x86_64",
Release: "1",
Version: "1.0.2",
SourceRpm: "package2-1.0.2-1.src.rpm",
Size: 12406784,
License: "MIT",
Vendor: "",
},
})
s, err := scope.NewScopeFromImage(img, scope.AllLayersScope)
pres := NewPresenter(catalog, s)
if err != nil {
t.Fatal(err)
}
d, err := distro.NewDistro(distro.RedHat, "8")
if err != nil {
t.Fatal(err)
}
pres := NewPresenter(catalog, s, d)
// run presenter
err = pres.Present(&buffer)
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:0667955f-34d0-49c1-8062-996dbc636974">
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:88c9a559-fb74-45a1-9dbb-a3d8bcbcacc8">
<components>
<component type="library">
<name>package-1</name>
<name>package1</name>
<version>1.0.1</version>
<purl>pkg:deb/ubuntu/package1@1.0.1?arch=amd64</purl>
</component>
<component type="library">
<name>package-2</name>
<name>package2</name>
<version>2.0.1</version>
<licenses>
<license>
@@ -16,10 +17,11 @@
<name>Apache-v2</name>
</license>
</licenses>
<purl>pkg:deb/ubuntu/package2@1.0.2?arch=amd64</purl>
</component>
</components>
<bd:metadata>
<bd:timestamp>2020-08-26T15:31:39-04:00</bd:timestamp>
<bd:timestamp>2020-08-29T20:17:49-04:00</bd:timestamp>
<bd:tool>
<bd:vendor>anchore</bd:vendor>
<bd:name>syft</bd:name>
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:2e7af7d0-83a4-4730-9175-66858406b285">
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:6da957f1-3337-4128-870c-fe271aa195d1">
<components>
<component type="library">
<name>package-1</name>
<name>package1</name>
<version>1.0.1</version>
<purl>pkg:rpm/redhat/package1@0:1.0.1-1?arch=x86_64</purl>
</component>
<component type="library">
<name>package-2</name>
<name>package2</name>
<version>2.0.1</version>
<licenses>
<license>
@@ -16,10 +17,11 @@
<name>Apache-v2</name>
</license>
</licenses>
<purl>pkg:rpm/redhat/package2@0:1.0.2-1?arch=x86_64</purl>
</component>
</components>
<bd:metadata>
<bd:timestamp>2020-08-26T15:31:39-04:00</bd:timestamp>
<bd:timestamp>2020-08-29T20:17:49-04:00</bd:timestamp>
<bd:tool>
<bd:vendor>anchore</bd:vendor>
<bd:name>syft</bd:name>
+4 -2
View File
@@ -7,6 +7,8 @@ package presenter
import (
"io"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/presenter/cyclonedx"
"github.com/anchore/syft/syft/pkg"
@@ -23,7 +25,7 @@ type Presenter interface {
}
// GetPresenter returns a presenter for images or directories
func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog) Presenter {
func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog, d *distro.Distro) Presenter {
switch option {
case JSONPresenter:
return json.NewPresenter(catalog, s)
@@ -32,7 +34,7 @@ func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog) Presenter
case TablePresenter:
return table.NewPresenter(catalog, s)
case CycloneDxPresenter:
return cyclonedx.NewPresenter(catalog, s)
return cyclonedx.NewPresenter(catalog, s, *d)
default:
return nil
}