replace power-user presenter with syft-json format

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2021-11-17 14:37:01 -05:00
parent 81c956cdbd
commit e809403e94
No known key found for this signature in database
GPG Key ID: 5CB45AE22BAB7EA7
32 changed files with 404 additions and 433 deletions

View File

@ -283,7 +283,7 @@ func packagesExecWorker(userInput string) <-chan error {
bus.Publish(partybus.Event{
Type: event.PresenterReady,
Value: f.Presenter(s),
Value: f.Presenter(s, appConfig),
})
}()
return errs

View File

@ -4,6 +4,8 @@ import (
"fmt"
"os"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/syft/artifact"
"github.com/gookit/color"
@ -13,7 +15,6 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/presenter/poweruser"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/source"
@ -139,7 +140,7 @@ func powerUserExecWorker(userInput string) <-chan error {
bus.Publish(partybus.Event{
Type: event.PresenterReady,
Value: poweruser.NewJSONPresenter(s, *appConfig),
Value: syftjson.Format().Presenter(s, *appConfig),
})
}()

View File

@ -25,7 +25,7 @@ type packageSBOMImportAPI interface {
func packageSbomModel(s sbom.SBOM) (*external.ImagePackageManifest, error) {
var buf bytes.Buffer
err := syftjson.Format().Presenter(s).Present(&buf)
err := syftjson.Format().Presenter(s, nil).Present(&buf)
if err != nil {
return nil, fmt.Errorf("unable to serialize results: %w", err)
}

View File

@ -105,7 +105,7 @@ func TestPackageSbomToModel(t *testing.T) {
}
var buf bytes.Buffer
pres := syftjson.Format().Presenter(s)
pres := syftjson.Format().Presenter(s, nil)
if err := pres.Present(&buf); err != nil {
t.Fatalf("unable to get expected json: %+v", err)
}

View File

@ -163,6 +163,10 @@ func (cfg *Application) parseLogLevelOption() error {
}
}
if cfg.Log.Level == "" {
cfg.Log.Level = cfg.Log.LevelOpt.String()
}
return nil
}

View File

@ -7,7 +7,7 @@ import (
"github.com/anchore/syft/syft/sbom"
)
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
enc := xml.NewEncoder(output)
enc.Indent("", " ")

View File

@ -12,7 +12,7 @@ var updateCycloneDx = flag.Bool("update-cyclonedx", false, "update the *.golden
func TestCycloneDxDirectoryPresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateCycloneDx,
cycloneDxRedactor,
)
@ -21,7 +21,7 @@ func TestCycloneDxDirectoryPresenter(t *testing.T) {
func TestCycloneDxImagePresenter(t *testing.T) {
testImage := "image-simple"
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
Format().Presenter(testutils.ImageInput(t, testImage)),
Format().Presenter(testutils.ImageInput(t, testImage), nil),
testImage,
*updateCycloneDx,
cycloneDxRedactor,

View File

@ -9,7 +9,7 @@ import (
const anchoreNamespace = "https://anchore.com/syft"
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
doc := toFormatModel(s)
enc := json.NewEncoder(output)

View File

@ -12,7 +12,7 @@ var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden f
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateSpdxJson,
spdxJsonRedactor,
)
@ -21,7 +21,7 @@ func TestSPDXJSONDirectoryPresenter(t *testing.T) {
func TestSPDXJSONImagePresenter(t *testing.T) {
testImage := "image-simple"
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot())),
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot()), nil),
testImage,
*updateSpdxJson,
spdxJsonRedactor,

View File

@ -3,18 +3,18 @@
"name": "/some/path",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2021-10-29T16:26:08.995826Z",
"created": "2021-11-17T19:35:54.834877Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-[not provided]"
],
"licenseListVersion": "3.14"
"licenseListVersion": "3.15"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https:/anchore.com/syft/dir/some/path-5362d380-914a-458f-b059-d8d27899574c",
"documentNamespace": "https:/anchore.com/syft/dir/some/path-65e2226e-a61e-4ed1-81bb-56022e1ff1eb",
"packages": [
{
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
"SPDXID": "SPDXRef-2a115ac97d018a0e",
"name": "package-1",
"licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION",
@ -31,15 +31,12 @@
}
],
"filesAnalyzed": false,
"hasFiles": [
"SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
],
"licenseDeclared": "MIT",
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
"versionInfo": "1.0.1"
},
{
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
"SPDXID": "SPDXRef-5e920b2bece2c3ae",
"name": "package-2",
"licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION",
@ -60,20 +57,5 @@
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
"versionInfo": "2.0.1"
}
],
"files": [
{
"SPDXID": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a",
"name": "foo",
"licenseConcluded": "",
"fileName": "/some/path/pkg1/dependencies/foo"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
}
]
}

