From e3e69596bd808ea705b2ad163ea534ca0a4c4dc9 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Mon, 12 May 2025 21:03:51 -0400 Subject: [PATCH] Translate Portage license strings to SPDX expressions (#1763) * fix portage license handling Signed-off-by: Alex Goodman Signed-off-by: Alex Goodman * cover license_group file Signed-off-by: Alex Goodman * add licenses to portage metadata in json schema Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman Signed-off-by: Alex Goodman --- internal/constants.go | 2 +- schema/json/schema-16.0.29.json | 2993 +++++++++++++++++ schema/json/schema-latest.json | 5 +- syft/pkg/cataloger/gentoo/cataloger_test.go | 176 +- syft/pkg/cataloger/gentoo/license.go | 248 ++ syft/pkg/cataloger/gentoo/license_test.go | 183 + .../gentoo/parse_portage_contents.go | 91 +- .../etc/portage/license_groups | 8 + .../pkg/app-containers/skopeo-1.5.1/CONTENTS | 13 + .../pkg/app-containers/skopeo-1.5.1/LICENSE | 1 + .../db/pkg/app-containers/skopeo-1.5.1/SIZE | 1 + .../gentoo/test-fixtures/license-groups/cycle | 2 + .../test-fixtures/license-groups/example1 | 5 + .../test-fixtures/license-groups/missing | 3 + .../gentoo/test-fixtures/license-groups/self | 2 + syft/pkg/portage.go | 3 +- 16 files changed, 3630 insertions(+), 106 deletions(-) create mode 100644 schema/json/schema-16.0.29.json create mode 100644 syft/pkg/cataloger/gentoo/license.go create mode 100644 syft/pkg/cataloger/gentoo/license_test.go create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/etc/portage/license_groups create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/LICENSE create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/SIZE create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/license-groups/cycle create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/license-groups/example1 create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/license-groups/missing create mode 100644 syft/pkg/cataloger/gentoo/test-fixtures/license-groups/self diff --git a/internal/constants.go b/internal/constants.go index 354a5085b..f494cd277 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.28" + JSONSchemaVersion = "16.0.29" ) diff --git a/schema/json/schema-16.0.29.json b/schema/json/schema-16.0.29.json new file mode 100644 index 000000000..ac872a8c7 --- /dev/null +++ b/schema/json/schema-16.0.29.json @@ -0,0 +1,2993 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.29/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" + ] + }, + "BitnamiSbomEntry": { + "properties": { + "name": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "distro": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "arch", + "distro", + "revision", + "version", + "path", + "files" + ] + }, + "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" + ] + }, + "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" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPackagesLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "contentHash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "contentHash", + "type" + ] + }, + "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" + ] + }, + "DpkgArchiveEntry": { + "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" + ] + }, + "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" + }, + "unknowns": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "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" + ] + }, + "GithubActionsUseStatement": { + "properties": { + "value": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value" + ] + }, + "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" + ] + }, + "JavaJvmInstallation": { + "properties": { + "release": { + "$ref": "#/$defs/JavaVMRelease" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "release", + "files" + ] + }, + "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" + ] + }, + "JavaVMRelease": { + "properties": { + "implementor": { + "type": "string" + }, + "implementorVersion": { + "type": "string" + }, + "javaRuntimeVersion": { + "type": "string" + }, + "javaVersion": { + "type": "string" + }, + "javaVersionDate": { + "type": "string" + }, + "libc": { + "type": "string" + }, + "modules": { + "items": { + "type": "string" + }, + "type": "array" + }, + "osArch": { + "type": "string" + }, + "osName": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "source": { + "type": "string" + }, + "buildSource": { + "type": "string" + }, + "buildSourceRepo": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "fullVersion": { + "type": "string" + }, + "semanticVersion": { + "type": "string" + }, + "buildInfo": { + "type": "string" + }, + "jvmVariant": { + "type": "string" + }, + "jvmVersion": { + "type": "string" + }, + "imageType": { + "type": "string" + }, + "buildType": { + "type": "string" + } + }, + "type": "object" + }, + "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" + }, + "fullText": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "contents": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value", + "fullText", + "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" + ] + }, + "NixDerivation": { + "properties": { + "path": { + "type": "string" + }, + "system": { + "type": "string" + }, + "inputDerivations": { + "items": { + "$ref": "#/$defs/NixDerivationReference" + }, + "type": "array" + }, + "inputSources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixDerivationReference": { + "properties": { + "path": { + "type": "string" + }, + "outputs": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixStoreEntry": { + "properties": { + "path": { + "type": "string" + }, + "output": { + "type": "string" + }, + "outputHash": { + "type": "string" + }, + "derivation": { + "$ref": "#/$defs/NixDerivation" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash" + ] + }, + "OpamPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "url": { + "type": "string" + }, + "checksum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "licenses", + "url", + "checksum", + "homepage", + "dependencies" + ] + }, + "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" + }, + "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/BitnamiSbomEntry" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPackagesLockEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgArchiveEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GithubActionsUseStatement" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavaJvmInstallation" + }, + { + "$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/OpamPackage" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPearEntry" + }, + { + "$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/TerraformLockProviderEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "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" + ] + }, + "PhpPearEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "licenses": { + "type": "string" + }, + "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" + ] + }, + "TerraformLockProviderEntry": { + "properties": { + "url": { + "type": "string" + }, + "constraints": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "url", + "constraints", + "version", + "hashes" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "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 f33d8196e..ac872a8c7 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.28/document", + "$id": "anchore.io/schema/syft/json/16.0.29/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -2266,6 +2266,9 @@ "installedSize": { "type": "integer" }, + "licenses": { + "type": "string" + }, "files": { "items": { "$ref": "#/$defs/PortageFileRecord" diff --git a/syft/pkg/cataloger/gentoo/cataloger_test.go b/syft/pkg/cataloger/gentoo/cataloger_test.go index 3e01b3929..79a4387be 100644 --- a/syft/pkg/cataloger/gentoo/cataloger_test.go +++ b/syft/pkg/cataloger/gentoo/cataloger_test.go @@ -10,64 +10,140 @@ import ( ) func TestPortageCataloger(t *testing.T) { - expectedLicenseLocation := file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE") - expectedPkgs := []pkg.Package{ + tests := []struct { + name string + fixture string + expectedPackages []pkg.Package + expectedRelationships []artifact.Relationship + }{ { - Name: "app-containers/skopeo", - Version: "1.5.1", - FoundBy: "portage-cataloger", - PURL: "pkg:ebuild/app-containers%2Fskopeo@1.5.1", - Locations: file.NewLocationSet( - file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"), - file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"), - expectedLicenseLocation, - ), - Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(expectedLicenseLocation, "Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT")...), - Type: pkg.PortagePkg, - Metadata: pkg.PortageEntry{ - InstalledSize: 27937835, - Files: []pkg.PortageFileRecord{ - { - Path: "/usr/bin/skopeo", - Digest: &file.Digest{ - Algorithm: "md5", - Value: "376c02bd3b22804df8fdfdc895e7dbfb", - }, - }, - { - Path: "/etc/containers/policy.json", - Digest: &file.Digest{ - Algorithm: "md5", - Value: "c01eb6950f03419e09d4fc88cb42ff6f", - }, - }, - { - Path: "/etc/containers/registries.d/default.yaml", - Digest: &file.Digest{ - Algorithm: "md5", - Value: "e6e66cd3c24623e0667f26542e0e08f6", - }, - }, - { - Path: "/var/lib/atomic/sigstore/.keep_app-containers_skopeo-0", - Digest: &file.Digest{ - Algorithm: "md5", - Value: "d41d8cd98f00b204e9800998ecf8427e", + name: "standard skopeo package", + fixture: "test-fixtures/layout", + expectedPackages: []pkg.Package{ + { + Name: "app-containers/skopeo", + Version: "1.5.1", + FoundBy: "portage-cataloger", + PURL: "pkg:ebuild/app-containers%2Fskopeo@1.5.1", + Locations: file.NewLocationSet( + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"), + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"), + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE"), + ), + Licenses: pkg.NewLicenseSet( + pkg.NewLicensesFromLocation( + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE"), + "Apache-2.0 AND BSD AND BSD-2 AND CC-BY-SA-4.0 AND ISC AND MIT")..., + ), + Type: pkg.PortagePkg, + Metadata: pkg.PortageEntry{ + InstalledSize: 27937835, + Licenses: "Apache-2.0 BSD BSD-2 CC-BY-SA-4.0 ISC MIT", + Files: []pkg.PortageFileRecord{ + { + Path: "/usr/bin/skopeo", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "376c02bd3b22804df8fdfdc895e7dbfb", + }, + }, + { + Path: "/etc/containers/policy.json", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "c01eb6950f03419e09d4fc88cb42ff6f", + }, + }, + { + Path: "/etc/containers/registries.d/default.yaml", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "e6e66cd3c24623e0667f26542e0e08f6", + }, + }, + { + Path: "/var/lib/atomic/sigstore/.keep_app-containers_skopeo-0", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "d41d8cd98f00b204e9800998ecf8427e", + }, + }, }, }, }, }, + // not supported at this time + expectedRelationships: nil, + }, + { + name: "standard skopeo package with license groups", + fixture: "test-fixtures/layout-license-groups", + expectedPackages: []pkg.Package{ + { + Name: "app-containers/skopeo", + Version: "1.5.1", + FoundBy: "portage-cataloger", + PURL: "pkg:ebuild/app-containers%2Fskopeo@1.5.1", + Locations: file.NewLocationSet( + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"), + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"), + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE"), + ), + Licenses: pkg.NewLicenseSet( + pkg.NewLicensesFromLocation( + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE"), + "Apache-2.0 AND BSD AND BSD-2 AND CC-BY-SA-4.0 AND ISC AND MIT")..., + ), + Type: pkg.PortagePkg, + Metadata: pkg.PortageEntry{ + InstalledSize: 27937835, + Licenses: "@GROUP1 @MIT-LIKE", + Files: []pkg.PortageFileRecord{ + { + Path: "/usr/bin/skopeo", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "376c02bd3b22804df8fdfdc895e7dbfb", + }, + }, + { + Path: "/etc/containers/policy.json", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "c01eb6950f03419e09d4fc88cb42ff6f", + }, + }, + { + Path: "/etc/containers/registries.d/default.yaml", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "e6e66cd3c24623e0667f26542e0e08f6", + }, + }, + { + Path: "/var/lib/atomic/sigstore/.keep_app-containers_skopeo-0", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "d41d8cd98f00b204e9800998ecf8427e", + }, + }, + }, + }, + }, + }, + // not supported at this time + expectedRelationships: nil, }, } - // TODO: relationships are not under test yet - var expectedRelationships []artifact.Relationship - - pkgtest.NewCatalogTester(). - FromDirectory(t, "test-fixtures/layout"). - Expects(expectedPkgs, expectedRelationships). - TestCataloger(t, NewPortageCataloger()) - + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pkgtest.NewCatalogTester(). + FromDirectory(t, test.fixture). + Expects(test.expectedPackages, test.expectedRelationships). + TestCataloger(t, NewPortageCataloger()) + }) + } } func TestCataloger_Globs(t *testing.T) { diff --git a/syft/pkg/cataloger/gentoo/license.go b/syft/pkg/cataloger/gentoo/license.go new file mode 100644 index 000000000..d1453f12d --- /dev/null +++ b/syft/pkg/cataloger/gentoo/license.go @@ -0,0 +1,248 @@ +package gentoo + +import ( + "bufio" + "bytes" + "fmt" + "io" + "slices" + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" +) + +// the licenses files seems to conform to a custom format that is common to gentoo packages. +// see more details: +// - https://www.gentoo.org/glep/glep-0023.html#id9 +// - https://devmanual.gentoo.org/general-concepts/licenses/index.html +// +// in short, the format is: +// +// mandatory-license +// || ( choosable-licence1 chooseable-license-2 ) +// useflag? ( optional-component-license ) +// +// "License names may contain [a-zA-Z0-9] (english alphanumeric characters), _ (underscore), - (hyphen), . +// (dot) and + (plus sign). They must not begin with a hyphen, a dot or a plus sign." +// +// this does not conform to SPDX license expressions, which would be a great enhancement in the future. + +// extractLicenses attempts to parse the license field into a valid SPDX license expression +func extractLicenses(resolver file.Resolver, closestLocation *file.Location, reader io.Reader) (string, string) { + findings := strset.New() + contentsWriter := bytes.Buffer{} + scanner := bufio.NewScanner(io.TeeReader(reader, &contentsWriter)) + scanner.Split(bufio.ScanWords) + var ( + mandatoryLicenses, conditionalLicenses, useflagLicenses []string + usesGroups bool + pipe bool + useflag bool + ) + + for scanner.Scan() { + token := scanner.Text() + if token == "||" { + pipe = true + continue + } + // useflag + if strings.Contains(token, "?") { + useflag = true + continue + } + if !strings.ContainsAny(token, "()|?") { + switch { + case useflag: + useflagLicenses = append(useflagLicenses, token) + case pipe: + conditionalLicenses = append(conditionalLicenses, token) + default: + mandatoryLicenses = append(mandatoryLicenses, token) + } + if strings.HasPrefix(token, "@") { + usesGroups = true + } + } + } + + var licenseGroups map[string][]string + if usesGroups { + licenseGroups = readLicenseGroups(resolver, closestLocation) + } + mandatoryLicenses = replaceLicenseGroups(mandatoryLicenses, licenseGroups) + conditionalLicenses = replaceLicenseGroups(conditionalLicenses, licenseGroups) + findings.Add(mandatoryLicenses...) + findings.Add(conditionalLicenses...) + findings.Add(useflagLicenses...) + + var mandatoryStatement, conditionalStatement string + + // attempt to build valid SPDX license expression + if len(mandatoryLicenses) > 0 { + mandatoryStatement = strings.Join(mandatoryLicenses, " AND ") + } + if len(conditionalLicenses) > 0 { + conditionalStatement = strings.Join(conditionalLicenses, " OR ") + } + + contents := strings.TrimSpace(contentsWriter.String()) + + if mandatoryStatement != "" && conditionalStatement != "" { + return contents, mandatoryStatement + " AND (" + conditionalStatement + ")" + } + + if mandatoryStatement != "" { + return contents, mandatoryStatement + } + + if conditionalStatement != "" { + return contents, conditionalStatement + } + + return contents, "" +} + +func readLicenseGroups(resolver file.Resolver, closestLocation *file.Location) map[string][]string { + if resolver == nil || closestLocation == nil { + return nil + } + var licenseGroups map[string][]string + groupLocation := resolver.RelativeFileByPath(*closestLocation, "/etc/portage/license_groups") + if groupLocation == nil { + return nil + } + + groupReader, err := resolver.FileContentsByLocation(*groupLocation) + defer internal.CloseAndLogError(groupReader, groupLocation.RealPath) + if err != nil { + log.WithFields("path", groupLocation.RealPath, "error", err).Debug("failed to fetch portage LICENSE") + return nil + } + + if groupReader == nil { + return nil + } + + licenseGroups, err = parseLicenseGroups(groupReader) + if err != nil { + log.WithFields("path", groupLocation.RealPath, "error", err).Debug("failed to parse portage LICENSE") + } + + return licenseGroups +} + +func replaceLicenseGroups(licenses []string, groups map[string][]string) []string { + if groups == nil { + return licenses + } + + result := make([]string, 0, len(licenses)) + for _, license := range licenses { + if strings.HasPrefix(license, "@") { + // this is a license group... + name := strings.TrimPrefix(license, "@") + if expandedLicenses, ok := groups[name]; ok { + result = append(result, expandedLicenses...) + } else { + // unable to expand, use the original license group value (including the '@') + result = append(result, license) + } + } else { + // this is a license... + result = append(result, license) + } + } + return result +} + +func parseLicenseGroups(reader io.Reader) (map[string][]string, error) { + result := make(map[string][]string) + rawGroups := make(map[string][]string) + + scanner := bufio.NewScanner(reader) + + // first collect all raw groups + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if line == "" || strings.HasPrefix(line, "#") { + // skip empty lines and comments + continue + } + + parts := strings.Fields(line) + if len(parts) < 2 { + return nil, fmt.Errorf("invalid line format: %s", line) + } + + groupName := parts[0] + licenses := parts[1:] + + rawGroups[groupName] = licenses + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + // next process each group to expand nested references + for groupName, licenses := range rawGroups { + expanded, err := expandLicenses(groupName, licenses, rawGroups, make(map[string]bool)) + if err != nil { + return nil, err + } + result[groupName] = expanded + } + + return result, nil +} + +// expandLicenses handles the recursive expansion of license groups, 'visited' is used to detect cycles. We are always +// in terms of slices instead of sets to ensure original ordering is preserved. +func expandLicenses(currentGroup string, licenses []string, rawGroups map[string][]string, visited map[string]bool) ([]string, error) { + if visited[currentGroup] { + return nil, fmt.Errorf("cycle detected in license group definitions for group: %s", currentGroup) + } + + visited[currentGroup] = true + + result := make([]string, 0) + + for _, item := range licenses { + if strings.HasPrefix(item, "@") { + // this is a reference to another group + refGroupName := item[1:] // remove '@' prefix + + refLicenses, exists := rawGroups[refGroupName] + if !exists { + return nil, fmt.Errorf("referenced group not found: %s", refGroupName) + } + + newVisited := make(map[string]bool) + for k, v := range visited { + newVisited[k] = v + } + + expanded, err := expandLicenses(refGroupName, refLicenses, rawGroups, newVisited) + if err != nil { + return nil, err + } + + for _, license := range expanded { + if !slices.Contains(result, license) { + result = append(result, license) + } + } + } else if !slices.Contains(result, item) { + // ...this is a regular license + result = append(result, item) + } + } + + return result, nil +} diff --git a/syft/pkg/cataloger/gentoo/license_test.go b/syft/pkg/cataloger/gentoo/license_test.go new file mode 100644 index 000000000..1d58fd7a0 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/license_test.go @@ -0,0 +1,183 @@ +package gentoo + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// you can get a good sense of test fixtures with: +// docker run --rm -it gentoo/stage3 bash -c 'find var/db/pkg/ | grep LICENSE | xargs cat' + +func Test_extractLicenses(t *testing.T) { + + tests := []struct { + name string + license string + wantExpression string + }{ + { + name: "empty", + license: "", + wantExpression: "", + }, + { + name: "single", + license: "GPL-2", + wantExpression: "GPL-2", + }, + { + name: "multiple", + license: "GPL-2 GPL-3 ", // note the extra space + wantExpression: "GPL-2 AND GPL-3", + }, + { + name: "license choices", + license: "|| ( GPL-2 GPL-3 )\n", // note the newline + wantExpression: "GPL-2 OR GPL-3", + }, + { + // this might not be correct behavior, but we do our best with missing info + name: "license choices with missing useflag suffix", + license: "GPL-3+ LGPL-3+ || ( GPL-3+ libgcc libstdc++ gcc-runtime-library-exception-3.1 ) FDL-1.3+", // no use flag so what do we do with FDL here? + wantExpression: "GPL-3+ AND LGPL-3+ AND (GPL-3+ OR libgcc OR libstdc++ OR gcc-runtime-library-exception-3.1 OR FDL-1.3+)", // "OR FDL-1.3+" is probably wrong at the end... + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + raw, expression := extractLicenses(nil, nil, strings.NewReader(tt.license)) + assert.Equalf(t, tt.wantExpression, expression, "unexpected expression for %v", tt.license) + assert.Equalf(t, strings.TrimSpace(tt.license), raw, "unexpected raw for %v", tt.license) + }) + } +} + +func TestParseLicenseGroups(t *testing.T) { + tests := []struct { + name string + input string + expected map[string][]string + expectError require.ErrorAssertionFunc + }{ + { + name: "basic nesting example", + input: "test-fixtures/license-groups/example1", + expected: map[string][]string{ + "FSF-APPROVED": { + "Apache-2.0", "BSD", "BSD-2", "GPL-2", "GPL-3", "LGPL-2.1", "LGPL-3", "X11", "ZLIB", + "Apache-1.1", "BSD-4", "MPL-1.0", "MPL-1.1", "PSF-2.0", + }, + "GPL-COMPATIBLE": { + "Apache-2.0", "BSD", "BSD-2", "GPL-2", "GPL-3", "LGPL-2.1", "LGPL-3", "X11", "ZLIB", + }, + }, + }, + { + name: "error on cycles", + input: "test-fixtures/license-groups/cycle", + expectError: require.Error, + }, + { + name: "error on self references", + input: "test-fixtures/license-groups/self", + expectError: require.Error, + }, + { + name: "error on missing reference", + input: "test-fixtures/license-groups/missing", + expectError: require.Error, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.expectError == nil { + tc.expectError = require.NoError + } + + contents, err := os.ReadFile(tc.input) + require.NoError(t, err) + + actual, err := parseLicenseGroups(bytes.NewReader(contents)) + tc.expectError(t, err) + if err != nil { + return + } + + if d := cmp.Diff(tc.expected, actual); d != "" { + t.Errorf("unexpected license groups (-want +got):\n%s", d) + } + }) + } +} + +func TestReplaceLicenseGroups(t *testing.T) { + tests := []struct { + name string + licenses []string + groups map[string][]string + expected []string + }{ + { + name: "nil groups", + licenses: []string{"MIT", "Apache-2.0", "@GPL"}, + groups: nil, + expected: []string{"MIT", "Apache-2.0", "@GPL"}, + }, + { + name: "empty groups", + licenses: []string{"MIT", "Apache-2.0", "@GPL"}, + groups: map[string][]string{}, + expected: []string{"MIT", "Apache-2.0", "@GPL"}, + }, + { + name: "no group references", + licenses: []string{"MIT", "Apache-2.0", "GPL-2.0"}, + groups: map[string][]string{"GPL": {"GPL-2.0", "GPL-3.0"}}, + expected: []string{"MIT", "Apache-2.0", "GPL-2.0"}, + }, + { + name: "single group reference", + licenses: []string{"MIT", "@GPL", "Apache-2.0"}, + groups: map[string][]string{"GPL": {"GPL-2.0", "GPL-3.0"}}, + expected: []string{"MIT", "GPL-2.0", "GPL-3.0", "Apache-2.0"}, + }, + { + name: "multiple group references", + licenses: []string{"@MIT-LIKE", "@GPL", "BSD-3"}, + groups: map[string][]string{ + "MIT-LIKE": {"MIT", "ISC"}, + "GPL": {"GPL-2.0", "GPL-3.0"}, + }, + expected: []string{"MIT", "ISC", "GPL-2.0", "GPL-3.0", "BSD-3"}, + }, + { + name: "unknown group reference", + licenses: []string{"MIT", "@UNKNOWN", "Apache-2.0"}, + groups: map[string][]string{"GPL": {"GPL-2.0", "GPL-3.0"}}, + expected: []string{"MIT", "@UNKNOWN", "Apache-2.0"}, + }, + { + name: "reference at end", + licenses: []string{"MIT", "Apache-2.0", "@GPL"}, + groups: map[string][]string{"GPL": {"GPL-2.0", "GPL-3.0"}}, + expected: []string{"MIT", "Apache-2.0", "GPL-2.0", "GPL-3.0"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + inputLicenses := make([]string, len(tc.licenses)) + copy(inputLicenses, tc.licenses) + + actual := replaceLicenseGroups(inputLicenses, tc.groups) + + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/syft/pkg/cataloger/gentoo/parse_portage_contents.go b/syft/pkg/cataloger/gentoo/parse_portage_contents.go index 403d68943..a5e3f86dc 100644 --- a/syft/pkg/cataloger/gentoo/parse_portage_contents.go +++ b/syft/pkg/cataloger/gentoo/parse_portage_contents.go @@ -10,8 +10,6 @@ import ( "strconv" "strings" - "github.com/scylladb/go-set/strset" - "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" @@ -38,41 +36,41 @@ func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic. return nil, nil, fmt.Errorf("failed to parse portage name and version") } - p := pkg.Package{ - Name: name, - Version: version, - PURL: packageURL(name, version), - Locations: file.NewLocationSet( - reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), - ), - Type: pkg.PortagePkg, - Metadata: pkg.PortageEntry{ - // ensure the default value for a collection is never nil since this may be shown as JSON - Files: make([]pkg.PortageFileRecord, 0), - }, + m := pkg.PortageEntry{ + // ensure the default value for a collection is never nil since this may be shown as JSON + Files: make([]pkg.PortageFileRecord, 0), + } + + locations := file.NewLocationSet(reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + + licenses, licenseLocations := addLicenses(resolver, reader.Location, &m) + locations.Add(licenseLocations...) + locations.Add(addSize(resolver, reader.Location, &m)...) + addFiles(resolver, reader.Location, &m) + + p := pkg.Package{ + Name: name, + Version: version, + PURL: packageURL(name, version), + Locations: locations, + Licenses: licenses, + Type: pkg.PortagePkg, + Metadata: m, } - addLicenses(resolver, reader.Location, &p) - addSize(resolver, reader.Location, &p) - addFiles(resolver, reader.Location, &p) p.SetID() return []pkg.Package{p}, nil, nil } -func addFiles(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { +func addFiles(resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) { contentsReader, err := resolver.FileContentsByLocation(dbLocation) if err != nil { - log.WithFields("path", dbLocation.RealPath, "package", p.Name, "error", err).Debug("failed to fetch portage contents") + log.WithFields("path", dbLocation.RealPath, "error", err).Debug("failed to fetch portage contents") return } defer internal.CloseAndLogError(contentsReader, dbLocation.RealPath) - entry, ok := p.Metadata.(pkg.PortageEntry) - if !ok { - return - } - scanner := bufio.NewScanner(contentsReader) for scanner.Scan() { line := strings.Trim(scanner.Text(), "\n") @@ -89,60 +87,48 @@ func addFiles(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) entry.Files = append(entry.Files, record) } } - - p.Metadata = entry - p.Locations.Add(dbLocation) } -func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { +func addLicenses(resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) (pkg.LicenseSet, []file.Location) { parentPath := filepath.Dir(dbLocation.RealPath) location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "LICENSE")) if location == nil { - return + return pkg.NewLicenseSet(), nil } licenseReader, err := resolver.FileContentsByLocation(*location) if err != nil { log.WithFields("path", dbLocation.RealPath, "error", err).Debug("failed to fetch portage LICENSE") - return + return pkg.NewLicenseSet(), nil } defer internal.CloseAndLogError(licenseReader, location.RealPath) - findings := strset.New() - scanner := bufio.NewScanner(licenseReader) - scanner.Split(bufio.ScanWords) - for scanner.Scan() { - token := scanner.Text() - if token != "||" && token != "(" && token != ")" { - findings.Add(token) - } - } + og, spdxExpression := extractLicenses(resolver, location, licenseReader) + entry.Licenses = og - licenseCandidates := findings.List() - p.Licenses = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(*location, licenseCandidates...)...) - p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) + return pkg.NewLicenseSet( + pkg.NewLicenseFromLocations(spdxExpression, *location), + ), + []file.Location{ + location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation), + } } -func addSize(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { +func addSize(resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) []file.Location { parentPath := filepath.Dir(dbLocation.RealPath) location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "SIZE")) if location == nil { - return - } - - entry, ok := p.Metadata.(pkg.PortageEntry) - if !ok { - return + return nil } sizeReader, err := resolver.FileContentsByLocation(*location) if err != nil { - log.WithFields("name", p.Name, "error", err).Debug("failed to fetch portage SIZE") - return + log.WithFields("path", dbLocation.RealPath, "error", err).Debug("failed to fetch portage SIZE") + return nil } defer internal.CloseAndLogError(sizeReader, location.RealPath) @@ -155,6 +141,5 @@ func addSize(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { } } - p.Metadata = entry - p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) + return []file.Location{location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)} } diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/etc/portage/license_groups b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/etc/portage/license_groups new file mode 100644 index 000000000..30b29aa54 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/etc/portage/license_groups @@ -0,0 +1,8 @@ +# The FSF-APPROVED group includes the entire GPL-COMPATIBLE group and more. +FSF-APPROVED @GPL-COMPATIBLE Apache-1.1 BSD-4 MPL-1.0 MPL-1.1 PSF-2.0 +# The GPL-COMPATIBLE group includes all licenses compatible with the GNU GPL. +GPL-COMPATIBLE Apache-2.0 BSD BSD-2 GPL-2 GPL-3 LGPL-2.1 LGPL-3 X11 ZLIB +# for skopeo +GROUP1 Apache-2.0 @BSD-COMPATIBLE CC-BY-SA-4.0 +BSD-COMPATIBLE BSD BSD-2 +MIT-LIKE ISC MIT \ No newline at end of file diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS new file mode 100644 index 000000000..26fdf9282 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS @@ -0,0 +1,13 @@ +dir /usr +dir /usr/bin +obj /usr/bin/skopeo 376c02bd3b22804df8fdfdc895e7dbfb 1649284374 +dir /etc +dir /etc/containers +obj /etc/containers/policy.json c01eb6950f03419e09d4fc88cb42ff6f 1649284375 +dir /etc/containers/registries.d +obj /etc/containers/registries.d/default.yaml e6e66cd3c24623e0667f26542e0e08f6 1649284375 +dir /var +dir /var/lib +dir /var/lib/atomic +dir /var/lib/atomic/sigstore +obj /var/lib/atomic/sigstore/.keep_app-containers_skopeo-0 d41d8cd98f00b204e9800998ecf8427e 1649284375 diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/LICENSE b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/LICENSE new file mode 100644 index 000000000..6db7a45a6 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/LICENSE @@ -0,0 +1 @@ +@GROUP1 @MIT-LIKE diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/SIZE b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/SIZE new file mode 100644 index 000000000..fd07ace22 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/layout-license-groups/var/db/pkg/app-containers/skopeo-1.5.1/SIZE @@ -0,0 +1 @@ +27937835 diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/cycle b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/cycle new file mode 100644 index 000000000..7dd7d53dc --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/cycle @@ -0,0 +1,2 @@ +FSF-APPROVED @GPL-COMPATIBLE Apache-1.1 +GPL-COMPATIBLE Apache-2.0 @FSF-APPROVED diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/example1 b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/example1 new file mode 100644 index 000000000..a3e183a47 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/example1 @@ -0,0 +1,5 @@ +# The FSF-APPROVED group includes the entire GPL-COMPATIBLE group and more. +FSF-APPROVED @GPL-COMPATIBLE Apache-1.1 BSD-4 MPL-1.0 MPL-1.1 PSF-2.0 + +# The GPL-COMPATIBLE group includes all licenses compatible with the GNU GPL. +GPL-COMPATIBLE Apache-2.0 BSD BSD-2 GPL-2 GPL-3 LGPL-2.1 LGPL-3 X11 ZLIB diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/missing b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/missing new file mode 100644 index 000000000..32afd0c39 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/missing @@ -0,0 +1,3 @@ +FSF-APPROVED @GPL-COMPATIBLE Apache-1.1 + +GPL-COMPATIBLE Apache-2.0 @MISSING diff --git a/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/self b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/self new file mode 100644 index 000000000..8e2a42639 --- /dev/null +++ b/syft/pkg/cataloger/gentoo/test-fixtures/license-groups/self @@ -0,0 +1,2 @@ +FSF-APPROVED Apache-1.1 +GPL-COMPATIBLE Apache-2.0 @GPL-COMPATIBLE diff --git a/syft/pkg/portage.go b/syft/pkg/portage.go index 5c9aa1b4b..35a556a70 100644 --- a/syft/pkg/portage.go +++ b/syft/pkg/portage.go @@ -12,7 +12,8 @@ var _ FileOwner = (*PortageEntry)(nil) // PortageEntry represents a single package entry in the portage DB flat-file store. type PortageEntry struct { - InstalledSize int `mapstructure:"InstalledSize" json:"installedSize" cyclonedx:"installedSize"` + InstalledSize int `json:"installedSize" cyclonedx:"installedSize"` + Licenses string `json:"licenses,omitempty"` Files []PortageFileRecord `json:"files"` }