Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new format abstraction #543

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 8 additions & 6 deletions cmd/packages.go
Expand Up @@ -6,6 +6,8 @@ import (
"io/ioutil"
"os"

"github.com/anchore/syft/syft/format"

"github.com/anchore/stereoscope"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/anchore"
Expand Down Expand Up @@ -48,7 +50,7 @@ const (
)

var (
packagesPresenterOpt packages.PresenterOption
packagesPresenterOpt format.Option
packagesArgs = cobra.MaximumNArgs(1)
packagesCmd = &cobra.Command{
Use: "packages [SOURCE]",
Expand All @@ -71,8 +73,8 @@ var (
}

// set the presenter
presenterOption := packages.ParsePresenterOption(appConfig.Output)
if presenterOption == packages.UnknownPresenterOption {
presenterOption := format.ParseOption(appConfig.Output)
if presenterOption == format.UnknownOption {
return fmt.Errorf("bad --output value '%s'", appConfig.Output)
}
packagesPresenterOpt = presenterOption
Expand Down Expand Up @@ -109,8 +111,8 @@ func setPackageFlags(flags *pflag.FlagSet) {
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))

flags.StringP(
"output", "o", string(packages.TablePresenterOption),
fmt.Sprintf("report output formatter, options=%v", packages.AllPresenters),
"output", "o", string(format.TableOption),
fmt.Sprintf("report output formatter, options=%v", format.AllOptions),
)

flags.StringP(
Expand Down Expand Up @@ -247,7 +249,7 @@ func packagesExecWorker(userInput string) <-chan error {

bus.Publish(partybus.Event{
Type: event.PresenterReady,
Value: packages.Presenter(packagesPresenterOpt, packages.PresenterConfig{
Value: packages.Presenter(packagesPresenterOpt, packages.Config{
SourceMetadata: src.Metadata,
Catalog: catalog,
Distro: d,
Expand Down
7 changes: 4 additions & 3 deletions internal/anchore/import_package_sbom.go
Expand Up @@ -8,7 +8,8 @@ import (
"fmt"
"net/http"

"github.com/anchore/syft/internal/presenter/packages"
"github.com/anchore/syft/internal/formats"
"github.com/anchore/syft/syft/format"

"github.com/wagoodman/go-progress"

Expand All @@ -24,9 +25,9 @@ type packageSBOMImportAPI interface {
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
}

func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) (*external.ImagePackageManifest, error) {
func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, _ source.Scope) (*external.ImagePackageManifest, error) {
var buf bytes.Buffer
pres := packages.NewJSONPresenter(catalog, s, d, scope)
pres := formats.ByOption(format.JSONOption).Presenter(catalog, &s, d)
err := pres.Present(&buf)
if err != nil {
return nil, fmt.Errorf("unable to serialize results: %w", err)
Expand Down
26 changes: 11 additions & 15 deletions internal/anchore/import_package_sbom_test.go
@@ -1,26 +1,23 @@
package anchore

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"

"github.com/anchore/syft/internal/presenter/packages"

"github.com/wagoodman/go-progress"

"github.com/anchore/syft/syft/distro"

"github.com/docker/docker/pkg/ioutils"

"github.com/anchore/client-go/pkg/external"
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/docker/docker/pkg/ioutils"
"github.com/go-test/deep"
"github.com/wagoodman/go-progress"
)

func must(c pkg.CPE, e error) pkg.CPE {
Expand Down Expand Up @@ -87,20 +84,19 @@ func TestPackageSbomToModel(t *testing.T) {
t.Fatalf("unable to marshal model: %+v", err)
}

var buf bytes.Buffer
pres := packages.NewJSONPresenter(c, m, &d, source.AllLayersScope)
if err := pres.Present(&buf); err != nil {
by, err := syft.Encode(c, &m, &d, format.JSONOption)
if err != nil {
t.Fatalf("unable to get expected json: %+v", err)
}

// unmarshal expected result
var expectedDoc packages.JSONDocument
if err := json.Unmarshal(buf.Bytes(), &expectedDoc); err != nil {
var expectedDoc syftjsonModel.Document
if err := json.Unmarshal(by, &expectedDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}

// unmarshal actual result
var actualDoc packages.JSONDocument
var actualDoc syftjsonModel.Document
if err := json.Unmarshal(modelJSON, &actualDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}
Expand Down
14 changes: 14 additions & 0 deletions internal/formats/common/spdxhelpers/description.go
@@ -0,0 +1,14 @@
package spdxhelpers

import "github.com/anchore/syft/syft/pkg"

func Description(p *pkg.Package) string {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
return metadata.Description
case pkg.NpmPackageJSONMetadata:
return metadata.Description
default:
return ""
}
}
56 changes: 56 additions & 0 deletions internal/formats/common/spdxhelpers/description_test.go
@@ -0,0 +1,56 @@
package spdxhelpers

import (
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/stretchr/testify/assert"
)

func Test_Description(t *testing.T) {
tests := []struct {
name string
input pkg.Package
expected string
}{
{
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "no metadata",
input: pkg.Package{},
expected: "",
},
{
name: "from apk",
input: pkg.Package{
Metadata: pkg.ApkMetadata{
Description: "a description!",
},
},
expected: "a description!",
},
{
name: "from npm",
input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{
Description: "a description!",
},
},
expected: "a description!",
},
{
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty",
input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{
Homepage: "",
},
},
expected: "",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Description(&test.input))
})
}
}
22 changes: 22 additions & 0 deletions internal/formats/common/spdxhelpers/download_location.go
@@ -0,0 +1,22 @@
package spdxhelpers

import "github.com/anchore/syft/syft/pkg"

func DownloadLocation(p *pkg.Package) string {
// 3.7: Package Download Location
// Cardinality: mandatory, one
// NONE if there is no download location whatsoever.
// NOASSERTION if:
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
// (ii) the SPDX file creator has made no attempt to determine this field; or
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).

switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
return NoneIfEmpty(metadata.URL)
case pkg.NpmPackageJSONMetadata:
return NoneIfEmpty(metadata.URL)
default:
return "NOASSERTION"
}
}
54 changes: 54 additions & 0 deletions internal/formats/common/spdxhelpers/download_location_test.go
@@ -0,0 +1,54 @@
package spdxhelpers

import (
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/stretchr/testify/assert"
)

func Test_DownloadLocation(t *testing.T) {
tests := []struct {
name string
input pkg.Package
expected string
}{
{
name: "no metadata",
input: pkg.Package{},
expected: "NOASSERTION",
},
{
name: "from apk",
input: pkg.Package{
Metadata: pkg.ApkMetadata{
URL: "http://a-place.gov",
},
},
expected: "http://a-place.gov",
},
{
name: "from npm",
input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{
URL: "http://a-place.gov",
},
},
expected: "http://a-place.gov",
},
{
name: "empty",
input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{
URL: "",
},
},
expected: "NONE",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, DownloadLocation(&test.input))
})
}
}
50 changes: 50 additions & 0 deletions internal/formats/common/spdxhelpers/external_refs.go
@@ -0,0 +1,50 @@
package spdxhelpers

import (
"github.com/anchore/syft/internal/formats/spdx22json/model"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
)

func ExternalRefs(p *pkg.Package) (externalRefs []model.ExternalRef) {
externalRefs = make([]model.ExternalRef, 0)
for _, c := range p.CPEs {
externalRefs = append(externalRefs, model.ExternalRef{
ReferenceCategory: model.SecurityReferenceCategory,
ReferenceLocator: c.BindToFmtString(),
ReferenceType: model.Cpe23ExternalRefType,
})
}

if p.PURL != "" {
externalRefs = append(externalRefs, model.ExternalRef{
ReferenceCategory: model.PackageManagerReferenceCategory,
ReferenceLocator: p.PURL,
ReferenceType: model.PurlExternalRefType,
})
}
return externalRefs
}

func ExtractPURL(refs []model.ExternalRef) string {
for _, r := range refs {
if r.ReferenceType == model.PurlExternalRefType {
return r.ReferenceLocator
}
}
return ""
}

func ExtractCPEs(refs []model.ExternalRef) (cpes []pkg.CPE) {
for _, r := range refs {
if r.ReferenceType == model.Cpe23ExternalRefType {
cpe, err := pkg.NewCPE(r.ReferenceLocator)
if err != nil {
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.ReferenceLocator, err)
continue
}
cpes = append(cpes, cpe)
}
}
return cpes
}
46 changes: 46 additions & 0 deletions internal/formats/common/spdxhelpers/external_refs_test.go
@@ -0,0 +1,46 @@
package spdxhelpers

import (
"testing"

"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/anchore/syft/internal/formats/spdx22json/model"
"github.com/anchore/syft/syft/pkg"
"github.com/stretchr/testify/assert"
)

func Test_ExternalRefs(t *testing.T) {
testCPE := testutils.MustCPE(pkg.NewCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*"))
tests := []struct {
name string
input pkg.Package
expected []model.ExternalRef
}{
{
name: "cpe + purl",
input: pkg.Package{
CPEs: []pkg.CPE{
testCPE,
},
PURL: "a-purl",
},
expected: []model.ExternalRef{
{
ReferenceCategory: model.SecurityReferenceCategory,
ReferenceLocator: testCPE.BindToFmtString(),
ReferenceType: model.Cpe23ExternalRefType,
},
{
ReferenceCategory: model.PackageManagerReferenceCategory,
ReferenceLocator: "a-purl",
ReferenceType: model.PurlExternalRefType,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expected, ExternalRefs(&test.input))
})
}
}