View File

@ -3,18 +3,18 @@
"name": "user-image-input",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2021-10-29T16:26:09.001799Z",
"created": "2021-11-17T19:35:57.761372Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-[not provided]"
],
"licenseListVersion": "3.14"
"licenseListVersion": "3.15"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-3ad8571c-513f-4fce-944e-5125353c3186",
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-5383918f-ec96-4aa9-b756-ad16e1ada31e",
"packages": [
{
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
"SPDXID": "SPDXRef-888661d4f0362f02",
"name": "package-1",
"licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION",
@ -36,7 +36,7 @@
"versionInfo": "1.0.1"
},
{
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
"SPDXID": "SPDXRef-4068ff5e8926b305",
"name": "package-2",
"licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION",

View File

@ -7,7 +7,7 @@ import (
"github.com/spdx/tools-golang/tvsaver"
)
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
model := toFormatModel(s)
return tvsaver.Save2_2(&model, output)
}

View File

@ -13,7 +13,7 @@ var updateSpdxTagValue = flag.Bool("update-spdx-tv", false, "update the *.golden
func TestSPDXTagValueDirectoryPresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateSpdxTagValue,
spdxTagValueRedactor,
)
@ -22,7 +22,7 @@ func TestSPDXTagValueDirectoryPresenter(t *testing.T) {
func TestSPDXTagValueImagePresenter(t *testing.T) {
testImage := "image-simple"
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot())),
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot()), nil),
testImage,
*updateSpdxTagValue,
spdxTagValueRedactor,

View File

@ -15,7 +15,7 @@ func TestEncodeDecodeCycle(t *testing.T) {
originalSBOM := testutils.ImageInput(t, testImage)
var buf bytes.Buffer
assert.NoError(t, encoder(&buf, originalSBOM))
assert.NoError(t, encoder(&buf, originalSBOM, map[string]string{"config": "value"}))
actualSBOM, err := decoder(bytes.NewReader(buf.Bytes()))
assert.NoError(t, err)

View File

@ -7,9 +7,9 @@ import (
"github.com/anchore/syft/syft/sbom"
)
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, appConfig interface{}) error {
// TODO: application config not available yet
doc := ToFormatModel(s, nil)
doc := ToFormatModel(s, appConfig)
enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload

View File

@ -4,6 +4,14 @@ import (
"flag"
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/internal/formats/common/testutils"
)
@ -11,7 +19,7 @@ var updateJson = flag.Bool("update-json", false, "update the *.golden files for
func TestDirectoryPresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateJson,
)
}
@ -19,8 +27,165 @@ func TestDirectoryPresenter(t *testing.T) {
func TestImagePresenter(t *testing.T) {
testImage := "image-simple"
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot())),
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot()), nil),
testImage,
*updateJson,
)
}
func TestFullJSONDocument(t *testing.T) {
catalog := pkg.NewCatalog()
p1 := pkg.Package{
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/a/place/a",
},
},
},
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Files: []pkg.PythonFileRecord{},
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
}
p2 := pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/b/place/b",
},
},
},
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
Files: []pkg.DpkgFileRecord{},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
}
catalog.Add(p1)
catalog.Add(p2)
s := sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
FileMetadata: map[source.Coordinates]source.FileMetadata{
source.NewLocation("/a/place").Coordinates: {
Mode: 0775,
Type: "directory",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/a/place/a").Coordinates: {
Mode: 0775,
Type: "regularFile",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b").Coordinates: {
Mode: 0775,
Type: "symbolicLink",
LinkDestination: "/c",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b/place/b").Coordinates: {
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
FileDigests: map[source.Coordinates][]file.Digest{
source.NewLocation("/a/place/a").Coordinates: {
{
Algorithm: "sha256",
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
},
},
source.NewLocation("/b/place/b").Coordinates: {
{
Algorithm: "sha256",
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
},
},
},
FileContents: map[source.Coordinates]string{
source.NewLocation("/a/place/a").Coordinates: "the-contents",
},
Distro: &distro.Distro{
Type: distro.RedHat,
RawVersion: "7",
IDLike: "rhel",
},
},
Relationships: []artifact.Relationship{
{
From: p1,
To: p2,
Type: artifact.OwnershipByFileOverlapRelationship,
Data: map[string]string{
"file": "path",
},
},
},
Source: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
UserInput: "user-image-input",
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
ManifestDigest: "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Tags: []string{
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b",
},
Size: 38,
Layers: []source.LayerMetadata{
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:3de16c5b8659a2e8d888b8ded8427be7a5686a3c8c4e4dd30de20f362827285b",
Size: 22,
},
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
Size: 16,
},
},
RawManifest: []byte("eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJh..."),
RawConfig: []byte("eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZp..."),
RepoDigests: []string{},
},
},
}
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(s, map[string]string{
"app": "config",
}),
*updateJson,
)
}

