migrate syft/presenter to internal/presenter

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2021-03-18 08:49:08 -04:00
parent 4666ca8469
commit ff4ed40d50
No known key found for this signature in database
GPG key ID: 5CB45AE22BAB7EA7
59 changed files with 641 additions and 699 deletions

View 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
}

View 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
}

View 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
}

View file

@ -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("", " ")

View file

@ -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)

View file

@ -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,

View 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"`
}

View 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
}

View 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)
}

View file

@ -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)
}

View file

@ -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),

View 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)
}
}

View 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
}
}

View 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
}

View 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
}
}

View file

@ -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]
}
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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)
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 /

View file

@ -1,2 +0,0 @@
another file!
with lines...

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -1,6 +0,0 @@
package json
type Schema struct {
Version string `json:"version"`
URL string `json:"url"`
}

View file

@ -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
}

View file

@ -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 /

View file

@ -1 +0,0 @@
this file has contents

View file

@ -1 +0,0 @@
file-2 contents!

View file

@ -1,2 +0,0 @@
another file!
with lines...

View file

@ -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": []
}

View file

@ -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": []
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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 /

View file

@ -1 +0,0 @@
this file has contents

View file

@ -1 +0,0 @@
file-2 contents!

View file

@ -1,2 +0,0 @@
another file!
with lines...

View file

@ -1 +0,0 @@
this file has contents

View file

@ -1 +0,0 @@
file-2 contents!

View file

@ -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"}