mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
feat: Add support for new Copyrights and schema updates
- **Added**: New JSON schema version `16.0.16` with support for the new `Copyrights`. - **Modified**: Updated the `JSONSchemaVersion` parameter to use the new schema. - **Added**: New `Copyrights` field to the `Package` and `PackageBasicData` structs, similar to the existing `Licenses` field. - **Added**: New `Copyright` struct. - **Implemented**: Sorting methods for the `Copyright` struct. - **Changed**: Updated the `PackageCopyrightText` to use `helpers.GetCopyrights(p.Copyrights)`, which formats the copyright text and returns a string. Example output: "Copyright 2014-2014 Matt Zabriskie & Collaborators". - **Added**: `Copyrights` assignment to the `toSyftPackage` function. Signed-off-by: dor-hayun <dor.hayun@mend.io>
This commit is contained in:
parent
f2caf45695
commit
b75dd28a62
35 changed files with 3132 additions and 103 deletions
|
@ -9,10 +9,11 @@ import (
|
|||
)
|
||||
|
||||
func DefaultCommonOptions() []cmp.Option {
|
||||
return CommonOptions(nil, nil)
|
||||
return CommonOptions(nil, nil, nil)
|
||||
}
|
||||
|
||||
func CommonOptions(licenseCmp LicenseComparer, locationCmp LocationComparer) []cmp.Option {
|
||||
//nolint:funlen
|
||||
func CommonOptions(licenseCmp LicenseComparer, locationCmp LocationComparer, copyrightCmp CopyrightComparer) []cmp.Option {
|
||||
if licenseCmp == nil {
|
||||
licenseCmp = DefaultLicenseComparer
|
||||
}
|
||||
|
@ -21,6 +22,10 @@ func CommonOptions(licenseCmp LicenseComparer, locationCmp LocationComparer) []c
|
|||
locationCmp = DefaultLocationComparer
|
||||
}
|
||||
|
||||
if copyrightCmp == nil {
|
||||
copyrightCmp = DefaultCopyrightComparer
|
||||
}
|
||||
|
||||
return []cmp.Option{
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes
|
||||
cmpopts.SortSlices(pkg.Less),
|
||||
|
@ -61,11 +66,31 @@ func CommonOptions(licenseCmp LicenseComparer, locationCmp LocationComparer) []c
|
|||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.CopyrightsSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !copyrightCmp(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
locationCmp,
|
||||
),
|
||||
cmp.Comparer(
|
||||
licenseCmp,
|
||||
),
|
||||
cmp.Comparer(
|
||||
copyrightCmp,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
17
internal/cmptest/copyright.go
Normal file
17
internal/cmptest/copyright.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package cmptest
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type CopyrightComparer func(x, y pkg.Copyright) bool
|
||||
|
||||
func DefaultCopyrightComparer(x, y pkg.Copyright) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(
|
||||
func(x, y string) bool {
|
||||
return x == y
|
||||
},
|
||||
))
|
||||
}
|
|
@ -3,5 +3,5 @@ package internal
|
|||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.15"
|
||||
JSONSchemaVersion = "16.0.16"
|
||||
)
|
||||
|
|
|
@ -351,6 +351,7 @@ func relationshipComparer(x, y []artifact.Relationship) string {
|
|||
artifact.Relationship{},
|
||||
file.LocationSet{},
|
||||
pkg.LicenseSet{},
|
||||
pkg.CopyrightsSet{},
|
||||
), cmpopts.SortSlices(lessRelationships))
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
|
2614
schema/json/schema-16.0.16.json
Normal file
2614
schema/json/schema-16.0.16.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.15/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.16/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
|
@ -370,6 +370,28 @@
|
|||
"path"
|
||||
]
|
||||
},
|
||||
"Copyright": {
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"startYear": {
|
||||
"type": "string"
|
||||
},
|
||||
"endYear": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"author",
|
||||
"startYear",
|
||||
"endYear"
|
||||
]
|
||||
},
|
||||
"DartPubspecLockEntry": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
@ -1462,6 +1484,9 @@
|
|||
"licenses": {
|
||||
"$ref": "#/$defs/licenses"
|
||||
},
|
||||
"copyrights": {
|
||||
"$ref": "#/$defs/copyrights"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1626,6 +1651,7 @@
|
|||
"foundBy",
|
||||
"locations",
|
||||
"licenses",
|
||||
"copyrights",
|
||||
"language",
|
||||
"cpes",
|
||||
"purl"
|
||||
|
@ -2566,6 +2592,12 @@
|
|||
"pluginInstallDirectory"
|
||||
]
|
||||
},
|
||||
"copyrights": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Copyright"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"cpes": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/CPE"
|
||||
|
|
|
@ -444,8 +444,8 @@ func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBO
|
|||
// NOASSERTION, if
|
||||
// (i) the SPDX document creator has made no attempt to determine this field; or
|
||||
// (ii) the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
//
|
||||
PackageCopyrightText: noAssertion,
|
||||
// (iii) Get the formatted copyright text if available, otherwise return NOASSERTION
|
||||
PackageCopyrightText: helpers.GetCopyrights(p.Copyrights),
|
||||
|
||||
// 7.18: Package Summary Description
|
||||
// Cardinality: optional, one
|
||||
|
|
|
@ -595,6 +595,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
|||
cmpopts.IgnoreUnexported(pkg.Collection{}),
|
||||
cmpopts.IgnoreUnexported(pkg.Package{}),
|
||||
cmpopts.IgnoreUnexported(pkg.LicenseSet{}),
|
||||
cmpopts.IgnoreUnexported(pkg.CopyrightsSet{}),
|
||||
cmpopts.IgnoreFields(sbom.Artifacts{}, "FileMetadata", "FileDigests"),
|
||||
); diff != "" {
|
||||
t.Fatalf("packages do not match:\n%s", diff)
|
||||
|
|
|
@ -48,6 +48,7 @@ func EncodeComponent(p pkg.Package) cyclonedx.Component {
|
|||
Version: p.Version,
|
||||
PackageURL: p.PURL,
|
||||
Licenses: encodeLicenses(p),
|
||||
Copyright: encodeCopyrights(p),
|
||||
CPE: encodeSingleCPE(p),
|
||||
Author: encodeAuthor(p),
|
||||
Publisher: encodePublisher(p),
|
||||
|
|
|
@ -187,6 +187,7 @@ func Test_encodeCompomentType(t *testing.T) {
|
|||
Value: "go-module",
|
||||
},
|
||||
},
|
||||
Copyright: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -206,6 +207,8 @@ func Test_encodeCompomentType(t *testing.T) {
|
|||
Value: "binary",
|
||||
},
|
||||
},
|
||||
|
||||
Copyright: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ import (
|
|||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
noAssertion = "NOASSERTION"
|
||||
copyrightPrefix = "Copyright"
|
||||
)
|
||||
|
||||
// This should be a function that just surfaces licenses already validated in the package struct
|
||||
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||
spdx, other, ex := separateLicenses(p)
|
||||
|
@ -195,3 +200,31 @@ func reduceOuter(expression string) string {
|
|||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func encodeCopyrights(p pkg.Package) string {
|
||||
if p.Copyrights.Empty() {
|
||||
return ""
|
||||
}
|
||||
|
||||
var strArr []string
|
||||
|
||||
for _, c := range p.Copyrights.ToSlice() {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(copyrightPrefix)
|
||||
|
||||
// Construct the string with Start Year, End Year, and Author
|
||||
if c.StartYear != "" {
|
||||
sb.WriteString(" " + c.StartYear)
|
||||
}
|
||||
if c.EndYear != "" {
|
||||
sb.WriteString("-" + c.EndYear)
|
||||
}
|
||||
if c.Author != "" {
|
||||
sb.WriteString(" " + c.Author)
|
||||
}
|
||||
|
||||
strArr = append(strArr, sb.String())
|
||||
}
|
||||
|
||||
return strings.Join(strArr, ", ")
|
||||
}
|
||||
|
|
45
syft/format/internal/spdxutil/helpers/copyright.go
Normal file
45
syft/format/internal/spdxutil/helpers/copyright.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package helpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
noAssertion = "NOASSERTION"
|
||||
copyrightPrefix = "Copyright"
|
||||
)
|
||||
|
||||
func GetCopyrights(copyrights pkg.CopyrightsSet) string {
|
||||
result := noAssertion
|
||||
|
||||
for _, c := range copyrights.ToSlice() {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(copyrightPrefix)
|
||||
|
||||
// Start Year
|
||||
if c.StartYear != "" {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(c.StartYear)
|
||||
}
|
||||
|
||||
// End Year
|
||||
if c.EndYear != "" {
|
||||
sb.WriteString("-")
|
||||
sb.WriteString(c.EndYear)
|
||||
}
|
||||
|
||||
// Author
|
||||
if c.Author != "" {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(c.Author)
|
||||
}
|
||||
|
||||
// Assign the formatted string to result
|
||||
result = sb.String()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
},
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-files-analyzed-false-7d37ba9d2f7c574b",
|
||||
"SPDXID": "SPDXRef-Package-files-analyzed-false-0950a383541717dc",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
|
@ -27,7 +27,7 @@
|
|||
},
|
||||
{
|
||||
"name": "files-analyzed-true",
|
||||
"SPDXID": "SPDXRef-Package-files-analyzed-true-035066c2086b8bb4",
|
||||
"SPDXID": "SPDXRef-Package-files-analyzed-true-1d0a8d923f0cd238",
|
||||
"versionInfo": "v1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -77,18 +77,18 @@
|
|||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-files-analyzed-true-035066c2086b8bb4",
|
||||
"spdxElementId": "SPDXRef-Package-files-analyzed-true-1d0a8d923f0cd238",
|
||||
"relatedSpdxElement": "SPDXRef-File-some-file-2c5bc344430decac",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Unknown-",
|
||||
"relatedSpdxElement": "SPDXRef-Package-files-analyzed-false-7d37ba9d2f7c574b",
|
||||
"relatedSpdxElement": "SPDXRef-Package-files-analyzed-false-0950a383541717dc",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Unknown-",
|
||||
"relatedSpdxElement": "SPDXRef-Package-files-analyzed-true-035066c2086b8bb4",
|
||||
"relatedSpdxElement": "SPDXRef-Package-files-analyzed-true-1d0a8d923f0cd238",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-5a2b1ae000fcb51e",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-f7fdfcfa4ca6e742",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-062f404587213e8b",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -75,12 +75,12 @@
|
|||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-5a2b1ae000fcb51e",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-f7fdfcfa4ca6e742",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-062f404587213e8b",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-fe989317bb1cbb62",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -89,12 +89,12 @@
|
|||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-fe989317bb1cbb62",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-fe989317bb1cbb62",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
@ -198,43 +198,43 @@
|
|||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-c5cf7ac34cbca450",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-69910a93dc37ffb4",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-fe989317bb1cbb62",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ PackageLicenseDeclared: NOASSERTION
|
|||
##### Package: @at-sign
|
||||
|
||||
PackageName: @at-sign
|
||||
SPDXID: SPDXRef-Package--at-sign-1c8c811ea5b1cd46
|
||||
SPDXID: SPDXRef-Package--at-sign-ec109f3d122ef1db
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
@ -34,7 +34,7 @@ PackageCopyrightText: NOASSERTION
|
|||
##### Package: some/slashes
|
||||
|
||||
PackageName: some/slashes
|
||||
SPDXID: SPDXRef-Package-some-slashes-8a8e95924316c66b
|
||||
SPDXID: SPDXRef-Package-some-slashes-8a21771e3392022f
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
@ -46,7 +46,7 @@ PackageCopyrightText: NOASSERTION
|
|||
##### Package: under_scores
|
||||
|
||||
PackageName: under_scores
|
||||
SPDXID: SPDXRef-Package-under-scores-883703d950ec00f3
|
||||
SPDXID: SPDXRef-Package-under-scores-5db453bf3f332f99
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
@ -57,8 +57,8 @@ PackageCopyrightText: NOASSERTION
|
|||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-1c8c811ea5b1cd46
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-8a8e95924316c66b
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-883703d950ec00f3
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-ec109f3d122ef1db
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-8a21771e3392022f
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-5db453bf3f332f99
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-foobar-baz
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
SPDXID: SPDXRef-Package-deb-package-2-fe989317bb1cbb62
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -84,7 +84,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-c5cf7ac34cbca450
|
||||
SPDXID: SPDXRef-Package-python-package-1-69910a93dc37ffb4
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -98,13 +98,13 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
|||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-c5cf7ac34cbca450 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-c5cf7ac34cbca450
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-69910a93dc37ffb4 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-69910a93dc37ffb4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-fe989317bb1cbb62
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ PackageLicenseDeclared: NOASSERTION
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-39392bb5e270f669
|
||||
SPDXID: SPDXRef-Package-deb-package-2-062f404587213e8b
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -37,7 +37,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-5a2b1ae000fcb51e
|
||||
SPDXID: SPDXRef-Package-python-package-1-f7fdfcfa4ca6e742
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -51,7 +51,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
|||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-5a2b1ae000fcb51e
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-39392bb5e270f669
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-f7fdfcfa4ca6e742
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-062f404587213e8b
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
|||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
SPDXID: SPDXRef-Package-deb-package-2-fe989317bb1cbb62
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -40,7 +40,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-c5cf7ac34cbca450
|
||||
SPDXID: SPDXRef-Package-python-package-1-69910a93dc37ffb4
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
|
@ -54,7 +54,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
|||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-c5cf7ac34cbca450
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-69910a93dc37ffb4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-fe989317bb1cbb62
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
|
|
@ -24,16 +24,17 @@ type Package struct {
|
|||
|
||||
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageBasicData struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs cpes `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Copyrights copyrights `json:"copyrights"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs cpes `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
type cpes []CPE
|
||||
|
@ -53,6 +54,15 @@ type License struct {
|
|||
Locations []file.Location `json:"locations"`
|
||||
}
|
||||
|
||||
type copyrights []Copyright
|
||||
|
||||
type Copyright struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Author string `json:"author"`
|
||||
StartYear string `json:"startYear"`
|
||||
EndYear string `json:"endYear"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
for _, v := range licenses {
|
||||
expression, err := license.ParseExpression(v)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "5a2b1ae000fcb51e",
|
||||
"id": "f7fdfcfa4ca6e742",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -21,6 +21,7 @@
|
|||
"locations": []
|
||||
}
|
||||
],
|
||||
"copyrights": [],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
{
|
||||
|
@ -44,7 +45,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "39392bb5e270f669",
|
||||
"id": "062f404587213e8b",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
@ -56,6 +57,7 @@
|
|||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"copyrights": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "ad3ecac55fe1c30f",
|
||||
"id": "ecf423ccf313f850",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -21,6 +21,7 @@
|
|||
"locations": []
|
||||
}
|
||||
],
|
||||
"copyrights": [],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
{
|
||||
|
@ -40,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "fa4ec37eccd65756",
|
||||
"id": "b4d209e1bb8d83cb",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
@ -52,6 +53,7 @@
|
|||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"copyrights": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "c5cf7ac34cbca450",
|
||||
"id": "69910a93dc37ffb4",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -22,6 +22,7 @@
|
|||
"locations": []
|
||||
}
|
||||
],
|
||||
"copyrights": [],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
{
|
||||
|
@ -41,7 +42,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"id": "4b756c6f6fb127a3",
|
||||
"id": "fe989317bb1cbb62",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
@ -54,6 +55,7 @@
|
|||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"copyrights": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
{
|
||||
|
|
|
@ -233,6 +233,18 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
|||
return
|
||||
}
|
||||
|
||||
func toCopyrightModel(pkgCopyrights []pkg.Copyright) (modelCopyrights []model.Copyright) {
|
||||
for _, l := range pkgCopyrights {
|
||||
modelCopyrights = append(modelCopyrights, model.Copyright{
|
||||
URL: l.URL,
|
||||
Author: l.Author,
|
||||
StartYear: l.StartYear,
|
||||
EndYear: l.EndYear,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// toPackageModel crates a new Package from the given pkg.Package.
|
||||
func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
|
||||
var cpes = make([]model.CPE, len(p.CPEs))
|
||||
|
@ -251,18 +263,24 @@ func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
|
|||
licenses = toLicenseModel(p.Licenses.ToSlice())
|
||||
}
|
||||
|
||||
var copyrights = make([]model.Copyright, 0)
|
||||
if !p.Copyrights.Empty() {
|
||||
copyrights = toCopyrightModel(p.Copyrights.ToSlice())
|
||||
}
|
||||
|
||||
return model.Package{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: string(p.ID()),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Type: p.Type,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: p.Locations.ToSlice(),
|
||||
Licenses: licenses,
|
||||
Language: p.Language,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
ID: string(p.ID()),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Type: p.Type,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: p.Locations.ToSlice(),
|
||||
Licenses: licenses,
|
||||
Copyrights: copyrights,
|
||||
Language: p.Language,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
},
|
||||
PackageCustomData: model.PackageCustomData{
|
||||
MetadataType: metadataType(p.Metadata, cfg.Legacy),
|
||||
|
|
|
@ -162,6 +162,19 @@ func toSyftLicenses(m []model.License) (p []pkg.License) {
|
|||
return
|
||||
}
|
||||
|
||||
func toSyftCopyrights(m []model.Copyright) (p []pkg.Copyright) {
|
||||
for _, l := range m {
|
||||
p = append(p, pkg.Copyright{
|
||||
URL: l.URL,
|
||||
Author: l.Author,
|
||||
StartYear: l.StartYear,
|
||||
EndYear: l.EndYear,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func toSyftFileType(ty string) stereoscopeFile.Type {
|
||||
switch ty {
|
||||
case "SymbolicLink":
|
||||
|
@ -331,16 +344,17 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
|||
}
|
||||
|
||||
out := pkg.Package{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: file.NewLocationSet(p.Locations...),
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
Metadata: p.Metadata,
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: file.NewLocationSet(p.Locations...),
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Copyrights: pkg.NewCopyrightSet(toSyftCopyrights(p.Copyrights)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
Metadata: p.Metadata,
|
||||
}
|
||||
|
||||
// we don't know if this package ID is truly unique, however, we need to trust the user input in case there are
|
||||
|
|
|
@ -71,6 +71,8 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) {
|
|||
// remove known exceptions, that is, types exported in the pkg Package that are not used
|
||||
// in a metadata type but are not metadata types themselves.
|
||||
names.Remove("Licenses", "KeyValue")
|
||||
names.Remove("Copyrights", "KeyValue")
|
||||
names.Remove("CopyrightsSet", "KeyValue")
|
||||
|
||||
strNames := names.List()
|
||||
sort.Strings(strNames)
|
||||
|
|
|
@ -157,7 +157,7 @@ func Test_newELFPackage(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := newELFPackage(test.metadata, file.NewLocationSet())
|
||||
if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})); diff != "" {
|
||||
if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{}, pkg.CopyrightsSet{})); diff != "" {
|
||||
t.Errorf("newELFPackage() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -42,6 +42,7 @@ type CatalogTester struct {
|
|||
compareOptions []cmp.Option
|
||||
locationComparer cmptest.LocationComparer
|
||||
licenseComparer cmptest.LicenseComparer
|
||||
copyrightComparer cmptest.CopyrightComparer
|
||||
packageStringer func(pkg.Package) string
|
||||
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
|
||||
}
|
||||
|
@ -267,7 +268,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
|||
func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
t.Helper()
|
||||
|
||||
p.compareOptions = append(p.compareOptions, cmptest.CommonOptions(p.licenseComparer, p.locationComparer)...)
|
||||
p.compareOptions = append(p.compareOptions, cmptest.CommonOptions(p.licenseComparer, p.locationComparer, p.copyrightComparer)...)
|
||||
|
||||
{
|
||||
r := cmptest.NewDiffReporter()
|
||||
|
@ -320,6 +321,7 @@ func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Pars
|
|||
NewCatalogTester().FromFile(t, fixturePath).WithEnv(env).Expects(expectedPkgs, expectedRelationships).TestParser(t, parser)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
||||
t.Helper()
|
||||
opts := []cmp.Option{
|
||||
|
@ -360,12 +362,33 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
|||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.CopyrightsSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !cmptest.DefaultCopyrightComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
cmptest.DefaultLocationComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
cmptest.DefaultLicenseComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
cmptest.DefaultCopyrightComparer,
|
||||
),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(a, b, opts...); diff != "" {
|
||||
|
|
70
syft/pkg/copyright.go
Normal file
70
syft/pkg/copyright.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type Copyright struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Author string `json:"author"`
|
||||
StartYear string `json:"startYear"`
|
||||
EndYear string `json:"endYear"`
|
||||
}
|
||||
|
||||
type Copyrights []Copyright
|
||||
|
||||
func (c Copyrights) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c Copyrights) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
func (c Copyrights) Less(i, j int) bool {
|
||||
return c[i].Author < c[j].Author
|
||||
}
|
||||
|
||||
// Merge attempts to merge two Copyright instances. It merges URLs if the Author,
|
||||
// StartYear, and EndYear are the same or compatible.
|
||||
func (s Copyright) Merge(c Copyright) (*Copyright, error) {
|
||||
// Check if the Author is the same
|
||||
if s.Author != c.Author {
|
||||
return nil, fmt.Errorf("cannot merge copyrights with different authors: %s vs %s", s.Author, c.Author)
|
||||
}
|
||||
|
||||
// Check if the StartYear and EndYear are compatible
|
||||
if s.StartYear != c.StartYear || s.EndYear != c.EndYear {
|
||||
return nil, fmt.Errorf("cannot merge copyrights with different years: %s-%s vs %s-%s", s.StartYear, s.EndYear, c.StartYear, c.EndYear)
|
||||
}
|
||||
|
||||
// Merge URLs
|
||||
if c.URL != "" {
|
||||
s.URL = mergeURLs(s.URL, c.URL)
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// mergeURLs merges two URL strings, deduplicates, and sorts them.
|
||||
func mergeURLs(sURL, cURL string) string {
|
||||
var urls []string
|
||||
if sURL != "" {
|
||||
urls = append(urls, sURL)
|
||||
}
|
||||
if cURL != "" {
|
||||
urls = append(urls, cURL)
|
||||
}
|
||||
|
||||
if len(urls) > 0 {
|
||||
// Deduplicate and sort URLs
|
||||
urlsSet := strset.New(urls...)
|
||||
sortedURLs := urlsSet.List()
|
||||
sort.Strings(sortedURLs)
|
||||
return sortedURLs[0] // Assuming we return the first one or join them into a single string
|
||||
}
|
||||
return ""
|
||||
}
|
89
syft/pkg/copyright_set.go
Normal file
89
syft/pkg/copyright_set.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
//nolint:dupl
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
|
||||
type CopyrightsSet struct {
|
||||
set map[artifact.ID]Copyright
|
||||
}
|
||||
|
||||
func NewCopyrightSet(copyrights ...Copyright) (c CopyrightsSet) {
|
||||
for _, l := range copyrights {
|
||||
c.Add(l)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CopyrightsSet) addToExisting(copyright Copyright) (id artifact.ID, merged bool, err error) {
|
||||
id, err = artifact.IDByHash(copyright)
|
||||
if err != nil {
|
||||
return id, false, fmt.Errorf("could not get the hash for a copyright: %w", err)
|
||||
}
|
||||
|
||||
v, ok := c.set[id]
|
||||
if !ok {
|
||||
// doesn't exist safe to add
|
||||
return id, false, nil
|
||||
}
|
||||
|
||||
// we got the same id; we want to merge the URLs and Location data
|
||||
// URLs/Location are not considered when taking the Hash
|
||||
m, err := v.Merge(copyright)
|
||||
if err != nil {
|
||||
return id, false, fmt.Errorf("could not merge license into map: %w", err)
|
||||
}
|
||||
c.set[id] = *m
|
||||
|
||||
return id, true, nil
|
||||
}
|
||||
|
||||
func (c *CopyrightsSet) Add(copyrights ...Copyright) {
|
||||
if c.set == nil {
|
||||
c.set = make(map[artifact.ID]Copyright)
|
||||
}
|
||||
for _, l := range copyrights {
|
||||
// we only want to add copyrights that have a value
|
||||
// note, this check should be moved to the license constructor in the future
|
||||
if l.Author != "" {
|
||||
if id, merged, err := c.addToExisting(l); err == nil && !merged {
|
||||
// doesn't exist, add it
|
||||
c.set[id] = l
|
||||
} else if err != nil {
|
||||
log.Trace("copyright set failed to add copyright %#v: %+v", l, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c CopyrightsSet) ToSlice() []Copyright {
|
||||
if c.set == nil {
|
||||
return nil
|
||||
}
|
||||
var copyrights []Copyright
|
||||
for _, v := range c.set {
|
||||
copyrights = append(copyrights, v)
|
||||
}
|
||||
sort.Sort(Copyrights(copyrights))
|
||||
return copyrights
|
||||
}
|
||||
|
||||
func (c CopyrightsSet) Hash() (uint64, error) {
|
||||
// access paths and filesystem IDs are not considered when hashing a copyright set, only the real paths
|
||||
return hashstructure.Hash(c.ToSlice(), hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||
ZeroNil: true,
|
||||
SlicesAsSets: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (c CopyrightsSet) Empty() bool {
|
||||
return len(c.set) < 1
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
//nolint:dupl
|
||||
package pkg
|
||||
|
||||
import (
|
||||
|
|
|
@ -17,17 +17,19 @@ import (
|
|||
// Package represents an application or library that has been bundled into a distributable format.
|
||||
// TODO: if we ignore FoundBy for ID generation should we merge the field to show it was found in two places?
|
||||
type Package struct {
|
||||
id artifact.ID `hash:"ignore"`
|
||||
Name string // the package name
|
||||
Version string // the version of the package
|
||||
FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package
|
||||
Locations file.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package)
|
||||
Licenses LicenseSet // licenses discovered with the package metadata
|
||||
Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc)
|
||||
Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc)
|
||||
CPEs []cpe.CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields)
|
||||
PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec)
|
||||
Metadata interface{} // additional data found while parsing the package source
|
||||
id artifact.ID `hash:"ignore"`
|
||||
Name string // the package name
|
||||
Version string // the version of the package
|
||||
FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package
|
||||
Locations file.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package)
|
||||
Licenses LicenseSet // licenses discovered with the package metadata
|
||||
Copyrights CopyrightsSet // copyrights discovered with the package metadata
|
||||
|
||||
Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc)
|
||||
Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc)
|
||||
CPEs []cpe.CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields)
|
||||
PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec)
|
||||
Metadata interface{} // additional data found while parsing the package source
|
||||
}
|
||||
|
||||
func (p *Package) OverrideID(id artifact.ID) {
|
||||
|
|
|
@ -416,6 +416,24 @@ func TestPackage_Merge(t *testing.T) {
|
|||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y CopyrightsSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !copyrightComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(locationComparer),
|
||||
); diff != "" {
|
||||
t.Errorf("unexpected result from parsing (-expected +actual)\n%s", diff)
|
||||
|
@ -428,6 +446,10 @@ func licenseComparer(x, y License) bool {
|
|||
return cmp.Equal(x, y, cmp.Comparer(locationComparer))
|
||||
}
|
||||
|
||||
func copyrightComparer(x, y Copyright) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(copyrightComparer))
|
||||
}
|
||||
|
||||
func locationComparer(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.AccessPath, y.AccessPath)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue