mirror of
https://github.com/anchore/syft
synced 2024-11-10 14:24:12 +00:00
migrate syft/presenter to internal/presenter
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
4666ca8469
commit
ff4ed40d50
59 changed files with 641 additions and 699 deletions
69
internal/presenter/packages/cyclonedx_bom_descriptor.go
Normal file
69
internal/presenter/packages/cyclonedx_bom_descriptor.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Source: https://cyclonedx.org/ext/bom-descriptor/
|
||||
|
||||
// CycloneDxBomDescriptor represents all metadata surrounding the BOM report (such as when the BOM was made, with which tool, and the item being cataloged).
|
||||
type CycloneDxBomDescriptor struct {
|
||||
XMLName xml.Name `xml:"metadata"`
|
||||
Timestamp string `xml:"timestamp,omitempty"` // The date and time (timestamp) when the document was created
|
||||
Tools []CycloneDxBdTool `xml:"tools>tool"` // The tool used to create the BOM.
|
||||
Component *CycloneDxBdComponent `xml:"component"` // The component that the BOM describes.
|
||||
}
|
||||
|
||||
// CycloneDxBdTool represents the tool that created the BOM report.
|
||||
type CycloneDxBdTool struct {
|
||||
XMLName xml.Name `xml:"tool"`
|
||||
Vendor string `xml:"vendor,omitempty"` // The vendor of the tool used to create the BOM.
|
||||
Name string `xml:"name,omitempty"` // The name of the tool used to create the BOM.
|
||||
Version string `xml:"version,omitempty"` // The version of the tool used to create the BOM.
|
||||
// TODO: hashes, author, manufacture, supplier
|
||||
// TODO: add user-defined fields for the remaining build/version parameters
|
||||
}
|
||||
|
||||
// CycloneDxBdComponent represents the software/package being cataloged.
|
||||
type CycloneDxBdComponent struct {
|
||||
XMLName xml.Name `xml:"component"`
|
||||
CycloneDxComponent
|
||||
}
|
||||
|
||||
// NewCycloneDxBomDescriptor returns a new CycloneDxBomDescriptor tailored for the current time and "syft" tool details.
|
||||
func NewCycloneDxBomDescriptor(name, version string, srcMetadata source.Metadata) *CycloneDxBomDescriptor {
|
||||
descriptor := CycloneDxBomDescriptor{
|
||||
XMLName: xml.Name{},
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Tools: []CycloneDxBdTool{
|
||||
{
|
||||
Vendor: "anchore",
|
||||
Name: name,
|
||||
Version: version,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
descriptor.Component = &CycloneDxBdComponent{
|
||||
CycloneDxComponent: CycloneDxComponent{
|
||||
Type: "container",
|
||||
Name: srcMetadata.ImageMetadata.UserInput,
|
||||
Version: srcMetadata.ImageMetadata.ManifestDigest,
|
||||
},
|
||||
}
|
||||
case source.DirectoryScheme:
|
||||
descriptor.Component = &CycloneDxBdComponent{
|
||||
CycloneDxComponent: CycloneDxComponent{
|
||||
Type: "file",
|
||||
Name: srcMetadata.Path,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &descriptor
|
||||
}
|
27
internal/presenter/packages/cyclonedx_component.go
Normal file
27
internal/presenter/packages/cyclonedx_component.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package packages
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// CycloneDxComponent represents a single element in the CycloneDX BOM
|
||||
type CycloneDxComponent struct {
|
||||
XMLName xml.Name `xml:"component"`
|
||||
Type string `xml:"type,attr"` // Required; Describes if the component is a library, framework, application, container, operating system, firmware, hardware device, or file
|
||||
Supplier string `xml:"supplier,omitempty"` // The organization that supplied the component. The supplier may often be the manufacture, but may also be a distributor or repackager.
|
||||
Author string `xml:"author,omitempty"` // The person(s) or organization(s) that authored the component
|
||||
Publisher string `xml:"publisher,omitempty"` // The person(s) or organization(s) that published the component
|
||||
Group string `xml:"group,omitempty"` // The high-level classification that a project self-describes as. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name.
|
||||
Name string `xml:"name"` // Required; The name of the component as defined by the project
|
||||
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 *[]CycloneDxLicense `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: source, hashes, copyright, cpe, purl, swid, modified, pedigree, externalReferences
|
||||
// TODO: add user-defined parameters for syft-specific values (image layer index, cataloger, location path, etc.)
|
||||
}
|
||||
|
||||
// CycloneDxLicense represents a single software license for a CycloneDxComponent
|
||||
type CycloneDxLicense struct {
|
||||
XMLName xml.Name `xml:"license"`
|
||||
ID string `xml:"id,omitempty"` // A valid SPDX license ID
|
||||
Name string `xml:"name,omitempty"` // If SPDX does not define the license used, this field may be used to provide the license name
|
||||
}
|
57
internal/presenter/packages/cyclonedx_document.go
Normal file
57
internal/presenter/packages/cyclonedx_document.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Source: https://github.com/CycloneDX/specification
|
||||
|
||||
// CycloneDxDocument represents a CycloneDX BOM CycloneDxDocument.
|
||||
type CycloneDxDocument struct {
|
||||
XMLName xml.Name `xml:"bom"`
|
||||
XMLNs string `xml:"xmlns,attr"`
|
||||
Version int `xml:"version,attr"`
|
||||
SerialNumber string `xml:"serialNumber,attr"`
|
||||
BomDescriptor *CycloneDxBomDescriptor `xml:"metadata"` // The BOM descriptor extension
|
||||
Components []CycloneDxComponent `xml:"components>component"` // The BOM contents
|
||||
}
|
||||
|
||||
// NewCycloneDxDocument returns a CycloneDX CycloneDxDocument object populated with the catalog contents.
|
||||
func NewCycloneDxDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) CycloneDxDocument {
|
||||
versionInfo := version.FromBuild()
|
||||
|
||||
doc := CycloneDxDocument{
|
||||
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
|
||||
Version: 1,
|
||||
SerialNumber: uuid.New().URN(),
|
||||
BomDescriptor: NewCycloneDxBomDescriptor(internal.ApplicationName, versionInfo.Version, srcMetadata),
|
||||
}
|
||||
|
||||
// attach components
|
||||
for p := range catalog.Enumerate() {
|
||||
component := CycloneDxComponent{
|
||||
Type: "library", // TODO: this is not accurate
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
PackageURL: p.PURL,
|
||||
}
|
||||
var licenses []CycloneDxLicense
|
||||
for _, licenseName := range p.Licenses {
|
||||
licenses = append(licenses, CycloneDxLicense{
|
||||
Name: licenseName,
|
||||
})
|
||||
}
|
||||
if len(licenses) > 0 {
|
||||
component.Licenses = &licenses
|
||||
}
|
||||
doc.Components = append(doc.Components, component)
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Package cyclonedx is responsible for generating a CycloneDX XML report for the given container image or file system.
|
||||
*/
|
||||
package cyclonedx
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
@ -11,23 +11,23 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Presenter writes a CycloneDX report from the given Catalog and Locations contents
|
||||
type Presenter struct {
|
||||
// CycloneDxPresenter writes a CycloneDX report from the given Catalog and Locations contents
|
||||
type CycloneDxPresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
}
|
||||
|
||||
// NewPresenter creates a CycloneDX presenter from the given Catalog and Locations objects.
|
||||
func NewPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *Presenter {
|
||||
return &Presenter{
|
||||
// NewCycloneDxPresenter creates a CycloneDX presenter from the given Catalog and Locations objects.
|
||||
func NewCycloneDxPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *CycloneDxPresenter {
|
||||
return &CycloneDxPresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: srcMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Present writes the CycloneDX report to the given io.Writer.
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
bom := NewDocument(pres.catalog, pres.srcMetadata)
|
||||
func (pres *CycloneDxPresenter) Present(output io.Writer) error {
|
||||
bom := NewCycloneDxDocument(pres.catalog, pres.srcMetadata)
|
||||
|
||||
encoder := xml.NewEncoder(output)
|
||||
encoder.Indent("", " ")
|
|
@ -1,4 +1,4 @@
|
|||
package cyclonedx
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -62,7 +62,7 @@ func TestCycloneDxDirsPresenter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pres := NewPresenter(catalog, s.Metadata)
|
||||
pres := NewCycloneDxPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
|
@ -93,8 +93,7 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
|
|||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
defer cleanup()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
@ -125,7 +124,7 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
|
|||
PURL: "the-purl-2",
|
||||
})
|
||||
|
||||
s, err := source.NewFromImage(img, source.SquashedScope, "user-image-input")
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -138,7 +137,7 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
|
|||
// This value is sourced from the "version" node in "./test-fixtures/snapshot/TestCycloneDxImgsPresenter.golden"
|
||||
s.Metadata.ImageMetadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
pres := NewPresenter(catalog, s.Metadata)
|
||||
pres := NewCycloneDxPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
|
@ -1,21 +1,21 @@
|
|||
package json
|
||||
package packages
|
||||
|
||||
import "github.com/anchore/syft/syft/distro"
|
||||
|
||||
// Distribution provides information about a detected Linux Distribution.
|
||||
type Distribution struct {
|
||||
// JSONDistribution provides information about a detected Linux JSONDistribution.
|
||||
type JSONDistribution struct {
|
||||
Name string `json:"name"` // Name of the Linux distribution
|
||||
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
|
||||
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
|
||||
}
|
||||
|
||||
// NewDistribution creates a struct with the Linux distribution to be represented in JSON.
|
||||
func NewDistribution(d *distro.Distro) Distribution {
|
||||
// NewJSONDistribution creates a struct with the Linux distribution to be represented in JSON.
|
||||
func NewJSONDistribution(d *distro.Distro) JSONDistribution {
|
||||
if d == nil {
|
||||
return Distribution{}
|
||||
return JSONDistribution{}
|
||||
}
|
||||
|
||||
return Distribution{
|
||||
return JSONDistribution{
|
||||
Name: d.Name(),
|
||||
Version: d.FullVersion(),
|
||||
IDLike: d.IDLike,
|
62
internal/presenter/packages/json_document.go
Normal file
62
internal/presenter/packages/json_document.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// JSONDocument represents the syft cataloging findings as a JSON document
|
||||
type JSONDocument struct {
|
||||
Artifacts []JSONPackage `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
ArtifactRelationships []JSONRelationship `json:"artifactRelationships"`
|
||||
Source JSONSource `json:"source"` // Source represents the original object that was cataloged
|
||||
Distro JSONDistribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||
Descriptor JSONDescriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||
Schema JSONSchema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
}
|
||||
|
||||
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||
func NewJSONDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Distro, scope source.Scope, configuration interface{}) (JSONDocument, error) {
|
||||
src, err := NewJSONSource(srcMetadata, scope)
|
||||
if err != nil {
|
||||
return JSONDocument{}, err
|
||||
}
|
||||
|
||||
artifacts, err := NewJSONPackages(catalog)
|
||||
if err != nil {
|
||||
return JSONDocument{}, err
|
||||
}
|
||||
|
||||
return JSONDocument{
|
||||
Artifacts: artifacts,
|
||||
ArtifactRelationships: newJSONRelationships(pkg.NewRelationships(catalog)),
|
||||
Source: src,
|
||||
Distro: NewJSONDistribution(d),
|
||||
Descriptor: JSONDescriptor{
|
||||
Name: internal.ApplicationName,
|
||||
Version: version.FromBuild().Version,
|
||||
Configuration: configuration,
|
||||
},
|
||||
Schema: JSONSchema{
|
||||
Version: internal.JSONSchemaVersion,
|
||||
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// JSONDescriptor describes what created the document as well as surrounding metadata
|
||||
type JSONDescriptor struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Configuration interface{} `json:"configuration,omitempty"`
|
||||
}
|
||||
|
||||
type JSONSchema struct {
|
||||
Version string `json:"version"`
|
||||
URL string `json:"url"`
|
||||
}
|
68
internal/presenter/packages/json_package.go
Normal file
68
internal/presenter/packages/json_package.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// JSONPackage represents a pkg.Package object specialized for JSON marshaling and unmarshaling.
|
||||
type JSONPackage struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Location `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Language string `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
MetadataType string `json:"metadataType"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
func NewJSONPackages(catalog *pkg.Catalog) ([]JSONPackage, error) {
|
||||
artifacts := make([]JSONPackage, 0)
|
||||
for _, p := range catalog.Sorted() {
|
||||
art, err := NewJSONPackage(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifacts = append(artifacts, art)
|
||||
}
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// NewJSONPackage crates a new JSONPackage from the given pkg.Package.
|
||||
func NewJSONPackage(p *pkg.Package) (JSONPackage, error) {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
for i, c := range p.CPEs {
|
||||
cpes[i] = c.BindToFmtString()
|
||||
}
|
||||
|
||||
// ensure collections are never nil for presentation reasons
|
||||
var locations = make([]source.Location, 0)
|
||||
if p.Locations != nil {
|
||||
locations = p.Locations
|
||||
}
|
||||
|
||||
var licenses = make([]string, 0)
|
||||
if p.Licenses != nil {
|
||||
licenses = p.Licenses
|
||||
}
|
||||
|
||||
return JSONPackage{
|
||||
ID: string(p.ID),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Type: string(p.Type),
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: locations,
|
||||
Licenses: licenses,
|
||||
Language: string(p.Language),
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
MetadataType: string(p.MetadataType),
|
||||
Metadata: p.Metadata,
|
||||
}, nil
|
||||
}
|
43
internal/presenter/packages/json_presenter.go
Normal file
43
internal/presenter/packages/json_presenter.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// JSONPresenter is a JSON presentation object for the syft results
|
||||
type JSONPresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
distro *distro.Distro
|
||||
scope source.Scope
|
||||
}
|
||||
|
||||
// NewJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewJSONPresenter(catalog *pkg.Catalog, s source.Metadata, d *distro.Distro, scope source.Scope) *JSONPresenter {
|
||||
return &JSONPresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: s,
|
||||
distro: d,
|
||||
scope: scope,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the catalog results to the given writer.
|
||||
func (pres *JSONPresenter) Present(output io.Writer) error {
|
||||
// we do not pass in configuration for backwards compatibility
|
||||
doc, err := NewJSONDocument(pres.catalog, pres.srcMetadata, pres.distro, pres.scope, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&doc)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package json
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
|
||||
var updateJSONGoldenFiles = flag.Bool("update-json", false, "update the *.golden files for json presenters")
|
||||
|
||||
func must(c pkg.CPE, e error) pkg.CPE {
|
||||
if e != nil {
|
||||
|
@ -24,7 +24,7 @@ func must(c pkg.CPE, e error) pkg.CPE {
|
|||
return c
|
||||
}
|
||||
|
||||
func TestJsonDirsPresenter(t *testing.T) {
|
||||
func TestJSONDirsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
@ -75,7 +75,7 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pres := NewPresenter(catalog, s.Metadata, d)
|
||||
pres := NewJSONPresenter(catalog, s.Metadata, d, source.SquashedScope)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
|
@ -84,7 +84,7 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
if *updateJSONGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
|
@ -98,12 +98,12 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestJsonImgsPresenter(t *testing.T) {
|
||||
func TestJSONImgsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
testImage := "image-simple"
|
||||
|
||||
if *update {
|
||||
if *updateJSONGoldenFiles {
|
||||
imagetest.UpdateGoldenFixtureImage(t, testImage)
|
||||
}
|
||||
|
||||
|
@ -158,9 +158,9 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
// this is a hard coded value that is not given by the fixture helper and must be provided manually
|
||||
img.Metadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
s, err := source.NewFromImage(img, source.SquashedScope, "user-image-input")
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
var d *distro.Distro
|
||||
pres := NewPresenter(catalog, s.Metadata, d)
|
||||
pres := NewJSONPresenter(catalog, s.Metadata, d, source.SquashedScope)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
|
@ -169,7 +169,7 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
if *updateJSONGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
package json
|
||||
package packages
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
type Relationship struct {
|
||||
type JSONRelationship struct {
|
||||
Parent string `json:"parent"`
|
||||
Child string `json:"child"`
|
||||
Type string `json:"type"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
func newRelationships(relationships []pkg.Relationship) []Relationship {
|
||||
result := make([]Relationship, len(relationships))
|
||||
func newJSONRelationships(relationships []pkg.Relationship) []JSONRelationship {
|
||||
result := make([]JSONRelationship, len(relationships))
|
||||
for i, r := range relationships {
|
||||
result[i] = Relationship{
|
||||
result[i] = JSONRelationship{
|
||||
Parent: string(r.Parent),
|
||||
Child: string(r.Child),
|
||||
Type: string(r.Type),
|
39
internal/presenter/packages/json_source.go
Normal file
39
internal/presenter/packages/json_source.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// JSONSource object represents the thing that was cataloged
|
||||
type JSONSource struct {
|
||||
Type string `json:"type"`
|
||||
Target interface{} `json:"target"`
|
||||
}
|
||||
|
||||
type JSONImageSource struct {
|
||||
source.ImageMetadata
|
||||
Scope source.Scope `json:"scope"`
|
||||
}
|
||||
|
||||
// NewJSONSource creates a new source object to be represented into JSON.
|
||||
func NewJSONSource(src source.Metadata, scope source.Scope) (JSONSource, error) {
|
||||
switch src.Scheme {
|
||||
case source.ImageScheme:
|
||||
return JSONSource{
|
||||
Type: "image",
|
||||
Target: JSONImageSource{
|
||||
Scope: scope,
|
||||
ImageMetadata: src.ImageMetadata,
|
||||
},
|
||||
}, nil
|
||||
case source.DirectoryScheme:
|
||||
return JSONSource{
|
||||
Type: "directory",
|
||||
Target: src.Path,
|
||||
}, nil
|
||||
default:
|
||||
return JSONSource{}, fmt.Errorf("unsupported source: %q", src.Scheme)
|
||||
}
|
||||
}
|
25
internal/presenter/packages/presenter.go
Normal file
25
internal/presenter/packages/presenter.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Defines a Presenter interface for displaying catalog results to an io.Writer as well as a helper utility to obtain
|
||||
a specific Presenter implementation given user configuration.
|
||||
*/
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/presenter"
|
||||
)
|
||||
|
||||
// Presenter returns a presenter for images or directories
|
||||
func Presenter(option PresenterOption, config PresenterConfig) presenter.Presenter {
|
||||
switch option {
|
||||
case JSONPresenterOption:
|
||||
return NewJSONPresenter(config.Catalog, config.SourceMetadata, config.Distro, config.Scope)
|
||||
case TextPresenterOption:
|
||||
return NewTextPresenter(config.Catalog, config.SourceMetadata)
|
||||
case TablePresenterOption:
|
||||
return NewTablePresenter(config.Catalog)
|
||||
case CycloneDxPresenterOption:
|
||||
return NewCycloneDxPresenter(config.Catalog, config.SourceMetadata)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
14
internal/presenter/packages/presenter_config.go
Normal file
14
internal/presenter/packages/presenter_config.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package packages
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type PresenterConfig struct {
|
||||
SourceMetadata source.Metadata
|
||||
Catalog *pkg.Catalog
|
||||
Distro *distro.Distro
|
||||
Scope source.Scope
|
||||
}
|
35
internal/presenter/packages/presenter_option.go
Normal file
35
internal/presenter/packages/presenter_option.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package packages
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
UnknownPresenterOption PresenterOption = "UnknownPresenterOption"
|
||||
JSONPresenterOption PresenterOption = "json"
|
||||
TextPresenterOption PresenterOption = "text"
|
||||
TablePresenterOption PresenterOption = "table"
|
||||
CycloneDxPresenterOption PresenterOption = "cyclonedx"
|
||||
)
|
||||
|
||||
var AllPresenters = []PresenterOption{
|
||||
JSONPresenterOption,
|
||||
TextPresenterOption,
|
||||
TablePresenterOption,
|
||||
CycloneDxPresenterOption,
|
||||
}
|
||||
|
||||
type PresenterOption string
|
||||
|
||||
func ParsePresenterOption(userStr string) PresenterOption {
|
||||
switch strings.ToLower(userStr) {
|
||||
case string(JSONPresenterOption):
|
||||
return JSONPresenterOption
|
||||
case string(TextPresenterOption):
|
||||
return TextPresenterOption
|
||||
case string(TablePresenterOption):
|
||||
return TablePresenterOption
|
||||
case string(CycloneDxPresenterOption), "cyclone", "cyclone-dx":
|
||||
return CycloneDxPresenterOption
|
||||
default:
|
||||
return UnknownPresenterOption
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package table
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -11,17 +11,17 @@ import (
|
|||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type Presenter struct {
|
||||
type TablePresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
}
|
||||
|
||||
func NewPresenter(catalog *pkg.Catalog) *Presenter {
|
||||
return &Presenter{
|
||||
func NewTablePresenter(catalog *pkg.Catalog) *TablePresenter {
|
||||
return &TablePresenter{
|
||||
catalog: catalog,
|
||||
}
|
||||
}
|
||||
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
func (pres *TablePresenter) Present(output io.Writer) error {
|
||||
rows := make([][]string, 0)
|
||||
|
||||
columns := []string{"Name", "Version", "Type"}
|
||||
|
@ -42,7 +42,7 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||
// sort by name, version, then type
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
for col := 0; col < len(columns); col++ {
|
||||
if rows[i][0] != rows[j][0] {
|
||||
if rows[i][col] != rows[j][col] {
|
||||
return rows[i][col] < rows[j][col]
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package table
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for table presenters")
|
||||
var updateTablePresenterGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table presenters")
|
||||
|
||||
func TestTablePresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
@ -24,8 +24,7 @@ func TestTablePresenter(t *testing.T) {
|
|||
testImage := "image-simple"
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
defer cleanup()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
@ -48,7 +47,7 @@ func TestTablePresenter(t *testing.T) {
|
|||
Type: pkg.DebPkg,
|
||||
})
|
||||
|
||||
pres := NewPresenter(catalog)
|
||||
pres := NewTablePresenter(catalog)
|
||||
|
||||
// run presenter
|
||||
err := pres.Present(&buffer)
|
||||
|
@ -57,7 +56,7 @@ func TestTablePresenter(t *testing.T) {
|
|||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
if *updateTablePresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package text
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -10,22 +10,22 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Presenter is a human-friendly text presenter to represent package and source data.
|
||||
type Presenter struct {
|
||||
// TextPresenter is a human-friendly text presenter to represent package and source data.
|
||||
type TextPresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
}
|
||||
|
||||
// NewPresenter creates a new presenter for the given set of catalog and image data.
|
||||
func NewPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *Presenter {
|
||||
return &Presenter{
|
||||
// NewTextPresenter creates a new presenter for the given set of catalog and image data.
|
||||
func NewTextPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *TextPresenter {
|
||||
return &TextPresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: srcMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Present is a method that is in charge of writing to an output buffer
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
func (pres *TextPresenter) Present(output io.Writer) error {
|
||||
// init the tabular writer
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
|
@ -1,4 +1,4 @@
|
|||
package text
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for text presenters")
|
||||
var updateTextPresenterGoldenFiles = flag.Bool("update-text", false, "update the *.golden files for text presenters")
|
||||
|
||||
func TestTextDirPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
@ -37,7 +37,7 @@ func TestTextDirPresenter(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("unable to create source: %+v", err)
|
||||
}
|
||||
pres := NewPresenter(catalog, s.Metadata)
|
||||
pres := NewTextPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
|
@ -46,7 +46,7 @@ func TestTextDirPresenter(t *testing.T) {
|
|||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
if *updateTextPresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
|
@ -69,8 +69,7 @@ func TestTextImgPresenter(t *testing.T) {
|
|||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
defer cleanup()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
@ -102,18 +101,18 @@ func TestTextImgPresenter(t *testing.T) {
|
|||
l.Metadata.Digest = "sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53"
|
||||
}
|
||||
|
||||
s, err := source.NewFromImage(img, source.SquashedScope, "user-image-input")
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pres := NewPresenter(catalog, s.Metadata)
|
||||
pres := NewTextPresenter(catalog, s.Metadata)
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
if *update {
|
||||
if *updateTextPresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
32
internal/presenter/poweruser/json_document.go
Normal file
32
internal/presenter/poweruser/json_document.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package poweruser
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/presenter/packages"
|
||||
)
|
||||
|
||||
type JSONDocument struct {
|
||||
// note: poweruser.JSONDocument is meant to always be a superset of packages.JSONDocument, any additional fields
|
||||
// here should be optional by supplying "omitempty" on these fields hint to the jsonschema generator to not
|
||||
// require these fields. As an accepted rule in this repo all collections should still be initialized in the
|
||||
// context of being used in a JSON document.
|
||||
FileMetadata []JSONFileMetadata `json:"fileMetadata,omitempty"`
|
||||
packages.JSONDocument
|
||||
}
|
||||
|
||||
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||
func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
|
||||
pkgsDoc, err := packages.NewJSONDocument(config.PackageCatalog, config.SourceMetadata, config.Distro, config.ApplicationConfig.Packages.ScopeOpt, config.ApplicationConfig)
|
||||
if err != nil {
|
||||
return JSONDocument{}, err
|
||||
}
|
||||
|
||||
fileMetadata, err := NewJSONFileMetadata(config.FileMetadata, config.FileDigests)
|
||||
if err != nil {
|
||||
return JSONDocument{}, err
|
||||
}
|
||||
|
||||
return JSONDocument{
|
||||
FileMetadata: fileMetadata,
|
||||
JSONDocument: pkgsDoc,
|
||||
}, nil
|
||||
}
|
18
internal/presenter/poweruser/json_document_config.go
Normal file
18
internal/presenter/poweruser/json_document_config.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package poweruser
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type JSONDocumentConfig struct {
|
||||
ApplicationConfig config.Application
|
||||
PackageCatalog *pkg.Catalog
|
||||
FileMetadata map[source.Location]source.FileMetadata
|
||||
FileDigests map[source.Location][]file.Digest
|
||||
Distro *distro.Distro
|
||||
SourceMetadata source.Metadata
|
||||
}
|
50
internal/presenter/poweruser/json_file_metadata.go
Normal file
50
internal/presenter/poweruser/json_file_metadata.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package poweruser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type JSONFileMetadata struct {
|
||||
Location source.Location `json:"location"`
|
||||
Metadata JSONFileMetadataEntry `json:"metadata"`
|
||||
}
|
||||
|
||||
type JSONFileMetadataEntry struct {
|
||||
Mode int `json:"mode"`
|
||||
Type source.FileType `json:"type"`
|
||||
UserID int `json:"userID"`
|
||||
GroupID int `json:"groupID"`
|
||||
Digests []file.Digest `json:"digests"`
|
||||
}
|
||||
|
||||
func NewJSONFileMetadata(data map[source.Location]source.FileMetadata, digests map[source.Location][]file.Digest) ([]JSONFileMetadata, error) {
|
||||
results := make([]JSONFileMetadata, 0)
|
||||
for location, metadata := range data {
|
||||
mode, err := strconv.Atoi(fmt.Sprintf("%o", metadata.Mode))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid mode found in file catalog @ location=%+v mode=%q: %w", location, metadata.Mode, err)
|
||||
}
|
||||
|
||||
digestResults := make([]file.Digest, 0)
|
||||
if digestsForLocation, exists := digests[location]; exists {
|
||||
digestResults = digestsForLocation
|
||||
}
|
||||
|
||||
results = append(results, JSONFileMetadata{
|
||||
Location: location,
|
||||
Metadata: JSONFileMetadataEntry{
|
||||
Mode: mode,
|
||||
Type: metadata.Type,
|
||||
UserID: metadata.UserID,
|
||||
GroupID: metadata.GroupID,
|
||||
Digests: digestResults,
|
||||
},
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
32
internal/presenter/poweruser/json_presenter.go
Normal file
32
internal/presenter/poweruser/json_presenter.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package poweruser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// JSONPresenter is a JSON presentation object for the syft results
|
||||
type JSONPresenter struct {
|
||||
config JSONDocumentConfig
|
||||
}
|
||||
|
||||
// NewJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewJSONPresenter(config JSONDocumentConfig) *JSONPresenter {
|
||||
return &JSONPresenter{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the PackageCatalog results to the given writer.
|
||||
func (p *JSONPresenter) Present(output io.Writer) error {
|
||||
doc, err := NewJSONDocument(p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&doc)
|
||||
}
|
9
internal/presenter/presenter.go
Normal file
9
internal/presenter/presenter.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package presenter
|
||||
|
||||
import "io"
|
||||
|
||||
// Presenter defines the expected behavior for an object responsible for displaying arbitrary input and processed data
|
||||
// to a given io.Writer.
|
||||
type Presenter interface {
|
||||
Present(io.Writer) error
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package cyclonedx
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Source: https://cyclonedx.org/ext/bom-descriptor/
|
||||
|
||||
// BomDescriptor represents all metadata surrounding the BOM report (such as when the BOM was made, with which tool, and the item being cataloged).
|
||||
type BomDescriptor struct {
|
||||
XMLName xml.Name `xml:"metadata"`
|
||||
Timestamp string `xml:"timestamp,omitempty"` // The date and time (timestamp) when the document was created
|
||||
Tools []BdTool `xml:"tools>tool"` // The tool used to create the BOM.
|
||||
Component *BdComponent `xml:"component"` // The component that the BOM describes.
|
||||
}
|
||||
|
||||
// BdTool represents the tool that created the BOM report.
|
||||
type BdTool struct {
|
||||
XMLName xml.Name `xml:"tool"`
|
||||
Vendor string `xml:"vendor,omitempty"` // The vendor of the tool used to create the BOM.
|
||||
Name string `xml:"name,omitempty"` // The name of the tool used to create the BOM.
|
||||
Version string `xml:"version,omitempty"` // The version of the tool used to create the BOM.
|
||||
// TODO: hashes, author, manufacture, supplier
|
||||
// TODO: add user-defined fields for the remaining build/version parameters
|
||||
}
|
||||
|
||||
// BdComponent represents the software/package being cataloged.
|
||||
type BdComponent struct {
|
||||
XMLName xml.Name `xml:"component"`
|
||||
Component
|
||||
}
|
||||
|
||||
// NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details.
|
||||
func NewBomDescriptor(name, version string, srcMetadata source.Metadata) *BomDescriptor {
|
||||
descriptor := BomDescriptor{
|
||||
XMLName: xml.Name{},
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Tools: []BdTool{
|
||||
{
|
||||
Vendor: "anchore",
|
||||
Name: name,
|
||||
Version: version,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
descriptor.Component = &BdComponent{
|
||||
Component: Component{
|
||||
Type: "container",
|
||||
Name: srcMetadata.ImageMetadata.UserInput,
|
||||
Version: srcMetadata.ImageMetadata.ManifestDigest,
|
||||
},
|
||||
}
|
||||
case source.DirectoryScheme:
|
||||
descriptor.Component = &BdComponent{
|
||||
Component: Component{
|
||||
Type: "file",
|
||||
Name: srcMetadata.Path,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &descriptor
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package cyclonedx
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Component represents a single element in the CycloneDX BOM
|
||||
type Component struct {
|
||||
XMLName xml.Name `xml:"component"`
|
||||
Type string `xml:"type,attr"` // Required; Describes if the component is a library, framework, application, container, operating system, firmware, hardware device, or file
|
||||
Supplier string `xml:"supplier,omitempty"` // The organization that supplied the component. The supplier may often be the manufacture, but may also be a distributor or repackager.
|
||||
Author string `xml:"author,omitempty"` // The person(s) or organization(s) that authored the component
|
||||
Publisher string `xml:"publisher,omitempty"` // The person(s) or organization(s) that published the component
|
||||
Group string `xml:"group,omitempty"` // The high-level classification that a project self-describes as. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name.
|
||||
Name string `xml:"name"` // Required; The name of the component as defined by the project
|
||||
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: source, hashes, copyright, cpe, purl, swid, modified, pedigree, externalReferences
|
||||
// TODO: add user-defined parameters for syft-specific values (image layer index, cataloger, location path, etc.)
|
||||
}
|
||||
|
||||
// License represents a single software license for a Component
|
||||
type License struct {
|
||||
XMLName xml.Name `xml:"license"`
|
||||
ID string `xml:"id,omitempty"` // A valid SPDX license ID
|
||||
Name string `xml:"name,omitempty"` // If SPDX does not define the license used, this field may be used to provide the license name
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package cyclonedx
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Source: https://github.com/CycloneDX/specification
|
||||
|
||||
// Document represents a CycloneDX BOM Document.
|
||||
type Document struct {
|
||||
XMLName xml.Name `xml:"bom"`
|
||||
XMLNs string `xml:"xmlns,attr"`
|
||||
Version int `xml:"version,attr"`
|
||||
SerialNumber string `xml:"serialNumber,attr"`
|
||||
BomDescriptor *BomDescriptor `xml:"metadata"` // The BOM descriptor extension
|
||||
Components []Component `xml:"components>component"` // The BOM contents
|
||||
}
|
||||
|
||||
// NewDocumentFromCatalog returns a CycloneDX Document object populated with the catalog contents.
|
||||
func NewDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) Document {
|
||||
versionInfo := version.FromBuild()
|
||||
|
||||
doc := Document{
|
||||
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
|
||||
Version: 1,
|
||||
SerialNumber: uuid.New().URN(),
|
||||
BomDescriptor: NewBomDescriptor(internal.ApplicationName, versionInfo.Version, srcMetadata),
|
||||
}
|
||||
|
||||
// attach components
|
||||
for p := range catalog.Enumerate() {
|
||||
component := Component{
|
||||
Type: "library", // TODO: this is not accurate
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
PackageURL: p.PURL,
|
||||
}
|
||||
var licenses []License
|
||||
for _, licenseName := range p.Licenses {
|
||||
licenses = append(licenses, License{
|
||||
Name: licenseName,
|
||||
})
|
||||
}
|
||||
if len(licenses) > 0 {
|
||||
component.Licenses = &licenses
|
||||
}
|
||||
doc.Components = append(doc.Components, component)
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
|
||||
FROM scratch
|
||||
ADD file-1.txt /somefile-1.txt
|
||||
ADD file-2.txt /somefile-2.txt
|
||||
# note: adding a directory will behave differently on docker engine v18 vs v19
|
||||
ADD target /
|
|
@ -1,2 +0,0 @@
|
|||
another file!
|
||||
with lines...
|
|
@ -1,7 +0,0 @@
|
|||
package json
|
||||
|
||||
// Descriptor describes what created the document as well as surrounding metadata
|
||||
type Descriptor struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Document represents the syft cataloging findings as a JSON document
|
||||
type Document struct {
|
||||
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||
Distro Distribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
ArtifactRelationships []Relationship `json:"artifactRelationships"`
|
||||
}
|
||||
|
||||
// NewDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||
func NewDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Distro) (Document, error) {
|
||||
src, err := NewSource(srcMetadata)
|
||||
if err != nil {
|
||||
return Document{}, nil
|
||||
}
|
||||
|
||||
doc := Document{
|
||||
Artifacts: make([]Package, 0),
|
||||
Source: src,
|
||||
Distro: NewDistribution(d),
|
||||
Descriptor: Descriptor{
|
||||
Name: internal.ApplicationName,
|
||||
Version: version.FromBuild().Version,
|
||||
},
|
||||
Schema: Schema{
|
||||
Version: internal.JSONSchemaVersion,
|
||||
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
|
||||
},
|
||||
ArtifactRelationships: newRelationships(pkg.NewRelationships(catalog)),
|
||||
}
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
art, err := NewPackage(p)
|
||||
if err != nil {
|
||||
return Document{}, err
|
||||
}
|
||||
doc.Artifacts = append(doc.Artifacts, art)
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Presenter is a JSON presentation object for the syft results
|
||||
type Presenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
distro *distro.Distro
|
||||
}
|
||||
|
||||
// NewPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewPresenter(catalog *pkg.Catalog, s source.Metadata, d *distro.Distro) *Presenter {
|
||||
return &Presenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: s,
|
||||
distro: d,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the catalog results to the given writer.
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
doc, err := NewDocument(pres.catalog, pres.srcMetadata, pres.distro)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&doc)
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package json
|
||||
|
||||
type Schema struct {
|
||||
Version string `json:"version"`
|
||||
URL string `json:"url"`
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Source object represents the thing that was cataloged
|
||||
type Source struct {
|
||||
Type string `json:"type"`
|
||||
Target interface{} `json:"target"`
|
||||
}
|
||||
|
||||
// sourceUnpacker is used to unmarshal Source objects
|
||||
type sourceUnpacker struct {
|
||||
Type string `json:"type"`
|
||||
Target json.RawMessage `json:"target"`
|
||||
}
|
||||
|
||||
// NewSource creates a new source object to be represented into JSON.
|
||||
func NewSource(src source.Metadata) (Source, error) {
|
||||
switch src.Scheme {
|
||||
case source.ImageScheme:
|
||||
return Source{
|
||||
Type: "image",
|
||||
Target: src.ImageMetadata,
|
||||
}, nil
|
||||
case source.DirectoryScheme:
|
||||
return Source{
|
||||
Type: "directory",
|
||||
Target: src.Path,
|
||||
}, nil
|
||||
default:
|
||||
return Source{}, fmt.Errorf("unsupported source: %T", src)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates a source object from JSON bytes.
|
||||
func (s *Source) UnmarshalJSON(b []byte) error {
|
||||
var unpacker sourceUnpacker
|
||||
if err := json.Unmarshal(b, &unpacker); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Type = unpacker.Type
|
||||
|
||||
switch s.Type {
|
||||
case "image":
|
||||
var payload source.ImageMetadata
|
||||
if err := json.Unmarshal(unpacker.Target, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Target = payload
|
||||
default:
|
||||
return fmt.Errorf("unsupported package metadata type: %+v", s.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToSourceMetadata takes a source object represented from JSON and creates a source.Metadata object.
|
||||
func (s *Source) ToSourceMetadata() source.Metadata {
|
||||
var metadata source.Metadata
|
||||
switch s.Type {
|
||||
case "directory":
|
||||
metadata.Scheme = source.DirectoryScheme
|
||||
metadata.Path = s.Target.(string)
|
||||
case "image":
|
||||
metadata.Scheme = source.ImageScheme
|
||||
metadata.ImageMetadata = s.Target.(source.ImageMetadata)
|
||||
}
|
||||
return metadata
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
|
||||
FROM scratch
|
||||
ADD file-1.txt /somefile-1.txt
|
||||
ADD file-2.txt /somefile-2.txt
|
||||
# note: adding a directory will behave differently on docker engine v18 vs v19
|
||||
ADD target /
|
|
@ -1 +0,0 @@
|
|||
this file has contents
|
|
@ -1 +0,0 @@
|
|||
file-2 contents!
|
|
@ -1,2 +0,0 @@
|
|||
another file!
|
||||
with lines...
|
|
@ -1,81 +0,0 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "package-1-id",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
"sitePackagesRootPath": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-2-id",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
"version": "2.0.1",
|
||||
"sourceVersion": "",
|
||||
"architecture": "",
|
||||
"maintainer": "",
|
||||
"installedSize": 0,
|
||||
"files": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"type": "directory",
|
||||
"target": "/some/path"
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": "",
|
||||
"idLike": ""
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
},
|
||||
"schema": {
|
||||
"version": "1.0.3",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.3.json"
|
||||
},
|
||||
"artifactRelationships": []
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "package-1-id",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:2abd6dc7d143b384177da9a30f5311eb1f6c4e920eb6e218ec32b58c1a8806b1"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-1",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
"sitePackagesRootPath": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "package-2-id",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:63574b0cbd9fcfc799d82a449c0975ee3f3560747b0957f042938bdec902c4cc"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
"version": "2.0.1",
|
||||
"sourceVersion": "",
|
||||
"architecture": "",
|
||||
"maintainer": "",
|
||||
"installedSize": 0,
|
||||
"files": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:0a7326bbb1c3c467969c7a73deb6366b1e34eb034616b97f8614ad662cad1ce1",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
|
||||
],
|
||||
"imageSize": 65,
|
||||
"scope": "Squashed",
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:2abd6dc7d143b384177da9a30f5311eb1f6c4e920eb6e218ec32b58c1a8806b1",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:63574b0cbd9fcfc799d82a449c0975ee3f3560747b0957f042938bdec902c4cc",
|
||||
"size": 16
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:b102400548006e8dfa3abf6e2b2e6f1c440e29936d91a36e145736e2fd8cf0a1",
|
||||
"size": 27
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjoxODA4LCJkaWdlc3QiOiJzaGEyNTY6MGE3MzI2YmJiMWMzYzQ2Nzk2OWM3YTczZGViNjM2NmIxZTM0ZWIwMzQ2MTZiOTdmODYxNGFkNjYyY2FkMWNlMSJ9LCJsYXllcnMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLCJzaXplIjoyMDQ4LCJkaWdlc3QiOiJzaGEyNTY6MmFiZDZkYzdkMTQzYjM4NDE3N2RhOWEzMGY1MzExZWIxZjZjNGU5MjBlYjZlMjE4ZWMzMmI1OGMxYTg4MDZiMSJ9LHsibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo2MzU3NGIwY2JkOWZjZmM3OTlkODJhNDQ5YzA5NzVlZTNmMzU2MDc0N2IwOTU3ZjA0MjkzOGJkZWM5MDJjNGNjIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MzU4NCwiZGlnZXN0Ijoic2hhMjU2OmIxMDI0MDA1NDgwMDZlOGRmYTNhYmY2ZTJiMmU2ZjFjNDQwZTI5OTM2ZDkxYTM2ZTE0NTczNmUyZmQ4Y2YwYTEifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJIb3N0bmFtZSI6IiIsIkRvbWFpbm5hbWUiOiIiLCJVc2VyIjoiIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiQ21kIjpudWxsLCJJbWFnZSI6InNoYTI1Njo4YjlhNTM1MmM3YzU3M2RmNTkxYTYwMzA0NzIxMmU3ZmYzOGI3YjRiYzZiMzk1NmI3Zjk2Nzk0MWU3NGMyMjhkIiwiVm9sdW1lcyI6bnVsbCwiV29ya2luZ0RpciI6IiIsIkVudHJ5cG9pbnQiOm51bGwsIk9uQnVpbGQiOm51bGwsIkxhYmVscyI6bnVsbH0sImNvbnRhaW5lcl9jb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6IiIsIkF0dGFjaFN0ZGluIjpmYWxzZSwiQXR0YWNoU3Rkb3V0IjpmYWxzZSwiQXR0YWNoU3RkZXJyIjpmYWxzZSwiVHR5IjpmYWxzZSwiT3BlblN0ZGluIjpmYWxzZSwiU3RkaW5PbmNlIjpmYWxzZSwiRW52IjpbIlBBVEg9L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbjovc2JpbjovYmluIl0sIkNtZCI6WyIvYmluL3NoIiwiLWMiLCIjKG5vcCkgQUREIGRpcjpjOTM3YzZhYTUwODkwN2UyODUwOWI2NDRhMTJmOGQ2YzY3ZDM0ZTk2OWY4M2IxNGRlZTkzZWExN2Q3NjkwMjhhIGluIC8gIl0sIkltYWdlIjoic2hhMjU2OjhiOWE1MzUyYzdjNTczZGY1OTFhNjAzMDQ3MjEyZTdmZjM4YjdiNGJjNmIzOTU2YjdmOTY3OTQxZTc0YzIyOGQiLCJWb2x1bWVzIjpudWxsLCJXb3JraW5nRGlyIjoiIiwiRW50cnlwb2ludCI6bnVsbCwiT25CdWlsZCI6bnVsbCwiTGFiZWxzIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTM6MDg6MzUuNDkyOTI1MzAzWiIsImRvY2tlcl92ZXJzaW9uIjoiMjAuMTAuMiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTAzLTIyVDEzOjA4OjM1LjA5NDMxMjQ3NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgQUREIGZpbGU6YWMzMmRhMjNkNTFlODAxZjAyZjkyNDEyM2VkMzA5OTBlYjNmMGZlYzFiOWVkNGYwYjA2YzI0ZTg4YjljMzY5NSBpbiAvc29tZWZpbGUtMS50eHQgIn0seyJjcmVhdGVkIjoiMjAyMS0wMy0yMlQxMzowODozNS4yODk5MjIwNTFaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOmRmM2I3NDRmNTRhOWIxNmI5YjlhZWQ0MGUzZTk4ZDljYTJiNDlmNWE3N2Q5ZmE4YTk3NjkwZDdiYWY1ODg4MjAgaW4gL3NvbWVmaWxlLTIudHh0ICJ9LHsiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTM6MDg6MzUuNDkyOTI1MzAzWiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIC1jICMobm9wKSBBREQgZGlyOmM5MzdjNmFhNTA4OTA3ZTI4NTA5YjY0NGExMmY4ZDZjNjdkMzRlOTY5ZjgzYjE0ZGVlOTNlYTE3ZDc2OTAyOGEgaW4gLyAifV0sIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1NjoyYWJkNmRjN2QxNDNiMzg0MTc3ZGE5YTMwZjUzMTFlYjFmNmM0ZTkyMGViNmUyMThlYzMyYjU4YzFhODgwNmIxIiwic2hhMjU2OjYzNTc0YjBjYmQ5ZmNmYzc5OWQ4MmE0NDljMDk3NWVlM2YzNTYwNzQ3YjA5NTdmMDQyOTM4YmRlYzkwMmM0Y2MiLCJzaGEyNTY6YjEwMjQwMDU0ODAwNmU4ZGZhM2FiZjZlMmIyZTZmMWM0NDBlMjk5MzZkOTFhMzZlMTQ1NzM2ZTJmZDhjZjBhMSJdfX0="
|
||||
}
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": "",
|
||||
"idLike": ""
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
},
|
||||
"schema": {
|
||||
"version": "1.0.3",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.3.json"
|
||||
},
|
||||
"artifactRelationships": []
|
||||
}
|
Binary file not shown.
|
@ -1,35 +0,0 @@
|
|||
package presenter
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
UnknownPresenter Option = "UnknownPresenter"
|
||||
JSONPresenter Option = "json"
|
||||
TextPresenter Option = "text"
|
||||
TablePresenter Option = "table"
|
||||
CycloneDxPresenter Option = "cyclonedx"
|
||||
)
|
||||
|
||||
var Options = []Option{
|
||||
JSONPresenter,
|
||||
TextPresenter,
|
||||
TablePresenter,
|
||||
CycloneDxPresenter,
|
||||
}
|
||||
|
||||
type Option string
|
||||
|
||||
func ParseOption(userStr string) Option {
|
||||
switch strings.ToLower(userStr) {
|
||||
case string(JSONPresenter):
|
||||
return JSONPresenter
|
||||
case string(TextPresenter):
|
||||
return TextPresenter
|
||||
case string(TablePresenter):
|
||||
return TablePresenter
|
||||
case string(CycloneDxPresenter), "cyclone", "cyclone-dx":
|
||||
return CycloneDxPresenter
|
||||
default:
|
||||
return UnknownPresenter
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
Defines a Presenter interface for displaying catalog results to an io.Writer as well as a helper utility to obtain
|
||||
a specific Presenter implementation given user configuration.
|
||||
*/
|
||||
package presenter
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
|
||||
"github.com/anchore/syft/syft/presenter/cyclonedx"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/presenter/json"
|
||||
"github.com/anchore/syft/syft/presenter/table"
|
||||
"github.com/anchore/syft/syft/presenter/text"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Presenter defines the expected behavior for an object responsible for displaying arbitrary input and processed data
|
||||
// to a given io.Writer.
|
||||
type Presenter interface {
|
||||
Present(io.Writer) error
|
||||
}
|
||||
|
||||
// GetPresenter returns a presenter for images or directories
|
||||
func GetPresenter(option Option, srcMetadata source.Metadata, catalog *pkg.Catalog, d *distro.Distro) Presenter {
|
||||
switch option {
|
||||
case JSONPresenter:
|
||||
return json.NewPresenter(catalog, srcMetadata, d)
|
||||
case TextPresenter:
|
||||
return text.NewPresenter(catalog, srcMetadata)
|
||||
case TablePresenter:
|
||||
return table.NewPresenter(catalog)
|
||||
case CycloneDxPresenter:
|
||||
return cyclonedx.NewPresenter(catalog, srcMetadata)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
|
||||
FROM scratch
|
||||
ADD file-1.txt /somefile-1.txt
|
||||
ADD file-2.txt /somefile-2.txt
|
||||
# note: adding a directory will behave differently on docker engine v18 vs v19
|
||||
ADD target /
|
|
@ -1 +0,0 @@
|
|||
this file has contents
|
|
@ -1 +0,0 @@
|
|||
file-2 contents!
|
|
@ -1,2 +0,0 @@
|
|||
another file!
|
||||
with lines...
|
|
@ -1 +0,0 @@
|
|||
this file has contents
|
|
@ -1 +0,0 @@
|
|||
file-2 contents!
|
|
@ -1 +0,0 @@
|
|||
{"artifacts":[{"name":"package-1","version":"1.0.1","type":"deb","cataloger":"","sources":[],"metadata":null},{"name":"package-2","version":"2.0.1","type":"deb","cataloger":"","sources":[],"metadata":null}],"Source":"/some/path"}
|
Loading…
Reference in a new issue