From 052f526952d6b7d89f395514fabce7ba129590d9 Mon Sep 17 00:00:00 2001 From: dor-hayun Date: Thu, 22 Aug 2024 16:40:50 +0300 Subject: [PATCH] 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 --- internal/cmptest/common_options.go | 29 +- internal/cmptest/copyright.go | 16 + internal/constants.go | 2 +- .../binary/binary_dependencies_test.go | 1 + schema/json/schema-16.0.16.json | 2614 +++++++++++++++++ schema/json/schema-latest.json | 34 +- .../common/spdxhelpers/to_format_model.go | 4 +- .../common/spdxhelpers/to_syft_model_test.go | 1 + .../TestCycloneDxDirectoryEncoder.golden | 2 + .../snapshot/TestCycloneDxImageEncoder.golden | 2 + .../TestCycloneDxDirectoryEncoder.golden | 2 + .../snapshot/TestCycloneDxImageEncoder.golden | 2 + .../cyclonedxutil/helpers/component.go | 1 + .../cyclonedxutil/helpers/component_test.go | 3 + .../cyclonedxutil/helpers/licenses.go | 34 +- .../internal/spdxutil/helpers/copyright.go | 45 + .../TestSPDX22JSONRequredProperties.golden | 10 +- .../TestSPDXJSONDirectoryEncoder.golden | 8 +- .../snapshot/TestSPDXJSONImageEncoder.golden | 8 +- .../snapshot/TestSPDXRelationshipOrder.golden | 20 +- .../snapshot/TestSPDXJSONSPDXIDs.golden | 12 +- .../snapshot/TestSPDXRelationshipOrder.golden | 20 +- .../TestSPDXTagValueDirectoryEncoder.golden | 8 +- .../TestSPDXTagValueImageEncoder.golden | 8 +- syft/format/syftjson/model/package.go | 30 +- .../snapshot/TestDirectoryEncoder.golden | 6 +- .../TestEncodeFullJSONDocument.golden | 6 +- .../snapshot/TestImageEncoder.golden | 6 +- syft/format/syftjson/to_format_model.go | 38 +- syft/format/syftjson/to_syft_model.go | 34 +- .../packagemetadata/discover_type_names.go | 2 + syft/pkg/cataloger/binary/elf_package_test.go | 2 +- .../internal/pkgtest/test_generic_parser.go | 25 +- syft/pkg/copyright.go | 70 + syft/pkg/copyright_set.go | 88 + syft/pkg/license_set.go | 4 +- syft/pkg/package.go | 24 +- syft/pkg/package_test.go | 22 + 38 files changed, 3138 insertions(+), 105 deletions(-) create mode 100644 internal/cmptest/copyright.go create mode 100644 schema/json/schema-16.0.16.json create mode 100644 syft/format/internal/spdxutil/helpers/copyright.go create mode 100644 syft/pkg/copyright.go create mode 100644 syft/pkg/copyright_set.go diff --git a/internal/cmptest/common_options.go b/internal/cmptest/common_options.go index ecfb54edfded..ce23f71839b8 100644 --- a/internal/cmptest/common_options.go +++ b/internal/cmptest/common_options.go @@ -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, + ), } } diff --git a/internal/cmptest/copyright.go b/internal/cmptest/copyright.go new file mode 100644 index 000000000000..789db8c2afca --- /dev/null +++ b/internal/cmptest/copyright.go @@ -0,0 +1,16 @@ +package cmptest + +import ( + "github.com/anchore/syft/syft/pkg" + "github.com/google/go-cmp/cmp" +) + +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 + }, + )) +} diff --git a/internal/constants.go b/internal/constants.go index 6d1dd197439c..93b0093ab6a2 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -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" ) diff --git a/internal/relationship/binary/binary_dependencies_test.go b/internal/relationship/binary/binary_dependencies_test.go index ea524fa1f7cf..602139e9ad56 100644 --- a/internal/relationship/binary/binary_dependencies_test.go +++ b/internal/relationship/binary/binary_dependencies_test.go @@ -351,6 +351,7 @@ func relationshipComparer(x, y []artifact.Relationship) string { artifact.Relationship{}, file.LocationSet{}, pkg.LicenseSet{}, + pkg.CopyrightsSet{}, ), cmpopts.SortSlices(lessRelationships)) } diff --git a/schema/json/schema-16.0.16.json b/schema/json/schema-16.0.16.json new file mode 100644 index 000000000000..537f41e98ff7 --- /dev/null +++ b/schema/json/schema-16.0.16.json @@ -0,0 +1,2614 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.16/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string" + }, + "packageID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "recipeRevision": { + "type": "string" + }, + "packageRevision": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CPE": { + "properties": { + "cpe": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "cpe" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "Copyright": { + "properties": { + "url": { + "type": "string" + }, + "author": { + "type": "string" + }, + "startYear": { + "type": "string" + }, + "endYear": { + "type": "string" + } + }, + "type": "object", + "required": [ + "author", + "startYear", + "endYear" + ] + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean" + }, + "stackCanary": { + "type": "boolean" + }, + "nx": { + "type": "boolean" + }, + "relRO": { + "type": "string" + }, + "pie": { + "type": "boolean" + }, + "dso": { + "type": "boolean" + }, + "safeStack": { + "type": "boolean" + }, + "cfi": { + "type": "boolean" + }, + "fortify": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ] + }, + "ElfBinaryPackageNoteJsonPayload": { + "properties": { + "type": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "osCPE": { + "type": "string" + }, + "os": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "system": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object" + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Executable": { + "properties": { + "format": { + "type": "string" + }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures" + } + }, + "type": "object", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + }, + "executable": { + "$ref": "#/$defs/Executable" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + }, + "goExperiments": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "LuarocksPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "homepage", + "description", + "url", + "dependencies" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixStoreEntry": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "copyrights": { + "$ref": "#/$defs/copyrights" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/LuarocksPackage" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/SwiplpackPackage" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "copyrights", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + }, + "requiresPython": { + "type": "string" + }, + "requiresDist": { + "items": { + "type": "string" + }, + "type": "array" + }, + "providesExtra": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonPoetryLockDependencyEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "markers": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "optional" + ] + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonPoetryLockDependencyEntry" + }, + "type": "array" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPoetryLockExtraEntry" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ] + }, + "PythonPoetryLockExtraEntry": { + "properties": { + "name": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "SwiplpackPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "packager": { + "type": "string" + }, + "packagerEmail": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "packager", + "packagerEmail", + "homepage", + "dependencies" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "copyrights": { + "items": { + "$ref": "#/$defs/Copyright" + }, + "type": "array" + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index 1bab78aa6bae..537f41e98ff7 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -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" diff --git a/syft/format/common/spdxhelpers/to_format_model.go b/syft/format/common/spdxhelpers/to_format_model.go index 3fd136e84df7..6e03d3fad544 100644 --- a/syft/format/common/spdxhelpers/to_format_model.go +++ b/syft/format/common/spdxhelpers/to_format_model.go @@ -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 diff --git a/syft/format/common/spdxhelpers/to_syft_model_test.go b/syft/format/common/spdxhelpers/to_syft_model_test.go index e8526bab5885..897fe2abba15 100644 --- a/syft/format/common/spdxhelpers/to_syft_model_test.go +++ b/syft/format/common/spdxhelpers/to_syft_model_test.go @@ -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) diff --git a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index e4e7bd7521b7..8311adb2857a 100644 --- a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -35,6 +35,7 @@ } } ], + "copyright":"NOASSERTION", "cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", "purl": "a-purl-2", "properties": [ @@ -62,6 +63,7 @@ }, { "bom-ref":"redacted", + "copyright":"NOASSERTION", "type": "library", "name": "package-2", "version": "2.0.1", diff --git a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 62750e9e68a5..3f1d5f844a89 100644 --- a/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/syft/format/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -36,6 +36,7 @@ } } ], + "copyright":"NOASSERTION", "cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", "purl": "a-purl-1", "properties": [ @@ -67,6 +68,7 @@ }, { "bom-ref":"redacted", + "copyright":"NOASSERTION", "type": "library", "name": "package-2", "version": "2.0.1", diff --git a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index 9a9f7bce8a65..1752c19a14a9 100644 --- a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -24,6 +24,7 @@ MIT + NOASSERTION cpe:2.3:*:some:package:2:*:*:*:*:*:*:* a-purl-2 @@ -37,6 +38,7 @@ package-2 2.0.1 + NOASSERTION cpe:2.3:*:some:package:2:*:*:*:*:*:*:* pkg:deb/debian/package-2@2.0.1 diff --git a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 12c99a5fa5d3..793fc2c885e2 100644 --- a/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/syft/format/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -25,6 +25,7 @@ MIT + NOASSERTION cpe:2.3:*:some:package:1:*:*:*:*:*:*:* a-purl-1 @@ -39,6 +40,7 @@ package-2 2.0.1 + NOASSERTION cpe:2.3:*:some:package:2:*:*:*:*:*:*:* pkg:deb/debian/package-2@2.0.1 diff --git a/syft/format/internal/cyclonedxutil/helpers/component.go b/syft/format/internal/cyclonedxutil/helpers/component.go index 526094ac5c30..d64a91cfa4ad 100644 --- a/syft/format/internal/cyclonedxutil/helpers/component.go +++ b/syft/format/internal/cyclonedxutil/helpers/component.go @@ -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), diff --git a/syft/format/internal/cyclonedxutil/helpers/component_test.go b/syft/format/internal/cyclonedxutil/helpers/component_test.go index 7f2b9d245250..7de350d4160b 100644 --- a/syft/format/internal/cyclonedxutil/helpers/component_test.go +++ b/syft/format/internal/cyclonedxutil/helpers/component_test.go @@ -187,6 +187,7 @@ func Test_encodeCompomentType(t *testing.T) { Value: "go-module", }, }, + Copyright: noAssertion, }, }, { @@ -206,6 +207,8 @@ func Test_encodeCompomentType(t *testing.T) { Value: "binary", }, }, + + Copyright: noAssertion, }, }, } diff --git a/syft/format/internal/cyclonedxutil/helpers/licenses.go b/syft/format/internal/cyclonedxutil/helpers/licenses.go index a092d3abdf3b..3ac7ba0135fc 100644 --- a/syft/format/internal/cyclonedxutil/helpers/licenses.go +++ b/syft/format/internal/cyclonedxutil/helpers/licenses.go @@ -5,11 +5,15 @@ import ( "strings" "github.com/CycloneDX/cyclonedx-go" - "github.com/anchore/syft/internal/spdxlicense" "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) @@ -198,3 +202,31 @@ func reduceOuter(expression string) string { return sb.String() } + +func encodeCopyrights(p pkg.Package) string { + if p.Copyrights.Empty() { + return noAssertion + } + + 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, ", ") +} diff --git a/syft/format/internal/spdxutil/helpers/copyright.go b/syft/format/internal/spdxutil/helpers/copyright.go new file mode 100644 index 000000000000..f269e4218537 --- /dev/null +++ b/syft/format/internal/spdxutil/helpers/copyright.go @@ -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 +} diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden index 118247b1dc09..0ca78b8b9da7 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDX22JSONRequredProperties.golden @@ -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" }, { diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden index 35433f6f547b..6d0237dc5e0e 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden @@ -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" }, { diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden index 737aed46893a..857ab51628c3 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden @@ -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" }, { diff --git a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 54533ae4ccfd..ebf1ff55bad3 100644 --- a/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/format/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -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" }, { diff --git a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden index dd946aa235b1..4a6cd08c23e1 100644 --- a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden +++ b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden @@ -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 diff --git a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 75cab71eb796..168c12624741 100644 --- a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -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 diff --git a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden index bccd8acc0f2d..9bbc6473d27b 100644 --- a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden +++ b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden @@ -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 diff --git a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden index c93fb63298fe..6eff49f246f3 100644 --- a/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden +++ b/syft/format/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden @@ -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 diff --git a/syft/format/syftjson/model/package.go b/syft/format/syftjson/model/package.go index 503709d1f16b..ca252b00a1bc 100644 --- a/syft/format/syftjson/model/package.go +++ b/syft/format/syftjson/model/package.go @@ -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) diff --git a/syft/format/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/syft/format/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index 0ae05a3aa5fa..a778afff863b 100644 --- a/syft/format/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden +++ b/syft/format/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden @@ -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": [ { diff --git a/syft/format/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/syft/format/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 6fc9041e9f13..ca774d826b9c 100644 --- a/syft/format/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/syft/format/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -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": [ { diff --git a/syft/format/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/syft/format/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index f013f2026a05..517eebab2089 100644 --- a/syft/format/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/syft/format/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -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": [ { diff --git a/syft/format/syftjson/to_format_model.go b/syft/format/syftjson/to_format_model.go index 42ec48f77d2c..7b3ef8247d7b 100644 --- a/syft/format/syftjson/to_format_model.go +++ b/syft/format/syftjson/to_format_model.go @@ -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), diff --git a/syft/format/syftjson/to_syft_model.go b/syft/format/syftjson/to_syft_model.go index 289e91fda527..241a4a159792 100644 --- a/syft/format/syftjson/to_syft_model.go +++ b/syft/format/syftjson/to_syft_model.go @@ -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 diff --git a/syft/internal/packagemetadata/discover_type_names.go b/syft/internal/packagemetadata/discover_type_names.go index 03f8a4cc62b7..ed8c4701c89a 100644 --- a/syft/internal/packagemetadata/discover_type_names.go +++ b/syft/internal/packagemetadata/discover_type_names.go @@ -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) diff --git a/syft/pkg/cataloger/binary/elf_package_test.go b/syft/pkg/cataloger/binary/elf_package_test.go index 85fa422211f2..93e16343b1ba 100644 --- a/syft/pkg/cataloger/binary/elf_package_test.go +++ b/syft/pkg/cataloger/binary/elf_package_test.go @@ -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) } }) diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index ae26bb6d13b0..ce743bec5d63 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -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 != "" { diff --git a/syft/pkg/copyright.go b/syft/pkg/copyright.go new file mode 100644 index 000000000000..a81fa617a0a7 --- /dev/null +++ b/syft/pkg/copyright.go @@ -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 "" +} diff --git a/syft/pkg/copyright_set.go b/syft/pkg/copyright_set.go new file mode 100644 index 000000000000..8c4c4ab69ffe --- /dev/null +++ b/syft/pkg/copyright_set.go @@ -0,0 +1,88 @@ +//nolint:dupl +package pkg + +import ( + "fmt" + "sort" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/artifact" + "github.com/mitchellh/hashstructure/v2" +) + +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 +} diff --git a/syft/pkg/license_set.go b/syft/pkg/license_set.go index 99593fae2a25..fdf104f4da88 100644 --- a/syft/pkg/license_set.go +++ b/syft/pkg/license_set.go @@ -1,13 +1,13 @@ +//nolint:dupl package pkg import ( "fmt" "sort" - "github.com/mitchellh/hashstructure/v2" - "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/mitchellh/hashstructure/v2" ) type LicenseSet struct { diff --git a/syft/pkg/package.go b/syft/pkg/package.go index 8ee8d969fae7..8b52cfc93b66 100644 --- a/syft/pkg/package.go +++ b/syft/pkg/package.go @@ -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) { diff --git a/syft/pkg/package_test.go b/syft/pkg/package_test.go index 94896d652ed4..c3e5397ea248 100644 --- a/syft/pkg/package_test.go +++ b/syft/pkg/package_test.go @@ -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) }