mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Add SPDX JSON format object (#584)
* 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>
This commit is contained in:
parent
4a2d1d7225
commit
9aca23f766
23
internal/formats/common/spdxhelpers/description.go
Normal file
23
internal/formats/common/spdxhelpers/description.go
Normal file
@ -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
internal/formats/common/spdxhelpers/description_test.go
Normal file
56
internal/formats/common/spdxhelpers/description_test.go
Normal file
@ -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
internal/formats/common/spdxhelpers/download_location.go
Normal file
23
internal/formats/common/spdxhelpers/download_location.go
Normal file
@ -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"
|
||||||
|
}
|
||||||
@ -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
internal/formats/common/spdxhelpers/external_refs.go
Normal file
55
internal/formats/common/spdxhelpers/external_refs.go
Normal file
@ -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
internal/formats/common/spdxhelpers/external_refs_test.go
Normal file
45
internal/formats/common/spdxhelpers/external_refs_test.go
Normal file
@ -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
internal/formats/common/spdxhelpers/files.go
Normal file
51
internal/formats/common/spdxhelpers/files.go
Normal file
@ -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
internal/formats/common/spdxhelpers/homepage.go
Normal file
15
internal/formats/common/spdxhelpers/homepage.go
Normal file
@ -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
internal/formats/common/spdxhelpers/homepage_test.go
Normal file
56
internal/formats/common/spdxhelpers/homepage_test.go
Normal file
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
internal/formats/common/spdxhelpers/license.go
Normal file
37
internal/formats/common/spdxhelpers/license.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func License(p *pkg.Package) string {
|
||||||
|
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||||
|
// The options to populate this field are limited to:
|
||||||
|
// A valid SPDX License Expression as defined in Appendix IV;
|
||||||
|
// NONE, if the SPDX file creator concludes there is no license available for this package; or
|
||||||
|
// 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 !packageExists(p) || len(p.Licenses) == 0 {
|
||||||
|
return "NONE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||||
|
var parsedLicenses []string
|
||||||
|
for _, l := range p.Licenses {
|
||||||
|
if value, exists := spdxlicense.ID(l); exists {
|
||||||
|
parsedLicenses = append(parsedLicenses, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedLicenses) == 0 {
|
||||||
|
return "NOASSERTION"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parsedLicenses, " AND ")
|
||||||
|
}
|
||||||
73
internal/formats/common/spdxhelpers/license_test.go
Normal file
73
internal/formats/common/spdxhelpers/license_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_License(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input pkg.Package
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no licenses",
|
||||||
|
input: pkg.Package{},
|
||||||
|
expected: "NONE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no SPDX licenses",
|
||||||
|
input: pkg.Package{
|
||||||
|
Licenses: []string{
|
||||||
|
"made-up",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "NOASSERTION",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with SPDX license",
|
||||||
|
input: pkg.Package{
|
||||||
|
Licenses: []string{
|
||||||
|
"MIT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "MIT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with SPDX license expression",
|
||||||
|
input: pkg.Package{
|
||||||
|
Licenses: []string{
|
||||||
|
"MIT",
|
||||||
|
"GPL-3.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "MIT AND GPL-3.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cap insensitive",
|
||||||
|
input: pkg.Package{
|
||||||
|
Licenses: []string{
|
||||||
|
"gpl-3.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "GPL-3.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "debian to spdx conversion",
|
||||||
|
input: pkg.Package{
|
||||||
|
Licenses: []string{
|
||||||
|
"GPL-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "GPL-2.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.expected, License(&test.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
12
internal/formats/common/spdxhelpers/none_if_empty.go
Normal file
12
internal/formats/common/spdxhelpers/none_if_empty.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NoneIfEmpty(value string) string {
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return "NONE"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
41
internal/formats/common/spdxhelpers/none_if_empty_test.go
Normal file
41
internal/formats/common/spdxhelpers/none_if_empty_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_noneIfEmpty(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-zero value",
|
||||||
|
value: "something",
|
||||||
|
expected: "something",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
value: "",
|
||||||
|
expected: "NONE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "space",
|
||||||
|
value: " ",
|
||||||
|
expected: "NONE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tab",
|
||||||
|
value: "\t",
|
||||||
|
expected: "NONE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.expected, NoneIfEmpty(test.value))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
114
internal/formats/common/spdxhelpers/originator_test.go
Normal file
114
internal/formats/common/spdxhelpers/originator_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Originator(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{
|
||||||
|
Authors: []string{
|
||||||
|
"auth1",
|
||||||
|
"auth2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from npm",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.NpmPackageJSONMetadata{
|
||||||
|
Author: "auth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from apk",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.ApkMetadata{
|
||||||
|
Maintainer: "auth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from python - just name",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.PythonPackageMetadata{
|
||||||
|
Author: "auth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from python - just email",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.PythonPackageMetadata{
|
||||||
|
AuthorEmail: "auth@auth.gov",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth@auth.gov",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from python - both name and email",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.PythonPackageMetadata{
|
||||||
|
Author: "auth",
|
||||||
|
AuthorEmail: "auth@auth.gov",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth <auth@auth.gov>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.RpmdbMetadata{
|
||||||
|
Vendor: "auth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from dpkg",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.DpkgMetadata{
|
||||||
|
Maintainer: "auth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "auth",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||||
|
name: "empty",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.NpmPackageJSONMetadata{
|
||||||
|
Author: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.expected, Originator(&test.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
internal/formats/common/spdxhelpers/origintor.go
Normal file
37
internal/formats/common/spdxhelpers/origintor.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Originator(p *pkg.Package) string {
|
||||||
|
if hasMetadata(p) {
|
||||||
|
switch metadata := p.Metadata.(type) {
|
||||||
|
case pkg.ApkMetadata:
|
||||||
|
return metadata.Maintainer
|
||||||
|
case pkg.NpmPackageJSONMetadata:
|
||||||
|
return metadata.Author
|
||||||
|
case pkg.PythonPackageMetadata:
|
||||||
|
author := metadata.Author
|
||||||
|
if author == "" {
|
||||||
|
return metadata.AuthorEmail
|
||||||
|
}
|
||||||
|
if metadata.AuthorEmail != "" {
|
||||||
|
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
||||||
|
}
|
||||||
|
return author
|
||||||
|
case pkg.GemMetadata:
|
||||||
|
if len(metadata.Authors) > 0 {
|
||||||
|
return metadata.Authors[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
case pkg.RpmdbMetadata:
|
||||||
|
return metadata.Vendor
|
||||||
|
case pkg.DpkgMetadata:
|
||||||
|
return metadata.Maintainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
43
internal/formats/common/spdxhelpers/source_info.go
Normal file
43
internal/formats/common/spdxhelpers/source_info.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SourceInfo(p *pkg.Package) string {
|
||||||
|
if !packageExists(p) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
answer := ""
|
||||||
|
switch p.Type {
|
||||||
|
case pkg.RpmPkg:
|
||||||
|
answer = "acquired package info from RPM DB"
|
||||||
|
case pkg.ApkPkg:
|
||||||
|
answer = "acquired package info from APK DB"
|
||||||
|
case pkg.DebPkg:
|
||||||
|
answer = "acquired package info from DPKG DB"
|
||||||
|
case pkg.NpmPkg:
|
||||||
|
answer = "acquired package info from installed node module manifest file"
|
||||||
|
case pkg.PythonPkg:
|
||||||
|
answer = "acquired package info from installed python package manifest file"
|
||||||
|
case pkg.JavaPkg, pkg.JenkinsPluginPkg:
|
||||||
|
answer = "acquired package info from installed java archive"
|
||||||
|
case pkg.GemPkg:
|
||||||
|
answer = "acquired package info from installed gem metadata file"
|
||||||
|
case pkg.GoModulePkg:
|
||||||
|
answer = "acquired package info from go module information"
|
||||||
|
case pkg.RustPkg:
|
||||||
|
answer = "acquired package info from rust cargo manifest"
|
||||||
|
default:
|
||||||
|
answer = "acquired package info from the following paths"
|
||||||
|
}
|
||||||
|
var paths []string
|
||||||
|
for _, l := range p.Locations {
|
||||||
|
paths = append(paths, l.RealPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer + ": " + strings.Join(paths, ", ")
|
||||||
|
}
|
||||||
141
internal/formats/common/spdxhelpers/source_info_test.go
Normal file
141
internal/formats/common/spdxhelpers/source_info_test.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_SourceInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input pkg.Package
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "locations are captured",
|
||||||
|
input: pkg.Package{
|
||||||
|
// note: no type given
|
||||||
|
Locations: []source.Location{
|
||||||
|
{
|
||||||
|
RealPath: "/a-place",
|
||||||
|
VirtualPath: "/b-place",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RealPath: "/c-place",
|
||||||
|
VirtualPath: "/d-place",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from the following paths",
|
||||||
|
"/a-place",
|
||||||
|
"/c-place",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// note: no specific support for this
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.KbPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from the following paths",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from RPM DB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.ApkPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from APK DB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.DebPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from DPKG DB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from installed node module manifest file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from installed python package manifest file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.JavaPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from installed java archive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.JenkinsPluginPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from installed java archive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.GemPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from installed gem metadata file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.GoModulePkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from go module information",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.RustPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from rust cargo manifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var pkgTypes []pkg.Type
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name+" "+string(test.input.Type), func(t *testing.T) {
|
||||||
|
if test.input.Type != "" {
|
||||||
|
pkgTypes = append(pkgTypes, test.input.Type)
|
||||||
|
}
|
||||||
|
actual := SourceInfo(&test.input)
|
||||||
|
for _, expected := range test.expected {
|
||||||
|
assert.Contains(t, actual, expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, pkg.AllPkgs, pkgTypes, "missing one or more package types to test against (maybe a package type was added?)")
|
||||||
|
}
|
||||||
@ -4,10 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/formats/cyclonedx12xml"
|
"github.com/anchore/syft/internal/formats/cyclonedx12xml"
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json"
|
||||||
"github.com/anchore/syft/internal/formats/table"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/formats/syftjson"
|
"github.com/anchore/syft/internal/formats/syftjson"
|
||||||
|
"github.com/anchore/syft/internal/formats/table"
|
||||||
"github.com/anchore/syft/syft/format"
|
"github.com/anchore/syft/syft/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +16,7 @@ func All() []format.Format {
|
|||||||
syftjson.Format(),
|
syftjson.Format(),
|
||||||
table.Format(),
|
table.Format(),
|
||||||
cyclonedx12xml.Format(),
|
cyclonedx12xml.Format(),
|
||||||
|
spdx22json.Format(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
internal/formats/spdx22json/encoder.go
Normal file
24
internal/formats/spdx22json/encoder.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
const anchoreNamespace = "https://anchore.com/syft"
|
||||||
|
|
||||||
|
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, scope source.Scope) error {
|
||||||
|
doc := toFormatModel(catalog, srcMetadata, d, scope)
|
||||||
|
|
||||||
|
enc := json.NewEncoder(output)
|
||||||
|
// prevent > and < from being escaped in the payload
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
|
||||||
|
return enc.Encode(&doc)
|
||||||
|
}
|
||||||
@ -1,19 +1,22 @@
|
|||||||
package packages
|
package spdx22json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||||
|
"github.com/anchore/syft/syft/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json presenters")
|
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json presenters")
|
||||||
|
|
||||||
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
||||||
catalog, metadata, _ := testutils.DirectoryInput(t)
|
catalog, metadata, distro := testutils.DirectoryInput(t)
|
||||||
testutils.AssertPresenterAgainstGoldenSnapshot(t,
|
testutils.AssertPresenterAgainstGoldenSnapshot(t,
|
||||||
NewSPDXJSONPresenter(catalog, metadata),
|
format.NewPresenter(encoder, catalog, &metadata, distro, source.UnknownScope),
|
||||||
*updateSpdxJson,
|
*updateSpdxJson,
|
||||||
spdxJsonRedactor,
|
spdxJsonRedactor,
|
||||||
)
|
)
|
||||||
@ -21,9 +24,9 @@ func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
|||||||
|
|
||||||
func TestSPDXJSONImagePresenter(t *testing.T) {
|
func TestSPDXJSONImagePresenter(t *testing.T) {
|
||||||
testImage := "image-simple"
|
testImage := "image-simple"
|
||||||
catalog, metadata, _ := testutils.ImageInput(t, testImage)
|
catalog, metadata, distro := testutils.ImageInput(t, testImage, testutils.FromSnapshot())
|
||||||
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
|
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
|
||||||
NewSPDXJSONPresenter(catalog, metadata),
|
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
|
||||||
testImage,
|
testImage,
|
||||||
*updateSpdxJson,
|
*updateSpdxJson,
|
||||||
spdxJsonRedactor,
|
spdxJsonRedactor,
|
||||||
13
internal/formats/spdx22json/format.go
Normal file
13
internal/formats/spdx22json/format.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/format"
|
||||||
|
|
||||||
|
// note: this format is LOSSY relative to the syftjson formation, which means that decoding and validation is not supported at this time
|
||||||
|
func Format() format.Format {
|
||||||
|
return format.NewFormat(
|
||||||
|
format.SPDXJSONOption,
|
||||||
|
encoder,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
21
internal/formats/spdx22json/model/annotation.go
Normal file
21
internal/formats/spdx22json/model/annotation.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type AnnotationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReviewerAnnotationType AnnotationType = "REVIEWER"
|
||||||
|
OtherAnnotationType AnnotationType = "OTHER"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Annotation struct {
|
||||||
|
// Identify when the comment was made. This is to be specified according to the combined date and time in the
|
||||||
|
// UTC format, as specified in the ISO 8601 standard.
|
||||||
|
AnnotationDate time.Time `json:"annotationDate"`
|
||||||
|
// Type of the annotation
|
||||||
|
AnnotationType AnnotationType `json:"annotationType"`
|
||||||
|
// This field identifies the person, organization or tool that has commented on a file, package, or the entire document.
|
||||||
|
Annotator string `json:"annotator"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
}
|
||||||
7
internal/formats/spdx22json/model/checksum.go
Normal file
7
internal/formats/spdx22json/model/checksum.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Checksum struct {
|
||||||
|
// Identifies the algorithm used to produce the subject Checksum. One of: "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224"
|
||||||
|
Algorithm string `json:"algorithm"`
|
||||||
|
ChecksumValue string `json:"checksumValue"`
|
||||||
|
}
|
||||||
19
internal/formats/spdx22json/model/creation_info.go
Normal file
19
internal/formats/spdx22json/model/creation_info.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type CreationInfo struct {
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
// Identify when the SPDX file was originally created. The date is to be specified according to combined date and
|
||||||
|
// time in UTC format as specified in ISO 8601 standard. This field is distinct from the fields in section 8,
|
||||||
|
// which involves the addition of information during a subsequent review.
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
// Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an
|
||||||
|
// individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization,
|
||||||
|
// indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version
|
||||||
|
// for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person
|
||||||
|
// name or organization name may be designated as “anonymous” if appropriate.
|
||||||
|
Creators []string `json:"creators"`
|
||||||
|
// An optional field for creators of the SPDX file to provide the version of the SPDX License List used when the SPDX file was created.
|
||||||
|
LicenseListVersion string `json:"licenseListVersion"`
|
||||||
|
}
|
||||||
49
internal/formats/spdx22json/model/document.go
Normal file
49
internal/formats/spdx22json/model/document.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/source"
|
||||||
|
|
||||||
|
// derived from:
|
||||||
|
// - https://spdx.github.io/spdx-spec/appendix-III-RDF-data-model-implementation-and-identifier-syntax/
|
||||||
|
// - https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json
|
||||||
|
// - https://github.com/spdx/spdx-spec/tree/v2.2/ontology
|
||||||
|
|
||||||
|
type Document struct {
|
||||||
|
Element
|
||||||
|
SPDXVersion string `json:"spdxVersion"`
|
||||||
|
// One instance is required for each SPDX file produced. It provides the necessary information for forward
|
||||||
|
// and backward compatibility for processing tools.
|
||||||
|
CreationInfo CreationInfo `json:"creationInfo"`
|
||||||
|
// SyftSourceData contains information about what is being described in this SPDX document (e.g. a container image, a directory, etc)
|
||||||
|
SyftSourceData *source.Metadata `json:"syftSourceData,omitempty"`
|
||||||
|
// 2.2: Data License; should be "CC0-1.0"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// License expression for dataLicense. Compliance with the SPDX specification includes populating the SPDX
|
||||||
|
// fields therein with data related to such fields (\"SPDX-Metadata\"). The SPDX specification contains numerous
|
||||||
|
// fields where an SPDX document creator may provide relevant explanatory text in SPDX-Metadata. Without
|
||||||
|
// opining on the lawfulness of \"database rights\" (in jurisdictions where applicable), such explanatory text
|
||||||
|
// is copyrightable subject matter in most Berne Convention countries. By using the SPDX specification, or any
|
||||||
|
// portion hereof, you hereby agree that any copyright rights (as determined by your jurisdiction) in any
|
||||||
|
// SPDX-Metadata, including without limitation explanatory text, shall be subject to the terms of the Creative
|
||||||
|
// Commons CC0 1.0 Universal license. For SPDX-Metadata not containing any copyright rights, you hereby agree
|
||||||
|
// and acknowledge that the SPDX-Metadata is provided to you \"as-is\" and without any representations or
|
||||||
|
// warranties of any kind concerning the SPDX-Metadata, express, implied, statutory or otherwise, including
|
||||||
|
// without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement,
|
||||||
|
// or the absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not
|
||||||
|
// discoverable, all to the greatest extent permissible under applicable law.
|
||||||
|
DataLicense string `json:"dataLicense"`
|
||||||
|
// Information about an external SPDX document reference including the checksum. This allows for verification of the external references.
|
||||||
|
ExternalDocumentRefs []ExternalDocumentRef `json:"externalDocumentRefs,omitempty"`
|
||||||
|
// Indicates that a particular ExtractedLicensingInfo was defined in the subject SpdxDocument.
|
||||||
|
HasExtractedLicensingInfos []HasExtractedLicensingInfo `json:"hasExtractedLicensingInfos,omitempty"`
|
||||||
|
// note: found in example documents from SPDX, but not in the JSON schema. See https://spdx.github.io/spdx-spec/2-document-creation-information/#25-spdx-document-namespace
|
||||||
|
DocumentNamespace string `json:"documentNamespace"`
|
||||||
|
// note: found in example documents from SPDX, but not in the JSON schema
|
||||||
|
// DocumentDescribes []string `json:"documentDescribes"`
|
||||||
|
Packages []Package `json:"packages"`
|
||||||
|
// Files referenced in the SPDX document
|
||||||
|
Files []File `json:"files,omitempty"`
|
||||||
|
// Snippets referenced in the SPDX document
|
||||||
|
Snippets []Snippet `json:"snippets,omitempty"`
|
||||||
|
// Relationships referenced in the SPDX document
|
||||||
|
Relationships []Relationship `json:"relationships,omitempty"`
|
||||||
|
}
|
||||||
12
internal/formats/spdx22json/model/element.go
Normal file
12
internal/formats/spdx22json/model/element.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Element struct {
|
||||||
|
SPDXID string `json:"SPDXID"`
|
||||||
|
// Identify name of this SpdxElement.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Relationships referenced in the SPDX document
|
||||||
|
Relationships []Relationship `json:"relationships,omitempty"`
|
||||||
|
// Provide additional information about an SpdxElement.
|
||||||
|
Annotations []Annotation `json:"annotations,omitempty"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
}
|
||||||
37
internal/formats/spdx22json/model/element_id.go
Normal file
37
internal/formats/spdx22json/model/element_id.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// ElementID represents the identifier string portion of an SPDX element
|
||||||
|
// identifier. DocElementID should be used for any attributes which can
|
||||||
|
// contain identifiers defined in a different SPDX document.
|
||||||
|
// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion.
|
||||||
|
type ElementID string
|
||||||
|
|
||||||
|
func (e ElementID) String() string {
|
||||||
|
return "SPDXRef-" + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocElementID represents an SPDX element identifier that could be defined
|
||||||
|
// in a different SPDX document, and therefore could have a "DocumentRef-"
|
||||||
|
// portion, such as Relationship and Annotations.
|
||||||
|
// ElementID is used for attributes in which a "DocumentRef-" portion cannot
|
||||||
|
// appear, such as a Package or File definition (since it is necessarily
|
||||||
|
// being defined in the present document).
|
||||||
|
// DocumentRefID will be the empty string for elements defined in the
|
||||||
|
// present document.
|
||||||
|
// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
|
||||||
|
// 'SPDXRef-' portions.
|
||||||
|
type DocElementID struct {
|
||||||
|
DocumentRefID string
|
||||||
|
ElementRefID ElementID
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderDocElementID takes a DocElementID and returns the string equivalent,
|
||||||
|
// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
|
||||||
|
// reinserted.
|
||||||
|
func (d DocElementID) String() string {
|
||||||
|
prefix := ""
|
||||||
|
if d.DocumentRefID != "" {
|
||||||
|
prefix = "DocumentRef-" + d.DocumentRefID + ":"
|
||||||
|
}
|
||||||
|
return prefix + d.ElementRefID.String()
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ExternalDocumentRef struct {
|
||||||
|
// externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.
|
||||||
|
ExternalDocumentID string `json:"externalDocumentId"`
|
||||||
|
Checksum Checksum `json:"checksum"`
|
||||||
|
// SPDX ID for SpdxDocument. A propoerty containing an SPDX document.
|
||||||
|
SpdxDocument string `json:"spdxDocument"`
|
||||||
|
}
|
||||||
43
internal/formats/spdx22json/model/external_ref.go
Normal file
43
internal/formats/spdx22json/model/external_ref.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ReferenceCategory string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SecurityReferenceCategory ReferenceCategory = "SECURITY"
|
||||||
|
PackageManagerReferenceCategory ReferenceCategory = "PACKAGE_MANAGER"
|
||||||
|
OtherReferenceCategory ReferenceCategory = "OTHER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// source: https://spdx.github.io/spdx-spec/appendix-VI-external-repository-identifiers/
|
||||||
|
|
||||||
|
type ExternalRefType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// see https://nvd.nist.gov/cpe
|
||||||
|
Cpe22ExternalRefType ExternalRefType = "cpe22Type"
|
||||||
|
// see https://nvd.nist.gov/cpe
|
||||||
|
Cpe23ExternalRefType ExternalRefType = "cpe23Type"
|
||||||
|
// see http://repo1.maven.org/maven2/
|
||||||
|
MavenCentralExternalRefType ExternalRefType = "maven-central"
|
||||||
|
// see https://www.npmjs.com/
|
||||||
|
NpmExternalRefType ExternalRefType = "npm"
|
||||||
|
// see https://www.nuget.org/
|
||||||
|
NugetExternalRefType ExternalRefType = "nuget"
|
||||||
|
// see http://bower.io/
|
||||||
|
BowerExternalRefType ExternalRefType = "bower"
|
||||||
|
// see https://github.com/package-url/purl-spec
|
||||||
|
PurlExternalRefType ExternalRefType = "purl"
|
||||||
|
// These point to objects present in the Software Heritage archive by the means of SoftWare Heritage persistent Identifiers (SWHID)
|
||||||
|
SwhExternalRefType ExternalRefType = "swh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalRef struct {
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
// Category for the external reference.
|
||||||
|
ReferenceCategory ReferenceCategory `json:"referenceCategory"`
|
||||||
|
// The unique string with no spaces necessary to access the package-specific information, metadata, or content
|
||||||
|
// within the target location. The format of the locator is subject to constraints defined by the <type>.
|
||||||
|
ReferenceLocator string `json:"referenceLocator"`
|
||||||
|
// Type of the external reference. These are defined in an appendix in the SPDX specification.
|
||||||
|
ReferenceType ExternalRefType `json:"referenceType"`
|
||||||
|
}
|
||||||
41
internal/formats/spdx22json/model/file.go
Normal file
41
internal/formats/spdx22json/model/file.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type FileType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DocumentationFileType FileType = "DOCUMENTATION"
|
||||||
|
ImageFileType FileType = "IMAGE"
|
||||||
|
VideoFileType FileType = "VIDEO"
|
||||||
|
ArchiveFileType FileType = "ARCHIVE"
|
||||||
|
SpdxFileType FileType = "SPDX"
|
||||||
|
ApplicationFileType FileType = "APPLICATION"
|
||||||
|
SourceFileType FileType = "SOURCE"
|
||||||
|
BinaryFileType FileType = "BINARY"
|
||||||
|
TextFileType FileType = "TEXT"
|
||||||
|
AudioFileType FileType = "AUDIO"
|
||||||
|
OtherFileType FileType = "OTHER"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Item
|
||||||
|
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
|
||||||
|
// contents of a File or Package have not changed.
|
||||||
|
Checksums []Checksum `json:"checksums,omitempty"`
|
||||||
|
// This field provides a place for the SPDX file creator to record file contributors. Contributors could include
|
||||||
|
// names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.
|
||||||
|
FileContributors []string `json:"fileContributors,omitempty"`
|
||||||
|
// Each element is a SPDX ID for a File.
|
||||||
|
FileDependencies []string `json:"fileDependencies,omitempty"`
|
||||||
|
// The name of the file relative to the root of the package.
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
// The type of the file
|
||||||
|
FileTypes []string `json:"fileTypes,omitempty"`
|
||||||
|
// This field provides a place for the SPDX file creator to record potential legal notices found in the file.
|
||||||
|
// This may or may not include copyright statements.
|
||||||
|
NoticeText string `json:"noticeText,omitempty"`
|
||||||
|
// Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name
|
||||||
|
// properties and the URI (if one is known) of doap:Project resources that are values of this property. All other
|
||||||
|
// properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or
|
||||||
|
// from some SPDX formats(deprecated).
|
||||||
|
ArtifactOf []string `json:"artifactOf,omitempty"`
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type HasExtractedLicensingInfo struct {
|
||||||
|
// Verbatim license or licensing notice text that was discovered.
|
||||||
|
ExtractedText string `json:"extractedText"`
|
||||||
|
// A human readable short form license identifier for a license. The license ID is iether on the standard license
|
||||||
|
// oist or the form \"LicenseRef-\"[idString] where [idString] is a unique string containing letters,
|
||||||
|
// numbers, \".\", \"-\" or \"+\".
|
||||||
|
LicenseID string `json:"licenseId"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
// Identify name of this SpdxElement.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
SeeAlsos []string `json:"seeAlsos,omitempty"`
|
||||||
|
}
|
||||||
22
internal/formats/spdx22json/model/item.go
Normal file
22
internal/formats/spdx22json/model/item.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
Element
|
||||||
|
// The licenseComments property allows the preparer of the SPDX document to describe why the licensing in
|
||||||
|
// spdx:licenseConcluded was chosen.
|
||||||
|
LicenseComments string `json:"licenseComments,omitempty"`
|
||||||
|
LicenseConcluded string `json:"licenseConcluded"`
|
||||||
|
// The licensing information that was discovered directly within the package. There will be an instance of this
|
||||||
|
// property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.
|
||||||
|
LicenseInfoFromFiles []string `json:"licenseInfoFromFiles,omitempty"`
|
||||||
|
// Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.
|
||||||
|
LicenseInfoInFiles []string `json:"licenseInfoInFiles,omitempty"`
|
||||||
|
// The text of copyright declarations recited in the Package or File.
|
||||||
|
CopyrightText string `json:"copyrightText,omitempty"`
|
||||||
|
// This field provides a place for the SPDX data creator to record acknowledgements that may be required to be
|
||||||
|
// communicated in some contexts. This is not meant to include the actual complete license text (see
|
||||||
|
// licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText).
|
||||||
|
// The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from
|
||||||
|
// license texts, which may be necessary or desirable to reproduce.
|
||||||
|
AttributionTexts []string `json:"attributionTexts,omitempty"`
|
||||||
|
}
|
||||||
50
internal/formats/spdx22json/model/package.go
Normal file
50
internal/formats/spdx22json/model/package.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
Item
|
||||||
|
// The checksum property provides a mechanism that can be used to verify that the contents of a File or
|
||||||
|
// Package have not changed.
|
||||||
|
Checksums []Checksum `json:"checksums,omitempty"`
|
||||||
|
// Provides a detailed description of the package.
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
// The URI at which this package is available for download. Private (i.e., not publicly reachable) URIs are
|
||||||
|
// acceptable as values of this property. The values http://spdx.org/rdf/terms#none and http://spdx.org/rdf/terms#noassertion
|
||||||
|
// may be used to specify that the package is not downloadable or that no attempt was made to determine its
|
||||||
|
// download location, respectively.
|
||||||
|
DownloadLocation string `json:"downloadLocation,omitempty"`
|
||||||
|
// An External Reference allows a Package to reference an external source of additional information, metadata,
|
||||||
|
// enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.
|
||||||
|
ExternalRefs []ExternalRef `json:"externalRefs,omitempty"`
|
||||||
|
// Indicates whether the file content of this package has been available for or subjected to analysis when
|
||||||
|
// creating the SPDX document. If false indicates packages that represent metadata or URI references to a
|
||||||
|
// project, product, artifact, distribution or a component. If set to false, the package must not contain any files
|
||||||
|
FilesAnalyzed bool `json:"filesAnalyzed"`
|
||||||
|
// Indicates that a particular file belongs to a package (elements are SPDX ID for a File).
|
||||||
|
HasFiles []string `json:"hasFiles,omitempty"`
|
||||||
|
// Provide a place for the SPDX file creator to record a web site that serves as the package's home page.
|
||||||
|
// This link can also be used to reference further information about the package referenced by the SPDX file creator.
|
||||||
|
Homepage string `json:"homepage,omitempty"`
|
||||||
|
// List the licenses that have been declared by the authors of the package. Any license information that does not
|
||||||
|
// originate from the package authors, e.g. license information from a third party repository, should not be included in this field.
|
||||||
|
LicenseDeclared string `json:"licenseDeclared"`
|
||||||
|
// The name and, optionally, contact information of the person or organization that originally created the package.
|
||||||
|
// Values of this property must conform to the agent and tool syntax.
|
||||||
|
Originator string `json:"originator,omitempty"`
|
||||||
|
// The base name of the package file name. For example, zlib-1.2.5.tar.gz.
|
||||||
|
PackageFileName string `json:"packageFileName,omitempty"`
|
||||||
|
// A manifest based verification code (the algorithm is defined in section 4.7 of the full specification) of the
|
||||||
|
// SPDX Item. This allows consumers of this data and/or database to determine if an SPDX item they have in hand
|
||||||
|
// is identical to the SPDX item from which the data was produced. This algorithm works even if the SPDX document
|
||||||
|
// is included in the SPDX item.
|
||||||
|
PackageVerificationCode *PackageVerificationCode `json:"packageVerificationCode,omitempty"`
|
||||||
|
// Allows the producer(s) of the SPDX document to describe how the package was acquired and/or changed from the original source.
|
||||||
|
SourceInfo string `json:"sourceInfo,omitempty"`
|
||||||
|
// Provides a short description of the package.
|
||||||
|
Summary string `json:"summary,omitempty"`
|
||||||
|
// The name and, optionally, contact information of the person or organization who was the immediate supplier
|
||||||
|
// of this package to the recipient. The supplier may be different than originator when the software has been
|
||||||
|
// repackaged. Values of this property must conform to the agent and tool syntax.
|
||||||
|
Supplier string `json:"supplier,omitempty"`
|
||||||
|
// Provides an indication of the version of the package that is described by this SpdxDocument.
|
||||||
|
VersionInfo string `json:"versionInfo,omitempty"`
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// Why are there two package identifier fields Package Checksum and Package Verification?
|
||||||
|
// Although the values of the two fields Package Checksum and Package Verification are similar, they each serve a
|
||||||
|
// different purpose. The Package Checksum provides a unique identifier of a software package which is computed by
|
||||||
|
// taking the SHA1 of the entire software package file. This enables one to quickly determine if two different copies
|
||||||
|
// of a package are the same. One disadvantage of this approach is that one cannot add an SPDX data file into the
|
||||||
|
// original package without changing the Package Checksum value. Alternatively, the Package Verification field enables
|
||||||
|
// the inclusion of an SPDX file. It enables one to quickly verify if one or more of the original package files has
|
||||||
|
// changed. The Package Verification field is a unique identifier that is based on SHAing only the original package
|
||||||
|
// files (e.g., excluding the SPDX file). This allows one to add an SPDX file to the original package without changing
|
||||||
|
// this unique identifier.
|
||||||
|
// source: https://wiki.spdx.org/view/SPDX_FAQ
|
||||||
|
type PackageVerificationCode struct {
|
||||||
|
// "A file that was excluded when calculating the package verification code. This is usually a file containing
|
||||||
|
// SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded
|
||||||
|
// from the package verification code. If this is not done it would be impossible to correctly calculate the
|
||||||
|
// verification codes in both files.
|
||||||
|
PackageVerificationCodeExcludedFiles []string `json:"packageVerificationCodeExcludedFiles"`
|
||||||
|
|
||||||
|
// The actual package verification code as a hex encoded value.
|
||||||
|
PackageVerificationCodeValue string `json:"packageVerificationCodeValue"`
|
||||||
|
}
|
||||||
183
internal/formats/spdx22json/model/relationship.go
Normal file
183
internal/formats/spdx22json/model/relationship.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Relationship struct {
|
||||||
|
// Id to which the SPDX element is related
|
||||||
|
SpdxElementID string `json:"spdxElementId"`
|
||||||
|
// Describes the type of relationship between two SPDX elements.
|
||||||
|
RelationshipType RelationshipType `json:"relationshipType"`
|
||||||
|
// SPDX ID for SpdxElement. A related SpdxElement.
|
||||||
|
RelatedSpdxElement string `json:"relatedSpdxElement"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// source: https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/
|
||||||
|
type RelationshipType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DescribedByRelationship is to be used when SPDXRef-A is described by SPDXREF-Document.
|
||||||
|
// Example: The package 'WildFly' is described by SPDX document WildFly.spdx.
|
||||||
|
DescribedByRelationship RelationshipType = "DESCRIBED_BY"
|
||||||
|
|
||||||
|
// ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.
|
||||||
|
// Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c.
|
||||||
|
ContainsRelationship RelationshipType = "CONTAINS"
|
||||||
|
|
||||||
|
// ContainedByRelationship is to be used when SPDXRef-A is contained by SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.c is contained by ARCHIVE file bar.tgz
|
||||||
|
ContainedByRelationship RelationshipType = "CONTAINED_BY"
|
||||||
|
|
||||||
|
// DependsOnRelationship is to be used when SPDXRef-A depends on SPDXRef-B.
|
||||||
|
// Example: Package A depends on the presence of package B in order to build and run
|
||||||
|
DependsOnRelationship RelationshipType = "DEPENDS_ON"
|
||||||
|
|
||||||
|
// DependencyOfRelationship is to be used when SPDXRef-A is dependency of SPDXRef-B.
|
||||||
|
// Example: A is explicitly stated as a dependency of B in a machine-readable file. Use when a package manager does not define scopes.
|
||||||
|
DependencyOfRelationship RelationshipType = "DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// DependencyManifestOfRelationship is to be used when SPDXRef-A is a manifest file that lists a set of dependencies for SPDXRef-B.
|
||||||
|
// Example: A file package.json is the dependency manifest of a package foo. Note that only one manifest should be used to define the same dependency graph.
|
||||||
|
DependencyManifestOfRelationship RelationshipType = "DEPENDENCY_MANIFEST_OF"
|
||||||
|
|
||||||
|
// BuildDependencyOfRelationship is to be used when SPDXRef-A is a build dependency of SPDXRef-B.
|
||||||
|
// Example: A is in the compile scope of B in a Maven project.
|
||||||
|
BuildDependencyOfRelationship RelationshipType = "BUILD_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// DevDependencyOfRelationship is to be used when SPDXRef-A is a development dependency of SPDXRef-B.
|
||||||
|
// Example: A is in the devDependencies scope of B in a Maven project.
|
||||||
|
DevDependencyOfRelationship RelationshipType = "DEV_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// OptionalDependencyOfRelationship is to be used when SPDXRef-A is an optional dependency of SPDXRef-B.
|
||||||
|
// Example: Use when building the code will proceed even if a dependency cannot be found, fails to install, or is only installed on a specific platform. For example, A is in the optionalDependencies scope of npm project B.
|
||||||
|
OptionalDependencyOfRelationship RelationshipType = "OPTIONAL_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// ProvidedDependencyOfRelationship is to be used when SPDXRef-A is a to be provided dependency of SPDXRef-B.
|
||||||
|
// Example: A is in the provided scope of B in a Maven project, indicating that the project expects it to be provided, for instance, by the container or JDK.
|
||||||
|
ProvidedDependencyOfRelationship RelationshipType = "PROVIDED_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// TestDependencyOfRelationship is to be used when SPDXRef-A is a test dependency of SPDXRef-B.
|
||||||
|
// Example: A is in the test scope of B in a Maven project.
|
||||||
|
TestDependencyOfRelationship RelationshipType = "TEST_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// RuntimeDependencyOfRelationship is to be used when SPDXRef-A is a dependency required for the execution of SPDXRef-B.
|
||||||
|
// Example: A is in the runtime scope of B in a Maven project.
|
||||||
|
RuntimeDependencyOfRelationship RelationshipType = "RUNTIME_DEPENDENCY_OF"
|
||||||
|
|
||||||
|
// ExampleOfRelationship is to be used when SPDXRef-A is an example of SPDXRef-B.
|
||||||
|
// Example: The file or snippet that illustrates how to use an application or library.
|
||||||
|
ExampleOfRelationship RelationshipType = "EXAMPLE_OF"
|
||||||
|
|
||||||
|
// GeneratesRelationship is to be used when SPDXRef-A generates SPDXRef-B.
|
||||||
|
// Example: A SOURCE file makefile.mk generates a BINARY file a.out
|
||||||
|
GeneratesRelationship RelationshipType = "GENERATES"
|
||||||
|
|
||||||
|
// GeneratedFromRelationship is to be used when SPDXRef-A was generated from SPDXRef-B.
|
||||||
|
// Example: A BINARY file a.out has been generated from a SOURCE file makefile.mk. A BINARY file foolib.a is generated from a SOURCE file bar.c.
|
||||||
|
GeneratedFromRelationship RelationshipType = "GENERATED_FROM"
|
||||||
|
|
||||||
|
// AncestorOfRelationship is to be used when SPDXRef-A is an ancestor (same lineage but pre-dates) SPDXRef-B.
|
||||||
|
// Example: A SOURCE file makefile.mk is a version of the original ancestor SOURCE file 'makefile2.mk'
|
||||||
|
AncestorOfRelationship RelationshipType = "ANCESTOR_OF"
|
||||||
|
|
||||||
|
// DescendantOfRelationship is to be used when SPDXRef-A is a descendant of (same lineage but postdates) SPDXRef-B.
|
||||||
|
// Example: A SOURCE file makefile2.mk is a descendant of the original SOURCE file 'makefile.mk'
|
||||||
|
DescendantOfRelationship RelationshipType = "DESCENDANT_OF"
|
||||||
|
|
||||||
|
// VariantOfRelationship is to be used when SPDXRef-A is a variant of (same lineage but not clear which came first) SPDXRef-B.
|
||||||
|
// Example: A SOURCE file makefile2.mk is a variant of SOURCE file makefile.mk if they differ by some edit, but there is no way to tell which came first (no reliable date information).
|
||||||
|
VariantOfRelationship RelationshipType = "VARIANT_OF"
|
||||||
|
|
||||||
|
// DistributionArtifactRelationship is to be used when distributing SPDXRef-A requires that SPDXRef-B also be distributed.
|
||||||
|
// Example: A BINARY file foo.o requires that the ARCHIVE file bar-sources.tgz be made available on distribution.
|
||||||
|
DistributionArtifactRelationship RelationshipType = "DISTRIBUTION_ARTIFACT"
|
||||||
|
|
||||||
|
// PatchForRelationship is to be used when SPDXRef-A is a patch file for (to be applied to) SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.diff is a patch file for SOURCE file foo.c.
|
||||||
|
PatchForRelationship RelationshipType = "PATCH_FOR"
|
||||||
|
|
||||||
|
// PatchAppliedRelationship is to be used when SPDXRef-A is a patch file that has been applied to SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.diff is a patch file that has been applied to SOURCE file 'foo-patched.c'.
|
||||||
|
PatchAppliedRelationship RelationshipType = "PATCH_APPLIED"
|
||||||
|
|
||||||
|
// CopyOfRelationship is to be used when SPDXRef-A is an exact copy of SPDXRef-B.
|
||||||
|
// Example: A BINARY file alib.a is an exact copy of BINARY file a2lib.a.
|
||||||
|
CopyOfRelationship RelationshipType = "COPY_OF"
|
||||||
|
|
||||||
|
// FileAddedRelationship is to be used when SPDXRef-A is a file that was added to SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.c has been added to package ARCHIVE bar.tgz.
|
||||||
|
FileAddedRelationship RelationshipType = "FILE_ADDED"
|
||||||
|
|
||||||
|
// FileDeletedRelationship is to be used when SPDXRef-A is a file that was deleted from SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.diff has been deleted from package ARCHIVE bar.tgz.
|
||||||
|
FileDeletedRelationship RelationshipType = "FILE_DELETED"
|
||||||
|
|
||||||
|
// FileModifiedRelationship is to be used when SPDXRef-A is a file that was modified from SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.c has been modified from SOURCE file foo.orig.c.
|
||||||
|
FileModifiedRelationship RelationshipType = "FILE_MODIFIED"
|
||||||
|
|
||||||
|
// ExpandedFromArchiveRelationship is to be used when SPDXRef-A is expanded from the archive SPDXRef-B.
|
||||||
|
// Example: A SOURCE file foo.c, has been expanded from the archive ARCHIVE file xyz.tgz.
|
||||||
|
ExpandedFromArchiveRelationship RelationshipType = "EXPANDED_FROM_ARCHIVE"
|
||||||
|
|
||||||
|
// DynamicLinkRelationship is to be used when SPDXRef-A dynamically links to SPDXRef-B.
|
||||||
|
// Example: An APPLICATION file 'myapp' dynamically links to BINARY file zlib.so.
|
||||||
|
DynamicLinkRelationship RelationshipType = "DYNAMIC_LINK"
|
||||||
|
|
||||||
|
// StaticLinkRelationship is to be used when SPDXRef-A statically links to SPDXRef-B.
|
||||||
|
// Example: An APPLICATION file 'myapp' statically links to BINARY zlib.a.
|
||||||
|
StaticLinkRelationship RelationshipType = "STATIC_LINK"
|
||||||
|
|
||||||
|
// DataFileOfRelationship is to be used when SPDXRef-A is a data file used in SPDXRef-B.
|
||||||
|
// Example: An IMAGE file 'kitty.jpg' is a data file of an APPLICATION 'hellokitty'.
|
||||||
|
DataFileOfRelationship RelationshipType = "DATA_FILE_OF"
|
||||||
|
|
||||||
|
// TestCaseOfRelationship is to be used when SPDXRef-A is a test case used in testing SPDXRef-B.
|
||||||
|
// Example: A SOURCE file testMyCode.java is a unit test file used to test an APPLICATION MyPackage.
|
||||||
|
TestCaseOfRelationship RelationshipType = "TEST_CASE_OF"
|
||||||
|
|
||||||
|
// BuildToolOfRelationship is to be used when SPDXRef-A is used to build SPDXRef-B.
|
||||||
|
// Example: A SOURCE file makefile.mk is used to build an APPLICATION 'zlib'.
|
||||||
|
BuildToolOfRelationship RelationshipType = "BUILD_TOOL_OF"
|
||||||
|
|
||||||
|
// DevToolOfRelationship is to be used when SPDXRef-A is used as a development tool for SPDXRef-B.
|
||||||
|
// Example: Any tool used for development such as a code debugger.
|
||||||
|
DevToolOfRelationship RelationshipType = "DEV_TOOL_OF"
|
||||||
|
|
||||||
|
// TestOfRelationship is to be used when SPDXRef-A is used for testing SPDXRef-B.
|
||||||
|
// Example: Generic relationship for cases where it's clear that something is used for testing but unclear whether it's TEST_CASE_OF or TEST_TOOL_OF.
|
||||||
|
TestOfRelationship RelationshipType = "TEST_OF"
|
||||||
|
|
||||||
|
// TestToolOfRelationship is to be used when SPDXRef-A is used as a test tool for SPDXRef-B.
|
||||||
|
// Example: Any tool used to test the code such as ESlint.
|
||||||
|
TestToolOfRelationship RelationshipType = "TEST_TOOL_OF"
|
||||||
|
|
||||||
|
// DocumentationOfRelationship is to be used when SPDXRef-A provides documentation of SPDXRef-B.
|
||||||
|
// Example: A DOCUMENTATION file readme.txt documents the APPLICATION 'zlib'.
|
||||||
|
DocumentationOfRelationship RelationshipType = "DOCUMENTATION_OF"
|
||||||
|
|
||||||
|
// OptionalComponentOfRelationship is to be used when SPDXRef-A is an optional component of SPDXRef-B.
|
||||||
|
// Example: A SOURCE file fool.c (which is in the contributors directory) may or may not be included in the build of APPLICATION 'atthebar'.
|
||||||
|
OptionalComponentOfRelationship RelationshipType = "OPTIONAL_COMPONENT_OF"
|
||||||
|
|
||||||
|
// MetafileOfRelationship is to be used when SPDXRef-A is a metafile of SPDXRef-B.
|
||||||
|
// Example: A SOURCE file pom.xml is a metafile of the APPLICATION 'Apache Xerces'.
|
||||||
|
MetafileOfRelationship RelationshipType = "METAFILE_OF"
|
||||||
|
|
||||||
|
// PackageOfRelationship is to be used when SPDXRef-A is used as a package as part of SPDXRef-B.
|
||||||
|
// Example: A Linux distribution contains an APPLICATION package gawk as part of the distribution MyLinuxDistro.
|
||||||
|
PackageOfRelationship RelationshipType = "PACKAGE_OF"
|
||||||
|
|
||||||
|
// AmendsRelationship is to be used when (current) SPDXRef-DOCUMENT amends the SPDX information in SPDXRef-B.
|
||||||
|
// Example: (Current) SPDX document A version 2 contains a correction to a previous version of the SPDX document A version 1. Note the reserved identifier SPDXRef-DOCUMENT for the current document is required.
|
||||||
|
AmendsRelationship RelationshipType = "AMENDS"
|
||||||
|
|
||||||
|
// PrerequisiteForRelationship is to be used when SPDXRef-A is a prerequisite for SPDXRef-B.
|
||||||
|
// Example: A library bar.dll is a prerequisite or dependency for APPLICATION foo.exe
|
||||||
|
PrerequisiteForRelationship RelationshipType = "PREREQUISITE_FOR"
|
||||||
|
|
||||||
|
// HasPrerequisiteRelationship is to be used when SPDXRef-A has as a prerequisite SPDXRef-B.
|
||||||
|
// Example: An APPLICATION foo.exe has prerequisite or dependency on bar.dll
|
||||||
|
HasPrerequisiteRelationship RelationshipType = "HAS_PREREQUISITE"
|
||||||
|
|
||||||
|
// OtherRelationship is to be used for a relationship which has not been defined in the formal SPDX specification. A description of the relationship should be included in the Relationship comments field.
|
||||||
|
OtherRelationship RelationshipType = "OTHER"
|
||||||
|
)
|
||||||
32
internal/formats/spdx22json/model/snippet.go
Normal file
32
internal/formats/spdx22json/model/snippet.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type StartPointer struct {
|
||||||
|
Offset int `json:"offset,omitempty"`
|
||||||
|
LineNumber int `json:"lineNumber,omitempty"`
|
||||||
|
// SPDX ID for File
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndPointer struct {
|
||||||
|
Offset int `json:"offset,omitempty"`
|
||||||
|
LineNumber int `json:"lineNumber,omitempty"`
|
||||||
|
// SPDX ID for File
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Range struct {
|
||||||
|
StartPointer StartPointer `json:"startPointer"`
|
||||||
|
EndPointer EndPointer `json:"endPointer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snippet struct {
|
||||||
|
Item
|
||||||
|
// Licensing information that was discovered directly in the subject snippet. This is also considered a declared
|
||||||
|
// license for the snippet. (elements are license expressions)
|
||||||
|
LicenseInfoInSnippets []string `json:"licenseInfoInSnippets"`
|
||||||
|
// SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).
|
||||||
|
SnippetFromFile string `json:"snippetFromFile"`
|
||||||
|
// (At least 1 range is required). This field defines the byte range in the original host file (in X.2) that the
|
||||||
|
// snippet information applies to.
|
||||||
|
Ranges []Range `json:"ranges"`
|
||||||
|
}
|
||||||
3
internal/formats/spdx22json/model/version.go
Normal file
3
internal/formats/spdx22json/model/version.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
const Version = "SPDX-2.2"
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
# 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
|
||||||
@ -0,0 +1 @@
|
|||||||
|
this file has contents
|
||||||
@ -0,0 +1 @@
|
|||||||
|
file-2 contents!
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"name": "/some/path",
|
||||||
|
"spdxVersion": "SPDX-2.2",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2021-10-22T19:25:38.33537Z",
|
||||||
|
"creators": [
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: syft-[not provided]"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.14"
|
||||||
|
},
|
||||||
|
"syftSourceData": {
|
||||||
|
"Scheme": "DirectoryScheme",
|
||||||
|
"ImageMetadata": {
|
||||||
|
"userInput": "",
|
||||||
|
"imageID": "",
|
||||||
|
"manifestDigest": "",
|
||||||
|
"mediaType": "",
|
||||||
|
"tags": null,
|
||||||
|
"imageSize": 0,
|
||||||
|
"layers": null,
|
||||||
|
"manifest": null,
|
||||||
|
"config": null,
|
||||||
|
"repoDigests": null
|
||||||
|
},
|
||||||
|
"Path": "/some/path"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https:/anchore.com/syft/dir/some/path-a868c45f-e62b-473f-9dd3-b72994be6294",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||||
|
"name": "package-1",
|
||||||
|
"licenseConcluded": "MIT",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
"name": "package-2",
|
||||||
|
"licenseConcluded": "NONE",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"name": "user-image-input",
|
||||||
|
"spdxVersion": "SPDX-2.2",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2021-10-22T19:25:38.341582Z",
|
||||||
|
"creators": [
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: syft-[not provided]"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.14"
|
||||||
|
},
|
||||||
|
"syftSourceData": {
|
||||||
|
"Scheme": "ImageScheme",
|
||||||
|
"ImageMetadata": {
|
||||||
|
"userInput": "user-image-input",
|
||||||
|
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
|
||||||
|
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"tags": [
|
||||||
|
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b"
|
||||||
|
],
|
||||||
|
"imageSize": 38,
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||||
|
"size": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||||
|
"size": 16
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||||
|
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||||
|
"repoDigests": []
|
||||||
|
},
|
||||||
|
"Path": ""
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-7c996682-9cdf-45cd-b70b-e771d740c9ed",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||||
|
"name": "package-1",
|
||||||
|
"licenseConcluded": "MIT",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-1",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "MIT",
|
||||||
|
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||||
|
"versionInfo": "1.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||||
|
"name": "package-2",
|
||||||
|
"licenseConcluded": "NONE",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||||
|
"versionInfo": "2.0.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
133
internal/formats/spdx22json/to_format_model.go
Normal file
133
internal/formats/spdx22json/to_format_model.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||||
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
|
"github.com/anchore/syft/internal/version"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||||
|
func toFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, _ *distro.Distro, _ source.Scope) model.Document {
|
||||||
|
name := documentName(srcMetadata)
|
||||||
|
packages, files, relationships := extractFromCatalog(catalog)
|
||||||
|
|
||||||
|
return model.Document{
|
||||||
|
Element: model.Element{
|
||||||
|
SPDXID: model.ElementID("DOCUMENT").String(),
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
SPDXVersion: model.Version,
|
||||||
|
CreationInfo: model.CreationInfo{
|
||||||
|
Created: time.Now().UTC(),
|
||||||
|
Creators: []string{
|
||||||
|
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
||||||
|
},
|
||||||
|
LicenseListVersion: spdxlicense.Version,
|
||||||
|
},
|
||||||
|
DataLicense: "CC0-1.0",
|
||||||
|
DocumentNamespace: documentNamespace(name, srcMetadata),
|
||||||
|
Packages: packages,
|
||||||
|
Files: files,
|
||||||
|
Relationships: relationships,
|
||||||
|
// TODO: add scope
|
||||||
|
SyftSourceData: srcMetadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentName(srcMetadata *source.Metadata) string {
|
||||||
|
if srcMetadata != nil {
|
||||||
|
switch srcMetadata.Scheme {
|
||||||
|
case source.ImageScheme:
|
||||||
|
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||||
|
case source.DirectoryScheme:
|
||||||
|
return cleanSPDXName(srcMetadata.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: is this alright?
|
||||||
|
return uuid.Must(uuid.NewRandom()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentNamespace(name string, srcMetadata *source.Metadata) string {
|
||||||
|
input := "unknown-source-type"
|
||||||
|
if srcMetadata != nil {
|
||||||
|
switch srcMetadata.Scheme {
|
||||||
|
case source.ImageScheme:
|
||||||
|
input = "image"
|
||||||
|
case source.DirectoryScheme:
|
||||||
|
input = "dir"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueID := uuid.Must(uuid.NewRandom())
|
||||||
|
identifier := path.Join(input, uniqueID.String())
|
||||||
|
if name != "." {
|
||||||
|
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(anchoreNamespace, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
|
||||||
|
packages := make([]model.Package, 0)
|
||||||
|
relationships := make([]model.Relationship, 0)
|
||||||
|
files := make([]model.File, 0)
|
||||||
|
|
||||||
|
for _, p := range catalog.Sorted() {
|
||||||
|
license := spdxhelpers.License(p)
|
||||||
|
packageSpdxID := model.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
|
||||||
|
|
||||||
|
packageFiles, fileIDs, packageFileRelationships := spdxhelpers.Files(packageSpdxID, p)
|
||||||
|
files = append(files, packageFiles...)
|
||||||
|
|
||||||
|
relationships = append(relationships, packageFileRelationships...)
|
||||||
|
|
||||||
|
// note: the license concluded and declared should be the same since we are collecting license information
|
||||||
|
// from the project data itself (the installed package files).
|
||||||
|
packages = append(packages, model.Package{
|
||||||
|
Description: spdxhelpers.Description(p),
|
||||||
|
DownloadLocation: spdxhelpers.DownloadLocation(p),
|
||||||
|
ExternalRefs: spdxhelpers.ExternalRefs(p),
|
||||||
|
FilesAnalyzed: false,
|
||||||
|
HasFiles: fileIDs,
|
||||||
|
Homepage: spdxhelpers.Homepage(p),
|
||||||
|
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
|
||||||
|
Originator: spdxhelpers.Originator(p),
|
||||||
|
SourceInfo: spdxhelpers.SourceInfo(p),
|
||||||
|
VersionInfo: p.Version,
|
||||||
|
Item: model.Item{
|
||||||
|
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
|
||||||
|
Element: model.Element{
|
||||||
|
SPDXID: packageSpdxID,
|
||||||
|
Name: p.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages, files, relationships
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSPDXName(name string) string {
|
||||||
|
// remove # according to specification
|
||||||
|
name = strings.ReplaceAll(name, "#", "-")
|
||||||
|
|
||||||
|
// remove : for url construction
|
||||||
|
name = strings.ReplaceAll(name, ":", "-")
|
||||||
|
|
||||||
|
// clean relative pathing
|
||||||
|
return path.Clean(name)
|
||||||
|
}
|
||||||
@ -1,207 +0,0 @@
|
|||||||
package packages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getSPDXExternalRefs(p *pkg.Package) (externalRefs []spdx22.ExternalRef) {
|
|
||||||
externalRefs = make([]spdx22.ExternalRef, 0)
|
|
||||||
for _, c := range p.CPEs {
|
|
||||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
|
||||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
|
||||||
ReferenceLocator: c.BindToFmtString(),
|
|
||||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PURL != "" {
|
|
||||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
|
||||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
|
||||||
ReferenceLocator: p.PURL,
|
|
||||||
ReferenceType: spdx22.PurlExternalRefType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return externalRefs
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXFiles(packageSpdxID string, p *pkg.Package) (files []spdx22.File, fileIDs []string, relationships []spdx22.Relationship) {
|
|
||||||
files = make([]spdx22.File, 0)
|
|
||||||
fileIDs = make([]string, 0)
|
|
||||||
relationships = make([]spdx22.Relationship, 0)
|
|
||||||
|
|
||||||
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 := spdx22.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()
|
|
||||||
|
|
||||||
fileIDs = append(fileIDs, fileSpdxID)
|
|
||||||
|
|
||||||
files = append(files, spdx22.File{
|
|
||||||
FileName: ownedFilePath,
|
|
||||||
Item: spdx22.Item{
|
|
||||||
Element: spdx22.Element{
|
|
||||||
SPDXID: fileSpdxID,
|
|
||||||
Name: baseFileName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
relationships = append(relationships, spdx22.Relationship{
|
|
||||||
SpdxElementID: packageSpdxID,
|
|
||||||
RelationshipType: spdx22.ContainsRelationship,
|
|
||||||
RelatedSpdxElement: fileSpdxID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, fileIDs, relationships
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXLicense(p *pkg.Package) string {
|
|
||||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
|
||||||
// The options to populate this field are limited to:
|
|
||||||
// A valid SPDX License Expression as defined in Appendix IV;
|
|
||||||
// NONE, if the SPDX file creator concludes there is no license available for this package; or
|
|
||||||
// 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 len(p.Licenses) == 0 {
|
|
||||||
return "NONE"
|
|
||||||
}
|
|
||||||
|
|
||||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
|
||||||
var parsedLicenses []string
|
|
||||||
for _, l := range p.Licenses {
|
|
||||||
if value, exists := spdxlicense.ID(l); exists {
|
|
||||||
parsedLicenses = append(parsedLicenses, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parsedLicenses) == 0 {
|
|
||||||
return "NOASSERTION"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parsedLicenses, " AND ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func noneIfEmpty(value string) string {
|
|
||||||
if strings.TrimSpace(value) == "" {
|
|
||||||
return "NONE"
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXDownloadLocation(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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXHomepage(p *pkg.Package) string {
|
|
||||||
switch metadata := p.Metadata.(type) {
|
|
||||||
case pkg.GemMetadata:
|
|
||||||
return metadata.Homepage
|
|
||||||
case pkg.NpmPackageJSONMetadata:
|
|
||||||
return metadata.Homepage
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXSourceInfo(p *pkg.Package) string {
|
|
||||||
answer := ""
|
|
||||||
switch p.Type {
|
|
||||||
case pkg.RpmPkg:
|
|
||||||
answer = "acquired package info from RPM DB"
|
|
||||||
case pkg.ApkPkg:
|
|
||||||
answer = "acquired package info from APK DB"
|
|
||||||
case pkg.DebPkg:
|
|
||||||
answer = "acquired package info from DPKG DB"
|
|
||||||
case pkg.NpmPkg:
|
|
||||||
answer = "acquired package info from installed node module manifest file"
|
|
||||||
case pkg.PythonPkg:
|
|
||||||
answer = "acquired package info from installed python package manifest file"
|
|
||||||
case pkg.JavaPkg, pkg.JenkinsPluginPkg:
|
|
||||||
answer = "acquired package info from installed java archive"
|
|
||||||
case pkg.GemPkg:
|
|
||||||
answer = "acquired package info from installed gem metadata file"
|
|
||||||
case pkg.GoModulePkg:
|
|
||||||
answer = "acquired package info from go module information"
|
|
||||||
case pkg.RustPkg:
|
|
||||||
answer = "acquired package info from rust cargo manifest"
|
|
||||||
default:
|
|
||||||
answer = "acquired package info from the following paths"
|
|
||||||
}
|
|
||||||
var paths []string
|
|
||||||
for _, l := range p.Locations {
|
|
||||||
paths = append(paths, l.RealPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return answer + ": " + strings.Join(paths, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXOriginator(p *pkg.Package) string {
|
|
||||||
switch metadata := p.Metadata.(type) {
|
|
||||||
case pkg.ApkMetadata:
|
|
||||||
return metadata.Maintainer
|
|
||||||
case pkg.NpmPackageJSONMetadata:
|
|
||||||
return metadata.Author
|
|
||||||
case pkg.PythonPackageMetadata:
|
|
||||||
author := metadata.Author
|
|
||||||
if author == "" {
|
|
||||||
return metadata.AuthorEmail
|
|
||||||
}
|
|
||||||
if metadata.AuthorEmail != "" {
|
|
||||||
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
|
||||||
}
|
|
||||||
return author
|
|
||||||
case pkg.GemMetadata:
|
|
||||||
if len(metadata.Authors) > 0 {
|
|
||||||
return metadata.Authors[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
case pkg.RpmdbMetadata:
|
|
||||||
return metadata.Vendor
|
|
||||||
case pkg.DpkgMetadata:
|
|
||||||
return metadata.Maintainer
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSPDXDescription(p *pkg.Package) string {
|
|
||||||
switch metadata := p.Metadata.(type) {
|
|
||||||
case pkg.ApkMetadata:
|
|
||||||
return metadata.Description
|
|
||||||
case pkg.NpmPackageJSONMetadata:
|
|
||||||
return metadata.Description
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,527 +0,0 @@
|
|||||||
package packages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/source"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_getSPDXExternalRefs(t *testing.T) {
|
|
||||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input pkg.Package
|
|
||||||
expected []spdx22.ExternalRef
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "cpe + purl",
|
|
||||||
input: pkg.Package{
|
|
||||||
CPEs: []pkg.CPE{
|
|
||||||
testCPE,
|
|
||||||
},
|
|
||||||
PURL: "a-purl",
|
|
||||||
},
|
|
||||||
expected: []spdx22.ExternalRef{
|
|
||||||
{
|
|
||||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
|
||||||
ReferenceLocator: testCPE.BindToFmtString(),
|
|
||||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
|
||||||
ReferenceLocator: "a-purl",
|
|
||||||
ReferenceType: spdx22.PurlExternalRefType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
assert.ElementsMatch(t, test.expected, getSPDXExternalRefs(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXLicense(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input pkg.Package
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no licenses",
|
|
||||||
input: pkg.Package{},
|
|
||||||
expected: "NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no SPDX licenses",
|
|
||||||
input: pkg.Package{
|
|
||||||
Licenses: []string{
|
|
||||||
"made-up",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "NOASSERTION",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with SPDX license",
|
|
||||||
input: pkg.Package{
|
|
||||||
Licenses: []string{
|
|
||||||
"MIT",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "MIT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with SPDX license expression",
|
|
||||||
input: pkg.Package{
|
|
||||||
Licenses: []string{
|
|
||||||
"MIT",
|
|
||||||
"GPL-3.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "MIT AND GPL-3.0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "cap insensitive",
|
|
||||||
input: pkg.Package{
|
|
||||||
Licenses: []string{
|
|
||||||
"gpl-3.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "GPL-3.0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "debian to spdx conversion",
|
|
||||||
input: pkg.Package{
|
|
||||||
Licenses: []string{
|
|
||||||
"GPL-2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "GPL-2.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, test.expected, getSPDXLicense(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_noneIfEmpty(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "non-zero value",
|
|
||||||
value: "something",
|
|
||||||
expected: "something",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
value: "",
|
|
||||||
expected: "NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "space",
|
|
||||||
value: " ",
|
|
||||||
expected: "NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tab",
|
|
||||||
value: "\t",
|
|
||||||
expected: "NONE",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, test.expected, noneIfEmpty(test.value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXDownloadLocation(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, getSPDXDownloadLocation(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXHomepage(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, getSPDXHomepage(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXSourceInfo(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input pkg.Package
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "locations are captured",
|
|
||||||
input: pkg.Package{
|
|
||||||
// note: no type given
|
|
||||||
Locations: []source.Location{
|
|
||||||
{
|
|
||||||
RealPath: "/a-place",
|
|
||||||
VirtualPath: "/b-place",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RealPath: "/c-place",
|
|
||||||
VirtualPath: "/d-place",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from the following paths",
|
|
||||||
"/a-place",
|
|
||||||
"/c-place",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// note: no specific support for this
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.KbPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from the following paths",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.RpmPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from RPM DB",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.ApkPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from APK DB",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.DebPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from DPKG DB",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.NpmPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from installed node module manifest file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.PythonPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from installed python package manifest file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.JavaPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from installed java archive",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.JenkinsPluginPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from installed java archive",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.GemPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from installed gem metadata file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.GoModulePkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from go module information",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: pkg.Package{
|
|
||||||
Type: pkg.RustPkg,
|
|
||||||
},
|
|
||||||
expected: []string{
|
|
||||||
"from rust cargo manifest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var pkgTypes []pkg.Type
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name+" "+string(test.input.Type), func(t *testing.T) {
|
|
||||||
if test.input.Type != "" {
|
|
||||||
pkgTypes = append(pkgTypes, test.input.Type)
|
|
||||||
}
|
|
||||||
actual := getSPDXSourceInfo(&test.input)
|
|
||||||
for _, expected := range test.expected {
|
|
||||||
assert.Contains(t, actual, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, pkg.AllPkgs, pkgTypes, "missing one or more package types to test against (maybe a package type was added?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXOriginator(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{
|
|
||||||
Authors: []string{
|
|
||||||
"auth1",
|
|
||||||
"auth2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from npm",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.NpmPackageJSONMetadata{
|
|
||||||
Author: "auth",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from apk",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.ApkMetadata{
|
|
||||||
Maintainer: "auth",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from python - just name",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.PythonPackageMetadata{
|
|
||||||
Author: "auth",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from python - just email",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.PythonPackageMetadata{
|
|
||||||
AuthorEmail: "auth@auth.gov",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth@auth.gov",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from python - both name and email",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.PythonPackageMetadata{
|
|
||||||
Author: "auth",
|
|
||||||
AuthorEmail: "auth@auth.gov",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth <auth@auth.gov>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from rpm",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.RpmdbMetadata{
|
|
||||||
Vendor: "auth",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from dpkg",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.DpkgMetadata{
|
|
||||||
Maintainer: "auth",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
|
||||||
name: "empty",
|
|
||||||
input: pkg.Package{
|
|
||||||
Metadata: pkg.NpmPackageJSONMetadata{
|
|
||||||
Author: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, test.expected, getSPDXOriginator(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSPDXDescription(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, getSPDXDescription(&test.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
package packages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
|
||||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
|
||||||
"github.com/anchore/syft/internal/version"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
"github.com/anchore/syft/syft/source"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const anchoreNamespace = "https://anchore.com/syft"
|
|
||||||
|
|
||||||
// SPDXJsonPresenter is a SPDX presentation object for the syft results (see https://github.com/spdx/spdx-spec)
|
|
||||||
type SPDXJsonPresenter struct {
|
|
||||||
catalog *pkg.Catalog
|
|
||||||
srcMetadata source.Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSPDXJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
|
||||||
func NewSPDXJSONPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *SPDXJsonPresenter {
|
|
||||||
return &SPDXJsonPresenter{
|
|
||||||
catalog: catalog,
|
|
||||||
srcMetadata: srcMetadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present the catalog results to the given writer.
|
|
||||||
func (pres *SPDXJsonPresenter) Present(output io.Writer) error {
|
|
||||||
doc := newSPDXJsonDocument(pres.catalog, pres.srcMetadata)
|
|
||||||
|
|
||||||
enc := json.NewEncoder(output)
|
|
||||||
// prevent > and < from being escaped in the payload
|
|
||||||
enc.SetEscapeHTML(false)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
return enc.Encode(&doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSPDXJsonDocument creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
|
||||||
func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document {
|
|
||||||
uniqueID := uuid.Must(uuid.NewRandom())
|
|
||||||
|
|
||||||
var name, input, identifier string
|
|
||||||
switch srcMetadata.Scheme {
|
|
||||||
case source.ImageScheme:
|
|
||||||
name = cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
|
||||||
input = "image"
|
|
||||||
case source.DirectoryScheme:
|
|
||||||
name = cleanSPDXName(srcMetadata.Path)
|
|
||||||
input = "dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
if name != "." {
|
|
||||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
|
||||||
} else {
|
|
||||||
identifier = path.Join(input, uniqueID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace := path.Join(anchoreNamespace, identifier)
|
|
||||||
packages, files, relationships := newSPDXJsonElements(catalog)
|
|
||||||
|
|
||||||
return spdx22.Document{
|
|
||||||
Element: spdx22.Element{
|
|
||||||
SPDXID: spdx22.ElementID("DOCUMENT").String(),
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
SPDXVersion: spdx22.Version,
|
|
||||||
CreationInfo: spdx22.CreationInfo{
|
|
||||||
Created: time.Now().UTC(),
|
|
||||||
Creators: []string{
|
|
||||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
|
||||||
"Organization: Anchore, Inc",
|
|
||||||
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
|
||||||
},
|
|
||||||
LicenseListVersion: spdxlicense.Version,
|
|
||||||
},
|
|
||||||
DataLicense: "CC0-1.0",
|
|
||||||
DocumentNamespace: namespace,
|
|
||||||
Packages: packages,
|
|
||||||
Files: files,
|
|
||||||
Relationships: relationships,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSPDXJsonElements(catalog *pkg.Catalog) ([]spdx22.Package, []spdx22.File, []spdx22.Relationship) {
|
|
||||||
packages := make([]spdx22.Package, 0)
|
|
||||||
relationships := make([]spdx22.Relationship, 0)
|
|
||||||
files := make([]spdx22.File, 0)
|
|
||||||
|
|
||||||
for _, p := range catalog.Sorted() {
|
|
||||||
license := getSPDXLicense(p)
|
|
||||||
packageSpdxID := spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
|
|
||||||
|
|
||||||
packageFiles, fileIDs, packageFileRelationships := getSPDXFiles(packageSpdxID, p)
|
|
||||||
files = append(files, packageFiles...)
|
|
||||||
|
|
||||||
relationships = append(relationships, packageFileRelationships...)
|
|
||||||
|
|
||||||
// note: the license concluded and declared should be the same since we are collecting license information
|
|
||||||
// from the project data itself (the installed package files).
|
|
||||||
packages = append(packages, spdx22.Package{
|
|
||||||
Description: getSPDXDescription(p),
|
|
||||||
DownloadLocation: getSPDXDownloadLocation(p),
|
|
||||||
ExternalRefs: getSPDXExternalRefs(p),
|
|
||||||
FilesAnalyzed: false,
|
|
||||||
HasFiles: fileIDs,
|
|
||||||
Homepage: getSPDXHomepage(p),
|
|
||||||
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
|
|
||||||
Originator: getSPDXOriginator(p),
|
|
||||||
SourceInfo: getSPDXSourceInfo(p),
|
|
||||||
VersionInfo: p.Version,
|
|
||||||
Item: spdx22.Item{
|
|
||||||
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
|
|
||||||
Element: spdx22.Element{
|
|
||||||
SPDXID: packageSpdxID,
|
|
||||||
Name: p.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return packages, files, relationships
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanSPDXName(name string) string {
|
|
||||||
// remove # according to specification
|
|
||||||
name = strings.ReplaceAll(name, "#", "-")
|
|
||||||
|
|
||||||
// remove : for url construction
|
|
||||||
name = strings.ReplaceAll(name, ":", "-")
|
|
||||||
|
|
||||||
// clean relative pathing
|
|
||||||
return path.Clean(name)
|
|
||||||
}
|
|
||||||
@ -5,6 +5,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
@ -114,7 +116,7 @@ func (pres *SPDXTagValuePresenter) packages() map[spdx.ElementID]*spdx.Package2_
|
|||||||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||||
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
|
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
|
||||||
// the Comments on License field (section 3.16) is preferred.
|
// the Comments on License field (section 3.16) is preferred.
|
||||||
license := getSPDXLicense(p)
|
license := spdxhelpers.License(p)
|
||||||
|
|
||||||
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
||||||
|
|
||||||
@ -274,7 +276,7 @@ func (pres *SPDXTagValuePresenter) packages() map[spdx.ElementID]*spdx.Package2_
|
|||||||
}
|
}
|
||||||
|
|
||||||
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
||||||
for _, ref := range getSPDXExternalRefs(p) {
|
for _, ref := range spdxhelpers.ExternalRefs(p) {
|
||||||
refs = append(refs, &spdx.PackageExternalReference2_2{
|
refs = append(refs, &spdx.PackageExternalReference2_2{
|
||||||
Category: string(ref.ReferenceCategory),
|
Category: string(ref.ReferenceCategory),
|
||||||
RefType: string(ref.ReferenceType),
|
RefType: string(ref.ReferenceType),
|
||||||
|
|||||||
@ -18,8 +18,6 @@ func Presenter(option format.Option, config PresenterConfig) presenter.Presenter
|
|||||||
return packages.NewTextPresenter(config.Catalog, config.SourceMetadata)
|
return packages.NewTextPresenter(config.Catalog, config.SourceMetadata)
|
||||||
case format.SPDXTagValueOption:
|
case format.SPDXTagValueOption:
|
||||||
return packages.NewSPDXTagValuePresenter(config.Catalog, config.SourceMetadata)
|
return packages.NewSPDXTagValuePresenter(config.Catalog, config.SourceMetadata)
|
||||||
case format.SPDXJSONOption:
|
|
||||||
return packages.NewSPDXJSONPresenter(config.Catalog, config.SourceMetadata)
|
|
||||||
default:
|
default:
|
||||||
// TODO: the final state is that all other cases would be replaced by formats.ByOption (wed remove this function entirely)
|
// TODO: the final state is that all other cases would be replaced by formats.ByOption (wed remove this function entirely)
|
||||||
f := formats.ByOption(option)
|
f := formats.ByOption(option)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user