mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
migrate and split common spdx format helpers
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
0c0890ef44
commit
d474281731
14
internal/formats/common/spdxhelpers/description.go
Normal file
14
internal/formats/common/spdxhelpers/description.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
|
func Description(p *pkg.Package) string {
|
||||||
|
switch metadata := p.Metadata.(type) {
|
||||||
|
case pkg.ApkMetadata:
|
||||||
|
return metadata.Description
|
||||||
|
case pkg.NpmPackageJSONMetadata:
|
||||||
|
return metadata.Description
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
56
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
22
internal/formats/common/spdxhelpers/download_location.go
Normal file
22
internal/formats/common/spdxhelpers/download_location.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
|
func DownloadLocation(p *pkg.Package) string {
|
||||||
|
// 3.7: Package Download Location
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// NONE if there is no download location whatsoever.
|
||||||
|
// NOASSERTION if:
|
||||||
|
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||||
|
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||||
|
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||||
|
|
||||||
|
switch metadata := p.Metadata.(type) {
|
||||||
|
case pkg.ApkMetadata:
|
||||||
|
return NoneIfEmpty(metadata.URL)
|
||||||
|
case pkg.NpmPackageJSONMetadata:
|
||||||
|
return NoneIfEmpty(metadata.URL)
|
||||||
|
default:
|
||||||
|
return "NOASSERTION"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_DownloadLocation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input pkg.Package
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no metadata",
|
||||||
|
input: pkg.Package{},
|
||||||
|
expected: "NOASSERTION",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from apk",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.ApkMetadata{
|
||||||
|
URL: "http://a-place.gov",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "http://a-place.gov",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from npm",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.NpmPackageJSONMetadata{
|
||||||
|
URL: "http://a-place.gov",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "http://a-place.gov",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
input: pkg.Package{
|
||||||
|
Metadata: pkg.NpmPackageJSONMetadata{
|
||||||
|
URL: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "NONE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.expected, DownloadLocation(&test.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
50
internal/formats/common/spdxhelpers/external_refs.go
Normal file
50
internal/formats/common/spdxhelpers/external_refs.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExternalRefs(p *pkg.Package) (externalRefs []model.ExternalRef) {
|
||||||
|
externalRefs = make([]model.ExternalRef, 0)
|
||||||
|
for _, c := range p.CPEs {
|
||||||
|
externalRefs = append(externalRefs, model.ExternalRef{
|
||||||
|
ReferenceCategory: model.SecurityReferenceCategory,
|
||||||
|
ReferenceLocator: c.BindToFmtString(),
|
||||||
|
ReferenceType: model.Cpe23ExternalRefType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.PURL != "" {
|
||||||
|
externalRefs = append(externalRefs, model.ExternalRef{
|
||||||
|
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||||
|
ReferenceLocator: p.PURL,
|
||||||
|
ReferenceType: model.PurlExternalRefType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return externalRefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractPURL(refs []model.ExternalRef) string {
|
||||||
|
for _, r := range refs {
|
||||||
|
if r.ReferenceType == model.PurlExternalRefType {
|
||||||
|
return r.ReferenceLocator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractCPEs(refs []model.ExternalRef) (cpes []pkg.CPE) {
|
||||||
|
for _, r := range refs {
|
||||||
|
if r.ReferenceType == model.Cpe23ExternalRefType {
|
||||||
|
cpe, err := pkg.NewCPE(r.ReferenceLocator)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.ReferenceLocator, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cpes = append(cpes, cpe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cpes
|
||||||
|
}
|
||||||
46
internal/formats/common/spdxhelpers/external_refs_test.go
Normal file
46
internal/formats/common/spdxhelpers/external_refs_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ExternalRefs(t *testing.T) {
|
||||||
|
testCPE := testutils.MustCPE(pkg.NewCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*"))
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input pkg.Package
|
||||||
|
expected []model.ExternalRef
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "cpe + purl",
|
||||||
|
input: pkg.Package{
|
||||||
|
CPEs: []pkg.CPE{
|
||||||
|
testCPE,
|
||||||
|
},
|
||||||
|
PURL: "a-purl",
|
||||||
|
},
|
||||||
|
expected: []model.ExternalRef{
|
||||||
|
{
|
||||||
|
ReferenceCategory: model.SecurityReferenceCategory,
|
||||||
|
ReferenceLocator: testCPE.BindToFmtString(),
|
||||||
|
ReferenceType: model.Cpe23ExternalRefType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||||
|
ReferenceLocator: "a-purl",
|
||||||
|
ReferenceType: model.PurlExternalRefType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.ElementsMatch(t, test.expected, ExternalRefs(&test.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
47
internal/formats/common/spdxhelpers/files.go
Normal file
47
internal/formats/common/spdxhelpers/files.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
14
internal/formats/common/spdxhelpers/homepage.go
Normal file
14
internal/formats/common/spdxhelpers/homepage.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
|
func Homepage(p *pkg.Package) string {
|
||||||
|
switch metadata := p.Metadata.(type) {
|
||||||
|
case pkg.GemMetadata:
|
||||||
|
return metadata.Homepage
|
||||||
|
case pkg.NpmPackageJSONMetadata:
|
||||||
|
return metadata.Homepage
|
||||||
|
default:
|
||||||
|
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 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
36
internal/formats/common/spdxhelpers/origintor.go
Normal file
36
internal/formats/common/spdxhelpers/origintor.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Originator(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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
39
internal/formats/common/spdxhelpers/source_info.go
Normal file
39
internal/formats/common/spdxhelpers/source_info.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SourceInfo(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, ", ")
|
||||||
|
}
|
||||||
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?)")
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user