View File

@ -80,7 +80,7 @@
"version": "[not provided]"
},
"schema": {
"version": "1.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json"
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
}
}

View File

@ -1,75 +1,4 @@
{
"fileContents": [
{
"location": {
"path": "/a/place/a"
},
"contents": "the-contents"
}
],
"fileMetadata": [
{
"location": {
"path": "/a/place"
},
"metadata": {
"mode": 775,
"type": "directory",
"userID": 0,
"groupID": 0,
"mimeType": ""
}
},
{
"location": {
"path": "/a/place/a"
},
"metadata": {
"mode": 775,
"type": "regularFile",
"userID": 0,
"groupID": 0,
"digests": [
{
"algorithm": "sha256",
"value": "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703"
}
],
"mimeType": ""
}
},
{
"location": {
"path": "/b"
},
"metadata": {
"mode": 775,
"type": "symbolicLink",
"linkDestination": "/c",
"userID": 0,
"groupID": 0,
"mimeType": ""
}
},
{
"location": {
"path": "/b/place/b"
},
"metadata": {
"mode": 644,
"type": "regularFile",
"userID": 1,
"groupID": 2,
"digests": [
{
"algorithm": "sha256",
"value": "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c"
}
],
"mimeType": ""
}
}
],
"artifacts": [
{
"id": "962403cfb7be50d7",
@ -131,7 +60,84 @@
}
}
],
"artifactRelationships": [],
"artifactRelationships": [
{
"parent": "962403cfb7be50d7",
"child": "b11f44847bba0ed1",
"type": "ownership-by-file-overlap",
"metadata": {
"file": "path"
}
}
],
"files": [
{
"id": "913b4592e2c2ebdf",
"location": {
"path": "/a/place"
},
"metadata": {
"mode": 775,
"type": "directory",
"userID": 0,
"groupID": 0,
"mimeType": ""
}
},
{
"id": "e7c88bd18e11b0b",
"location": {
"path": "/a/place/a"
},
"metadata": {
"mode": 775,
"type": "regularFile",
"userID": 0,
"groupID": 0,
"mimeType": ""
},
"contents": "the-contents",
"digests": [
{
"algorithm": "sha256",
"value": "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703"
}
]
},
{
"id": "5c3dc6885f48b5a1",
"location": {
"path": "/b"
},
"metadata": {
"mode": 775,
"type": "symbolicLink",
"linkDestination": "/c",
"userID": 0,
"groupID": 0,
"mimeType": ""
}
},
{
"id": "799d2f12da0bcec4",
"location": {
"path": "/b/place/b"
},
"metadata": {
"mode": 644,
"type": "regularFile",
"userID": 1,
"groupID": 2,
"mimeType": ""
},
"digests": [
{
"algorithm": "sha256",
"value": "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c"
}
]
}
],
"source": {
"type": "image",
"target": {
@ -169,75 +175,11 @@
"name": "syft",
"version": "[not provided]",
"configuration": {
"configPath": "",
"output": "",
"file": "",
"quiet": false,
"check-for-app-update": false,
"anchore": {
"host": "",
"path": "",
"dockerfile": "",
"overwrite-existing-image": false,
"import-timeout": 0
},
"dev": {
"profile-cpu": false,
"profile-mem": false
},
"log": {
"structured": false,
"level": "",
"file-location": ""
},
"package": {
"cataloger": {
"enabled": false,
"scope": ""
}
},
"file-metadata": {
"cataloger": {
"enabled": false,
"scope": ""
},
"digests": [
"sha256"
]
},
"file-classification": {
"cataloger": {
"enabled": false,
"scope": ""
}
},
"file-contents": {
"cataloger": {
"enabled": false,
"scope": ""
},
"skip-files-above-size": 0,
"globs": null
},
"secrets": {
"cataloger": {
"enabled": false,
"scope": ""
},
"additional-patterns": null,
"exclude-pattern-names": null,
"reveal-values": false,
"skip-files-above-size": 0
},
"registry": {
"insecure-skip-tls-verify": false,
"insecure-use-http": false,
"auth": null
}
"app": "config"
}
},
"schema": {
"version": "1.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json"
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
}
}

