Skip to content

Commit

Permalink
Add SPDX JSON format object (anchore#584)
Browse files Browse the repository at this point in the history
* remove existing spdxjson presenter + helpers

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

* add new spdx22json format

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

* add common sdpxhelpers (migrated)

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

* use new common spdx helpers

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

* wire up new spdx22json format object

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

* remove lossless syft-specific property bags

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

* remove spdxjson decoder and validator

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

* add nil checks in spdx test helpers

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

* remove empty default case

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

* use explicit golden snapshot

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
  • Loading branch information
wagoodman committed Oct 29, 2021
1 parent 1bd98ca commit b3f1bd7
Show file tree
Hide file tree
Showing 49 changed files with 1,817 additions and 888 deletions.
23 changes: 23 additions & 0 deletions internal/formats/common/spdxhelpers/description.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package spdxhelpers

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

func Description(p *pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
return metadata.Description
case pkg.NpmPackageJSONMetadata:
return metadata.Description
}
}
return ""
}

func packageExists(p *pkg.Package) bool {
return p != nil
}

func hasMetadata(p *pkg.Package) bool {
return packageExists(p) && p.Metadata != nil
}
56 changes: 56 additions & 0 deletions internal/formats/common/spdxhelpers/description_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
}
}
23 changes: 23 additions & 0 deletions internal/formats/common/spdxhelpers/download_location.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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).

if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
return NoneIfEmpty(metadata.URL)
case pkg.NpmPackageJSONMetadata:
return NoneIfEmpty(metadata.URL)
}
}
return "NOASSERTION"
}
54 changes: 54 additions & 0 deletions internal/formats/common/spdxhelpers/download_location_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
}
}
55 changes: 55 additions & 0 deletions internal/formats/common/spdxhelpers/external_refs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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)

if !packageExists(p) {
return externalRefs
}

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
}
45 changes: 45 additions & 0 deletions internal/formats/common/spdxhelpers/external_refs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package spdxhelpers

import (
"testing"

"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 := pkg.MustCPE("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))
})
}
}
51 changes: 51 additions & 0 deletions internal/formats/common/spdxhelpers/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package spdxhelpers

import (
"crypto/sha256"
"fmt"
"path/filepath"

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

func Files(packageSpdxID string, p *pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
files = make([]model.File, 0)
fileIDs = make([]string, 0)
relationships = make([]model.Relationship, 0)

if !hasMetadata(p) {
return files, fileIDs, relationships
}

pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
return files, fileIDs, relationships
}

for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
baseFileName := filepath.Base(ownedFilePath)
pathHash := sha256.Sum256([]byte(ownedFilePath))
fileSpdxID := model.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()

fileIDs = append(fileIDs, fileSpdxID)

files = append(files, model.File{
FileName: ownedFilePath,
Item: model.Item{
Element: model.Element{
SPDXID: fileSpdxID,
Name: baseFileName,
},
},
})

relationships = append(relationships, model.Relationship{
SpdxElementID: packageSpdxID,
RelationshipType: model.ContainsRelationship,
RelatedSpdxElement: fileSpdxID,
})
}

return files, fileIDs, relationships
}
15 changes: 15 additions & 0 deletions internal/formats/common/spdxhelpers/homepage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package spdxhelpers

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

func Homepage(p *pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.GemMetadata:
return metadata.Homepage
case pkg.NpmPackageJSONMetadata:
return metadata.Homepage
}
}
return ""
}
56 changes: 56 additions & 0 deletions internal/formats/common/spdxhelpers/homepage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package spdxhelpers

import (
"testing"

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

func Test_Homepage(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 gem",
input: pkg.Package{
Metadata: pkg.GemMetadata{
Homepage: "http://a-place.gov",
},
},
expected: "http://a-place.gov",
},
{
name: "from npm",
input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{
Homepage: "http://a-place.gov",
},
},
expected: "http://a-place.gov",
},
{
// 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, Homepage(&test.input))
})
}
}

0 comments on commit b3f1bd7

Please sign in to comment.