View File

@ -101,7 +101,7 @@
"version": "[not provided]"
},
"schema": {
"version": "1.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json"
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
}
}

View File

@ -11,7 +11,7 @@ import (
"github.com/olekukonko/tablewriter"
)
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
var rows [][]string
columns := []string{"Name", "Version", "Type"}

View File

@ -12,7 +12,7 @@ var updateTableGoldenFiles = flag.Bool("update-table", false, "update the *.gold
func TestTablePresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateTableGoldenFiles,
)
}

View File

@ -10,7 +10,7 @@ import (
"github.com/anchore/syft/syft/source"
)
func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
// init the tabular writer
w := new(tabwriter.Writer)
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)

View File

@ -11,7 +11,7 @@ var updateTextPresenterGoldenFiles = flag.Bool("update-text", false, "update the
func TestTextDirectoryPresenter(t *testing.T) {
testutils.AssertPresenterAgainstGoldenSnapshot(t,
Format().Presenter(testutils.DirectoryInput(t)),
Format().Presenter(testutils.DirectoryInput(t), nil),
*updateTextPresenterGoldenFiles,
)
}
@ -19,7 +19,7 @@ func TestTextDirectoryPresenter(t *testing.T) {
func TestTextImagePresenter(t *testing.T) {
testImage := "image-simple"
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot())),
Format().Presenter(testutils.ImageInput(t, testImage, testutils.FromSnapshot()), nil),
testImage,
*updateTextPresenterGoldenFiles,
)

View File

@ -1,34 +0,0 @@
package poweruser
import (
"encoding/json"
"io"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/syft/sbom"
)
// JSONPresenter is a JSON presentation object for the syft results
type JSONPresenter struct {
sbom sbom.SBOM
config interface{}
}
// NewJSONPresenter creates a new JSON presenter object for the given cataloging results.
func NewJSONPresenter(s sbom.SBOM, appConfig interface{}) *JSONPresenter {
return &JSONPresenter{
sbom: s,
config: appConfig,
}
}
// Present the PackageCatalog results to the given writer.
func (p *JSONPresenter) Present(output io.Writer) error {
doc := syftjson.ToFormatModel(p.sbom, p.config)
enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(&doc)
}

View File

@ -1,189 +0,0 @@
package poweruser
import (
"bytes"
"flag"
"testing"
"github.com/anchore/syft/syft/sbom"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/anchore/syft/syft/file"
"github.com/anchore/go-testutils"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
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 {
panic(e)
}
return c
}
func TestJSONPresenter(t *testing.T) {
var buffer bytes.Buffer
catalog := pkg.NewCatalog()
catalog.Add(pkg.Package{
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/a/place/a",
},
},
},
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Files: []pkg.PythonFileRecord{},
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
{
Coordinates: source.Coordinates{
RealPath: "/b/place/b",
},
},
},
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
Files: []pkg.DpkgFileRecord{},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
},
})
appConfig := config.Application{
FileMetadata: config.FileMetadata{
Digests: []string{"sha256"},
},
}
cfg := sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
FileMetadata: map[source.Coordinates]source.FileMetadata{
source.NewLocation("/a/place").Coordinates: {
Mode: 0775,
Type: "directory",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/a/place/a").Coordinates: {
Mode: 0775,
Type: "regularFile",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b").Coordinates: {
Mode: 0775,
Type: "symbolicLink",
LinkDestination: "/c",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b/place/b").Coordinates: {
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
FileDigests: map[source.Coordinates][]file.Digest{
source.NewLocation("/a/place/a").Coordinates: {
{
Algorithm: "sha256",
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
},
},
source.NewLocation("/b/place/b").Coordinates: {
{
Algorithm: "sha256",
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
},
},
},
FileContents: map[source.Coordinates]string{
source.NewLocation("/a/place/a").Coordinates: "the-contents",
},
Distro: &distro.Distro{
Type: distro.RedHat,
RawVersion: "7",
IDLike: "rhel",
},
},
Source: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
UserInput: "user-image-input",
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
ManifestDigest: "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Tags: []string{
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b",
},
Size: 38,
Layers: []source.LayerMetadata{
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:3de16c5b8659a2e8d888b8ded8427be7a5686a3c8c4e4dd30de20f362827285b",
Size: 22,
},
{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Digest: "sha256:366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
Size: 16,
},
},
RawManifest: []byte("eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJh..."),
RawConfig: []byte("eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZp..."),
RepoDigests: []string{},
},
},
}
if err := NewJSONPresenter(cfg, appConfig).Present(&buffer); err != nil {
t.Fatal(err)
}
actual := buffer.Bytes()
if *updateJSONGoldenFiles {
testutils.UpdateGoldenFileContents(t, actual)
}
expected := testutils.GetGoldenFileContents(t)
if !bytes.Equal(expected, actual) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true)
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
}
}

View File

@ -12,14 +12,14 @@ import (
)
// Encode takes all SBOM elements and a format option and encodes an SBOM document.
func Encode(s sbom.SBOM, option format.Option) ([]byte, error) {
func Encode(s sbom.SBOM, appConfig interface{}, option format.Option) ([]byte, error) {
f := formats.ByOption(option)
if f == nil {
return nil, fmt.Errorf("unsupported format: %+v", option)
}
buff := bytes.Buffer{}
if err := f.Encode(&buff, s); err != nil {
if err := f.Encode(&buff, s, appConfig); err != nil {
return nil, fmt.Errorf("unable to encode sbom: %w", err)
}

View File

@ -7,4 +7,4 @@ import (
)
// Encoder is a function that can transform Syft native objects into an SBOM document of a specific format written to the given writer.
type Encoder func(io.Writer, sbom.SBOM) error
type Encoder func(io.Writer, sbom.SBOM, interface{}) error

View File

@ -29,11 +29,11 @@ func NewFormat(option Option, encoder Encoder, decoder Decoder, validator Valida
}
}
func (f Format) Encode(output io.Writer, s sbom.SBOM) error {
func (f Format) Encode(output io.Writer, s sbom.SBOM, appConfig interface{}) error {
if f.encoder == nil {
return ErrEncodingNotSupported
}
return f.encoder(output, s)
return f.encoder(output, s, appConfig)
}
func (f Format) Decode(reader io.Reader) (*sbom.SBOM, error) {
@ -51,9 +51,9 @@ func (f Format) Validate(reader io.Reader) error {
return f.validator(reader)
}
func (f Format) Presenter(s sbom.SBOM) *Presenter {
func (f Format) Presenter(s sbom.SBOM, appConfig interface{}) *Presenter {
if f.encoder == nil {
return nil
}
return NewPresenter(f.encoder, s)
return NewPresenter(f.encoder, s, appConfig)
}

View File

@ -7,17 +7,19 @@ import (
)
type Presenter struct {
sbom sbom.SBOM
encoder Encoder
sbom sbom.SBOM
appConfig interface{}
encoder Encoder
}
func NewPresenter(encoder Encoder, s sbom.SBOM) *Presenter {
func NewPresenter(encoder Encoder, s sbom.SBOM, appConfig interface{}) *Presenter {
return &Presenter{
sbom: s,
encoder: encoder,
sbom: s,
appConfig: appConfig,
encoder: encoder,
}
}
func (pres *Presenter) Present(output io.Writer) error {
return pres.encoder(output, pres.sbom)
return pres.encoder(output, pres.sbom, pres.appConfig)
}

View File

@ -1,6 +1,8 @@
package cataloger
import (
"fmt"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
@ -43,6 +45,7 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
catalog := pkg.NewCatalog()
var allRelationships []artifact.Relationship
// TODO: update to show relationships
filesProcessed, packagesDiscovered := newMonitor()
// perform analysis, accumulating errors for each failed analysis
@ -57,6 +60,7 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
catalogedPackages := len(packages)
// TODO: update to show relationships and files
log.Debugf("package cataloger %q discovered %d packages", theCataloger.Name(), catalogedPackages)
packagesDiscovered.N += int64(catalogedPackages)
@ -67,6 +71,15 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
// generate PURL
p.PURL = generatePackageURL(p, theDistro)
// TODO: break out into another function (refactor this function)
// create file-to-package relationships for files owned by the package
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
if err != nil {
log.Warnf("unable to create any package-file relationships for package name=%q: %w", p.Name, err)
} else {
allRelationships = append(allRelationships, owningRelationships...)
}
// add to catalog
catalog.Add(p)
}
@ -85,3 +98,33 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
return catalog, allRelationships, nil
}
func packageFileOwnershipRelationships(p pkg.Package, resolver source.FilePathResolver) ([]artifact.Relationship, error) {
fileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
return nil, nil
}
var relationships []artifact.Relationship
for _, path := range fileOwner.OwnedFiles() {
locations, err := resolver.FilesByPath(path)
if err != nil {
return nil, fmt.Errorf("unable to find path for path=%q: %w", path, err)
}
//if len(locations) == 0 {
// // TODO: this is notable, we should at least log it(?)... however, ideally there is something in the SBOM about this
//}
for _, l := range locations {
relationships = append(relationships, artifact.Relationship{
From: l.Coordinates,
To: p,
Type: artifact.PackageOfRelationship,
})
}
}
return relationships, nil
}

View File

@ -0,0 +1,51 @@
package source
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCoordinateSet(t *testing.T) {
binA := Coordinates{
RealPath: "/bin",
FileSystemID: "a",
}
binB := Coordinates{
RealPath: "/bin",
FileSystemID: "b",
}
tests := []struct {
name string
input []Coordinates
expected []Coordinates
}{
{
name: "de-dup same location",
input: []Coordinates{
binA, binA, binA,
},
expected: []Coordinates{
binA,
},
},
{
name: "dont de-dup different filesystem",
input: []Coordinates{
binB, binA,
},
expected: []Coordinates{
binA, binB,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, NewCoordinateSet(test.input...).ToSlice())
})
}
}

View File

@ -31,14 +31,18 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
originalSBOM, _ := catalogFixtureImage(t, "image-pkg-coverage")
by1, err := syft.Encode(originalSBOM, test.format)
appConfig := map[string]string{
"config": "value",
}
by1, err := syft.Encode(originalSBOM, appConfig, test.format)
assert.NoError(t, err)
newSBOM, newFormat, err := syft.Decode(bytes.NewReader(by1))
assert.NoError(t, err)
assert.Equal(t, test.format, newFormat)
by2, err := syft.Encode(*newSBOM, test.format)
by2, err := syft.Encode(*newSBOM, appConfig, test.format)
assert.NoError(t, err)
if !assert.True(t, bytes.Equal(by1, by2)) {