From d7a51a69dd5d275c515f056455f515f2420f8d2e Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 9 Nov 2022 09:55:54 -0500 Subject: [PATCH] Update java generic cataloger (#1329) * remove centralize pURL generation Signed-off-by: Alex Goodman * port java cataloger to new generic cataloger pattern Signed-off-by: Alex Goodman * remove common.GenericCataloger Signed-off-by: Alex Goodman * update format test fixtures to reflect ID updates Signed-off-by: Alex Goodman * fix package sort instability for encode-decode-encode cycles Signed-off-by: Alex Goodman Signed-off-by: Alex Goodman --- go.mod | 2 +- go.sum | 4 +- internal/constants.go | 2 +- schema/json/generate.go | 6 +- schema/json/schema-5.1.0.json | 1511 +++++++++++++++++ .../TestCycloneDxDirectoryEncoder.golden | 8 +- .../snapshot/TestCycloneDxImageEncoder.golden | 8 +- .../TestCycloneDxDirectoryEncoder.golden | 8 +- .../snapshot/TestCycloneDxImageEncoder.golden | 8 +- .../TestSPDXJSONDirectoryEncoder.golden | 8 +- .../snapshot/TestSPDXJSONImageEncoder.golden | 8 +- .../snapshot/TestSPDXRelationshipOrder.golden | 20 +- .../snapshot/TestSPDXJSONSPDXIDs.golden | 10 +- .../TestSPDXTagValueDirectoryEncoder.golden | 8 +- .../TestSPDXTagValueImageEncoder.golden | 8 +- .../snapshot/TestDirectoryEncoder.golden | 8 +- .../TestEncodeFullJSONDocument.golden | 8 +- .../snapshot/TestImageEncoder.golden | 8 +- syft/pkg/cataloger/catalog.go | 5 - syft/pkg/cataloger/common/cpe/java.go | 4 + .../pkg/cataloger/common/generic_cataloger.go | 136 -- .../common/generic_cataloger_test.go | 176 -- syft/pkg/cataloger/common/parser.go | 11 - .../cataloger/common/test-fixtures/a-path.txt | 1 - .../common/test-fixtures/another-path.txt | 1 - .../cataloger/common/test-fixtures/empty.txt | 0 .../common/test-fixtures/last/path.txt | 1 - .../internal/pkgtest/test_generic_parser.go | 40 +- syft/pkg/cataloger/java/archive_parser.go | 104 +- .../pkg/cataloger/java/archive_parser_test.go | 90 +- syft/pkg/cataloger/java/cataloger.go | 31 +- syft/pkg/cataloger/java/package_url.go | 10 +- syft/pkg/cataloger/java/package_url_test.go | 2 +- .../cataloger/java/parse_pom_properties.go | 5 - .../java/parse_pom_properties_test.go | 4 - syft/pkg/cataloger/java/parse_pom_xml.go | 42 +- syft/pkg/cataloger/java/parse_pom_xml_test.go | 80 +- syft/pkg/cataloger/java/pom_cataloger.go | 17 - .../java/tar_wrapped_archive_parser.go | 17 +- .../java/tar_wrapped_archive_parser_test.go | 7 +- .../java/zip_wrapped_archive_parser.go | 13 +- .../java/zip_wrapped_archive_parser_test.go | 7 +- syft/pkg/java_metadata.go | 11 +- syft/pkg/package.go | 63 +- syft/pkg/package_test.go | 4 +- syft/pkg/php_composer_json_metadata.go | 35 - syft/pkg/php_composer_json_metadata_test.go | 66 - syft/pkg/url.go | 38 - syft/pkg/url_test.go | 100 -- test/cli/json_schema_test.go | 1 + test/integration/encode_decode_cycle_test.go | 2 +- 51 files changed, 1872 insertions(+), 895 deletions(-) create mode 100644 schema/json/schema-5.1.0.json delete mode 100644 syft/pkg/cataloger/common/generic_cataloger.go delete mode 100644 syft/pkg/cataloger/common/generic_cataloger_test.go delete mode 100644 syft/pkg/cataloger/common/parser.go delete mode 100644 syft/pkg/cataloger/common/test-fixtures/a-path.txt delete mode 100644 syft/pkg/cataloger/common/test-fixtures/another-path.txt delete mode 100644 syft/pkg/cataloger/common/test-fixtures/empty.txt delete mode 100644 syft/pkg/cataloger/common/test-fixtures/last/path.txt delete mode 100644 syft/pkg/cataloger/java/pom_cataloger.go delete mode 100644 syft/pkg/php_composer_json_metadata_test.go delete mode 100644 syft/pkg/url_test.go diff --git a/go.mod b/go.mod index 633296c47..9dfb3f6e7 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acobaugh/osrelease v0.1.0 github.com/adrg/xdg v0.3.3 - github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b @@ -57,6 +56,7 @@ require ( github.com/docker/docker v20.10.17+incompatible github.com/google/go-containerregistry v0.11.0 github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f + github.com/invopop/jsonschema v0.7.0 github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce github.com/opencontainers/go-digest v1.0.0 github.com/sassoftware/go-rpmutils v0.2.0 diff --git a/go.sum b/go.sum index 1db9b3347..11df57a6b 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,6 @@ github.com/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U= github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 h1:Lw9q+WyJLFOR+AULchS5/2GKfM+6gOh4szzizdfH3MU= -github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -1220,6 +1218,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= +github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So= +github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= diff --git a/internal/constants.go b/internal/constants.go index cf55707cb..274699b13 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ 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 = "5.0.1" + JSONSchemaVersion = "5.1.0" ) diff --git a/schema/json/generate.go b/schema/json/generate.go index ed4753ff6..ad87d148c 100644 --- a/schema/json/generate.go +++ b/schema/json/generate.go @@ -10,7 +10,7 @@ import ( "sort" "strings" - "github.com/alecthomas/jsonschema" + "github.com/invopop/jsonschema" "github.com/anchore/syft/internal" syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model" @@ -56,7 +56,7 @@ func main() { func build() *jsonschema.Schema { reflector := &jsonschema.Reflector{ AllowAdditionalProperties: true, - TypeNamer: func(r reflect.Type) string { + Namer: func(r reflect.Type) string { return strings.TrimPrefix(r.Name(), "JSON") }, } @@ -88,7 +88,7 @@ func build() *jsonschema.Schema { } for _, name := range metadataNames { metadataTypes = append(metadataTypes, map[string]string{ - "$ref": fmt.Sprintf("#/definitions/%s", name), + "$ref": fmt.Sprintf("#/$defs/%s", name), }) } diff --git a/schema/json/schema-5.1.0.json b/schema/json/schema-5.1.0.json new file mode 100644 index 000000000..be570633b --- /dev/null +++ b/schema/json/schema-5.1.0.json @@ -0,0 +1,1511 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/anchore/syft/syft/formats/syftjson/model/document", + "$ref": "#/$defs/Document", + "$defs": { + "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" + }, + "AlpmMetadata": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "license": { + "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" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "license", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "ApkMetadata": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "type": "string" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "license", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "CargoPackageMetadata": { + "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" + ] + }, + "Classification": { + "properties": { + "class": { + "type": "string" + }, + "virtual_path": { + "type": "string" + }, + "metadata": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "class", + "virtual_path", + "metadata" + ] + }, + "CocoapodsMetadata": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "ConanLockMetadata": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "type": "string" + }, + "build_requires": { + "type": "string" + }, + "py_requires": { + "type": "string" + }, + "options": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ConanMetadata": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubMetadata": { + "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" + }, + "secrets": { + "items": { + "$ref": "#/$defs/Secrets" + }, + "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" + ] + }, + "DotnetDepsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "DpkgMetadata": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "classifications": { + "items": { + "$ref": "#/$defs/Classification" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType" + ] + }, + "GemMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "GolangBinMetadata": { + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "HackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "JavaMetadata": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/PomProperties" + }, + "pomProject": { + "$ref": "#/$defs/PomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "KbPackageMetadata": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "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" + } + }, + "type": "object" + }, + "NpmPackageJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "author": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "licenses", + "homepage", + "description", + "url", + "private" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Coordinates" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmMetadata" + }, + { + "$ref": "#/$defs/ApkMetadata" + }, + { + "$ref": "#/$defs/CargoPackageMetadata" + }, + { + "$ref": "#/$defs/CocoapodsMetadata" + }, + { + "$ref": "#/$defs/ConanLockMetadata" + }, + { + "$ref": "#/$defs/ConanMetadata" + }, + { + "$ref": "#/$defs/DartPubMetadata" + }, + { + "$ref": "#/$defs/DotnetDepsMetadata" + }, + { + "$ref": "#/$defs/DpkgMetadata" + }, + { + "$ref": "#/$defs/GemMetadata" + }, + { + "$ref": "#/$defs/GolangBinMetadata" + }, + { + "$ref": "#/$defs/HackageMetadata" + }, + { + "$ref": "#/$defs/JavaMetadata" + }, + { + "$ref": "#/$defs/KbPackageMetadata" + }, + { + "$ref": "#/$defs/NpmPackageJSONMetadata" + }, + { + "$ref": "#/$defs/PhpComposerJSONMetadata" + }, + { + "$ref": "#/$defs/PortageMetadata" + }, + { + "$ref": "#/$defs/PythonPackageMetadata" + }, + { + "$ref": "#/$defs/RpmMetadata" + } + ] + } + }, + "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" + ] + }, + "PhpComposerJSONMetadata": { + "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" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "license": { + "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" + ] + }, + "PomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "PomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/PomParent" + }, + "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" + ] + }, + "PomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PortageMetadata": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "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" + ] + }, + "PythonPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "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" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmMetadata": { + "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" + }, + "license": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmdbFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "license", + "vendor", + "modularityLabel", + "files" + ] + }, + "RpmdbFileRecord": { + "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" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SearchResult": { + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ] + }, + "Secrets": { + "properties": { + "location": { + "$ref": "#/$defs/Coordinates" + }, + "secrets": { + "items": { + "$ref": "#/$defs/SearchResult" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "location", + "secrets" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "target": true + }, + "type": "object", + "required": [ + "id", + "type", + "target" + ] + } + } +} diff --git a/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index 355967410..a7a90ebaa 100644 --- a/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -1,10 +1,10 @@ { "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:f426926b-4867-4b52-9142-23997f685f2c", + "serialNumber": "urn:uuid:cfd602eb-022b-4a61-84d4-50d34612bd84", "version": 1, "metadata": { - "timestamp": "2022-10-24T09:54:37-04:00", + "timestamp": "2022-11-07T09:11:21-05:00", "tools": [ { "vendor": "anchore", @@ -20,7 +20,7 @@ }, "components": [ { - "bom-ref": "e624319940d8d36a", + "bom-ref": "1b1d0be59ac59d2c", "type": "library", "name": "package-1", "version": "1.0.1", @@ -57,7 +57,7 @@ ] }, { - "bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=b8645f4ac2a0891e", + "bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3", "type": "library", "name": "package-2", "version": "2.0.1", diff --git a/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 984423df0..21e034acb 100644 --- a/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -1,10 +1,10 @@ { "bomFormat": "CycloneDX", "specVersion": "1.4", - "serialNumber": "urn:uuid:41bbbcc7-694d-4b07-a678-0afb67dabdf9", + "serialNumber": "urn:uuid:ced49687-6384-48fa-a930-ef83a258bf77", "version": 1, "metadata": { - "timestamp": "2022-10-24T09:54:37-04:00", + "timestamp": "2022-11-07T09:11:21-05:00", "tools": [ { "vendor": "anchore", @@ -21,7 +21,7 @@ }, "components": [ { - "bom-ref": "5ffee24fb164cffc", + "bom-ref": "66ba429119b8bec6", "type": "library", "name": "package-1", "version": "1.0.1", @@ -62,7 +62,7 @@ ] }, { - "bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=8b16570b2b4155c3", + "bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4", "type": "library", "name": "package-2", "version": "2.0.1", diff --git a/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index a399d21c5..095d2c3b8 100644 --- a/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -1,7 +1,7 @@ - + - 2022-10-24T09:54:54-04:00 + 2022-11-07T09:11:06-05:00 anchore @@ -14,7 +14,7 @@ - + package-1 1.0.1 @@ -32,7 +32,7 @@ /some/path/pkg1 - + package-2 2.0.1 cpe:2.3:*:some:package:2:*:*:*:*:*:*:* diff --git a/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 1783aba0d..814962bed 100644 --- a/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -1,7 +1,7 @@ - + - 2022-10-24T09:54:54-04:00 + 2022-11-07T09:11:06-05:00 anchore @@ -15,7 +15,7 @@ - + package-1 1.0.1 @@ -34,7 +34,7 @@ /somefile-1.txt - + package-2 2.0.1 cpe:2.3:*:some:package:2:*:*:*:*:*:*:* diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden index 35b8c11b0..31b2f4cd0 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden +++ b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden @@ -3,7 +3,7 @@ "name": "/some/path", "spdxVersion": "SPDX-2.2", "creationInfo": { - "created": "2022-10-24T13:54:19.225779Z", + "created": "2022-11-07T14:11:33.934417Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" @@ -11,10 +11,10 @@ "licenseListVersion": "3.18" }, "dataLicense": "CC0-1.0", - "documentNamespace": "https://anchore.com/syft/dir/some/path-cd89c782-240b-461e-81a1-63863e02642f", + "documentNamespace": "https://anchore.com/syft/dir/some/path-938e09e5-4fcc-4e3a-8833-a8b59e923bdc", "packages": [ { - "SPDXID": "SPDXRef-e624319940d8d36a", + "SPDXID": "SPDXRef-1b1d0be59ac59d2c", "name": "package-1", "licenseConcluded": "MIT", "downloadLocation": "NOASSERTION", @@ -36,7 +36,7 @@ "versionInfo": "1.0.1" }, { - "SPDXID": "SPDXRef-b8645f4ac2a0891e", + "SPDXID": "SPDXRef-db4abfe497c180d3", "name": "package-2", "licenseConcluded": "NONE", "downloadLocation": "NOASSERTION", diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden index 6b857338f..38d015e90 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden +++ b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden @@ -3,7 +3,7 @@ "name": "user-image-input", "spdxVersion": "SPDX-2.2", "creationInfo": { - "created": "2022-10-24T13:54:19.477217Z", + "created": "2022-11-07T14:11:33.941498Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" @@ -11,10 +11,10 @@ "licenseListVersion": "3.18" }, "dataLicense": "CC0-1.0", - "documentNamespace": "https://anchore.com/syft/image/user-image-input-0b40ce75-7e54-4760-bd9d-4fa833b352dd", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-797c013c-1d76-4d3e-9391-521152bfc87d", "packages": [ { - "SPDXID": "SPDXRef-5ffee24fb164cffc", + "SPDXID": "SPDXRef-66ba429119b8bec6", "name": "package-1", "licenseConcluded": "MIT", "downloadLocation": "NOASSERTION", @@ -36,7 +36,7 @@ "versionInfo": "1.0.1" }, { - "SPDXID": "SPDXRef-8b16570b2b4155c3", + "SPDXID": "SPDXRef-958443e2d9304af4", "name": "package-2", "licenseConcluded": "NONE", "downloadLocation": "NOASSERTION", diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index da5ece14c..8591b9da8 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -3,7 +3,7 @@ "name": "user-image-input", "spdxVersion": "SPDX-2.2", "creationInfo": { - "created": "2022-10-24T13:54:19.48428Z", + "created": "2022-11-07T14:11:33.947742Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" @@ -11,10 +11,10 @@ "licenseListVersion": "3.18" }, "dataLicense": "CC0-1.0", - "documentNamespace": "https://anchore.com/syft/image/user-image-input-1a4dc179-1222-463c-b4e9-619131af7e97", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-30117c3a-546f-45b7-a1a8-91f2ecd8d2aa", "packages": [ { - "SPDXID": "SPDXRef-5ffee24fb164cffc", + "SPDXID": "SPDXRef-66ba429119b8bec6", "name": "package-1", "licenseConcluded": "MIT", "downloadLocation": "NOASSERTION", @@ -44,7 +44,7 @@ "versionInfo": "1.0.1" }, { - "SPDXID": "SPDXRef-8b16570b2b4155c3", + "SPDXID": "SPDXRef-958443e2d9304af4", "name": "package-2", "licenseConcluded": "NONE", "downloadLocation": "NOASSERTION", @@ -118,32 +118,32 @@ ], "relationships": [ { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c" }, { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-839d99ee67d9d174" }, { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-9c2f7510199b17f6" }, { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-c641caa71518099f" }, { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-c6f5b29dca12661f" }, { - "spdxElementId": "SPDXRef-5ffee24fb164cffc", + "spdxElementId": "SPDXRef-66ba429119b8bec6", "relationshipType": "CONTAINS", "relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd" } diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden index e8a34bd73..3fc131241 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden +++ b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden @@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: . -DocumentNamespace: https://anchore.com/syft/dir/4593d944-756e-49aa-af4e-b1a5acf09b97 +DocumentNamespace: https://anchore.com/syft/dir/a4820ad7-d106-497f-bda7-e694e9ad1050 LicenseListVersion: 3.18 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-10-24T13:53:53Z +Created: 2022-11-07T14:11:42Z ##### Package: @at-sign PackageName: @at-sign -SPDXID: SPDXRef-Package---at-sign-fe69bc18c2698fc4 +SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageLicenseConcluded: NONE @@ -21,7 +21,7 @@ PackageCopyrightText: NOASSERTION ##### Package: some/slashes PackageName: some/slashes -SPDXID: SPDXRef-Package--some-slashes-57ed206c09e6e5f4 +SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageLicenseConcluded: NONE @@ -31,7 +31,7 @@ PackageCopyrightText: NOASSERTION ##### Package: under_scores PackageName: under_scores -SPDXID: SPDXRef-Package--under-scores-8b7505907fdaf19d +SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageLicenseConcluded: NONE diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden index 13eca0f9e..571c7c36a 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden +++ b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden @@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: /some/path -DocumentNamespace: https://anchore.com/syft/dir/some/path-a4e58523-00d0-4135-9d21-cf586fbd340c +DocumentNamespace: https://anchore.com/syft/dir/some/path-01844fe2-60ea-45fd-bb92-df9f5660330d LicenseListVersion: 3.18 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-10-24T13:53:52Z +Created: 2022-11-07T14:11:42Z ##### Package: package-2 PackageName: package-2 -SPDXID: SPDXRef-Package-deb-package-2-b8645f4ac2a0891e +SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3 PackageVersion: 2.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false @@ -24,7 +24,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-e624319940d8d36a +SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c PackageVersion: 1.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden index e9d613185..b13c79c3b 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden +++ b/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden @@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: user-image-input -DocumentNamespace: https://anchore.com/syft/image/user-image-input-639f628a-5f8b-4050-a69e-90c85f0d7837 +DocumentNamespace: https://anchore.com/syft/image/user-image-input-32ec506e-82d8-4da6-991d-e1000e4e562e LicenseListVersion: 3.18 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-10-24T13:53:53Z +Created: 2022-11-07T14:11:42Z ##### Package: package-2 PackageName: package-2 -SPDXID: SPDXRef-Package-deb-package-2-8b16570b2b4155c3 +SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4 PackageVersion: 2.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false @@ -24,7 +24,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-5ffee24fb164cffc +SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6 PackageVersion: 1.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index 6e9f1514d..6d979abc7 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "e624319940d8d36a", + "id": "1b1d0be59ac59d2c", "name": "package-1", "version": "1.0.1", "type": "python", @@ -36,7 +36,7 @@ } }, { - "id": "b8645f4ac2a0891e", + "id": "db4abfe497c180d3", "name": "package-2", "version": "2.0.1", "type": "deb", @@ -89,7 +89,7 @@ } }, "schema": { - "version": "5.0.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" + "version": "5.1.0", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 10f7f7422..ac194b6c4 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "8373dcf05581b932", + "id": "304a5a8e5958a49d", "name": "package-1", "version": "1.0.1", "type": "python", @@ -31,7 +31,7 @@ } }, { - "id": "c3d4da40f387eec7", + "id": "9fd0b9f41034991d", "name": "package-2", "version": "2.0.1", "type": "deb", @@ -185,7 +185,7 @@ } }, "schema": { - "version": "5.0.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" + "version": "5.1.0", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index 1daf6a0e0..c31bbaa58 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "5ffee24fb164cffc", + "id": "66ba429119b8bec6", "name": "package-1", "version": "1.0.1", "type": "python", @@ -32,7 +32,7 @@ } }, { - "id": "8b16570b2b4155c3", + "id": "958443e2d9304af4", "name": "package-2", "version": "2.0.1", "type": "deb", @@ -112,7 +112,7 @@ } }, "schema": { - "version": "5.0.1", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" + "version": "5.1.0", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json" } } diff --git a/syft/pkg/cataloger/catalog.go b/syft/pkg/cataloger/catalog.go index 3a2b0f771..ce0c7158e 100644 --- a/syft/pkg/cataloger/catalog.go +++ b/syft/pkg/cataloger/catalog.go @@ -67,11 +67,6 @@ func Catalog(resolver source.FileResolver, release *linux.Release, catalogers .. // we might have binary classified CPE already with the package so we want to append here p.CPEs = append(p.CPEs, cpe.Generate(p)...) - // generate PURL (note: this is excluded from package ID, so is safe to mutate) - if p.PURL == "" { - p.PURL = pkg.URL(p, release) - } - // if we were not able to identify the language we have an opportunity // to try and get this value from the PURL. Worst case we assert that // we could not identify the language at either stage and set UnknownLanguage diff --git a/syft/pkg/cataloger/common/cpe/java.go b/syft/pkg/cataloger/common/cpe/java.go index 7975e80e4..6de454c06 100644 --- a/syft/pkg/cataloger/common/cpe/java.go +++ b/syft/pkg/cataloger/common/cpe/java.go @@ -181,6 +181,10 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) { return nil } + return GroupIDsFromJavaMetadata(metadata) +} + +func GroupIDsFromJavaMetadata(metadata pkg.JavaMetadata) (groupIDs []string) { groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...) groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...) groupIDs = append(groupIDs, groupIDsFromJavaManifest(metadata.Manifest)...) diff --git a/syft/pkg/cataloger/common/generic_cataloger.go b/syft/pkg/cataloger/common/generic_cataloger.go deleted file mode 100644 index 5de3666bf..000000000 --- a/syft/pkg/cataloger/common/generic_cataloger.go +++ /dev/null @@ -1,136 +0,0 @@ -/* -Package common provides generic utilities used by multiple catalogers. -*/ -package common - -import ( - "fmt" - - "github.com/anchore/syft/internal" - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" -) - -// GenericCataloger implements the Catalog interface and is responsible for dispatching the proper parser function for -// a given path or glob pattern. This is intended to be reusable across many package cataloger types. -type GenericCataloger struct { - globParsers map[string]ParserFn - pathParsers map[string]ParserFn - postProcessors []PostProcessFunc - upstreamCataloger string -} - -type PostProcessFunc func(resolver source.FileResolver, location source.Location, p *pkg.Package) error - -// NewGenericCataloger if provided path-to-parser-function and glob-to-parser-function lookups creates a GenericCataloger -func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn, upstreamCataloger string, postProcessors ...PostProcessFunc) *GenericCataloger { - return &GenericCataloger{ - globParsers: globParsers, - pathParsers: pathParsers, - postProcessors: postProcessors, - upstreamCataloger: upstreamCataloger, - } -} - -// Name returns a string that uniquely describes the upstream cataloger that this Generic Cataloger represents. -func (c *GenericCataloger) Name() string { - return c.upstreamCataloger -} - -// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source. -func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { - var packages []pkg.Package - var relationships []artifact.Relationship - - for location, parser := range c.selectFiles(resolver) { - contentReader, err := resolver.FileContentsByLocation(location) - if err != nil { - // TODO: fail or log? - return nil, nil, fmt.Errorf("unable to fetch contents at location=%v: %w", location, err) - } - - discoveredPackages, discoveredRelationships, err := parser(location.RealPath, contentReader) - internal.CloseAndLogError(contentReader, location.VirtualPath) - if err != nil { - // TODO: should we fail? or only log? - log.Warnf("cataloger '%s' failed to parse entries at location=%+v: %+v", c.upstreamCataloger, location, err) - continue - } - - pkgsForRemoval := make(map[artifact.ID]struct{}) - var cleanedRelationships []artifact.Relationship - for _, p := range discoveredPackages { - p.FoundBy = c.upstreamCataloger - p.Locations.Add(location) - p.SetID() - // doing it here so all packages have an ID, - // IDs are later used to remove relationships - if !pkg.IsValid(p) { - pkgsForRemoval[p.ID()] = struct{}{} - continue - } - - for _, postProcess := range c.postProcessors { - err = postProcess(resolver, location, p) - if err != nil { - return nil, nil, err - } - } - - packages = append(packages, *p) - } - - cleanedRelationships = removeRelationshipsWithArtifactIDs(pkgsForRemoval, discoveredRelationships) - relationships = append(relationships, cleanedRelationships...) - } - return packages, relationships, nil -} - -func removeRelationshipsWithArtifactIDs(artifactsToExclude map[artifact.ID]struct{}, relationships []artifact.Relationship) []artifact.Relationship { - if len(artifactsToExclude) == 0 || len(relationships) == 0 { - // no removal to do - return relationships - } - - var cleanedRelationships []artifact.Relationship - for _, r := range relationships { - _, removeTo := artifactsToExclude[r.To.ID()] - _, removaFrom := artifactsToExclude[r.From.ID()] - if !removeTo && !removaFrom { - cleanedRelationships = append(cleanedRelationships, r) - } - } - - return cleanedRelationships -} - -// SelectFiles takes a set of file trees and resolves and file references of interest for future cataloging -func (c *GenericCataloger) selectFiles(resolver source.FilePathResolver) map[source.Location]ParserFn { - var parserByLocation = make(map[source.Location]ParserFn) - - // select by exact path - for path, parser := range c.pathParsers { - files, err := resolver.FilesByPath(path) - if err != nil { - log.Warnf("cataloger failed to select files by path: %+v", err) - } - for _, f := range files { - parserByLocation[f] = parser - } - } - - // select by glob pattern - for globPattern, parser := range c.globParsers { - fileMatches, err := resolver.FilesByGlob(globPattern) - if err != nil { - log.Warnf("failed to find files by glob: %s", globPattern) - } - for _, f := range fileMatches { - parserByLocation[f] = parser - } - } - - return parserByLocation -} diff --git a/syft/pkg/cataloger/common/generic_cataloger_test.go b/syft/pkg/cataloger/common/generic_cataloger_test.go deleted file mode 100644 index 6e6a2c729..000000000 --- a/syft/pkg/cataloger/common/generic_cataloger_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package common - -import ( - "fmt" - "io" - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" -) - -func TestGenericCataloger(t *testing.T) { - allParsedPathes := make(map[string]bool) - parser := func(path string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - allParsedPathes[path] = true - contents, err := ioutil.ReadAll(reader) - require.NoError(t, err) - - p := &pkg.Package{Name: string(contents)} - r := artifact.Relationship{From: p, To: p, - Type: artifact.ContainsRelationship, - } - - return []*pkg.Package{p}, []artifact.Relationship{r}, nil - } - - globParsers := map[string]ParserFn{ - "**/a-path.txt": parser, - "**/empty.txt": parser, - } - pathParsers := map[string]ParserFn{ - "test-fixtures/another-path.txt": parser, - "test-fixtures/last/path.txt": parser, - } - upstream := "some-other-cataloger" - - expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt", "test-fixtures/empty.txt"} - resolver := source.NewMockResolverForPaths(expectedSelection...) - cataloger := NewGenericCataloger(pathParsers, globParsers, upstream) - - actualPkgs, relationships, err := cataloger.Catalog(resolver) - assert.NoError(t, err) - - expectedPkgs := make(map[string]pkg.Package) - for _, path := range expectedSelection { - require.True(t, allParsedPathes[path]) - expectedPkgs[path] = pkg.Package{ - FoundBy: upstream, - Name: fmt.Sprintf("%s file contents!", path), - } - } - - assert.Len(t, allParsedPathes, len(expectedSelection)) - // empty.txt won't become a package - assert.Len(t, actualPkgs, len(expectedPkgs)-1) - // right now, a relationship is created for each package, but if the relationship includes an invalid package it should be dropped. - assert.Len(t, relationships, len(actualPkgs)) - - for _, p := range actualPkgs { - ref := p.Locations.ToSlice()[0] - exP, ok := expectedPkgs[ref.RealPath] - if !ok { - t.Errorf("missing expected pkg: ref=%+v", ref) - continue - } - - if p.FoundBy != exP.FoundBy { - t.Errorf("bad upstream: %s", p.FoundBy) - } - - if exP.Name != p.Name { - t.Errorf("bad contents mapping: %+v", p.Locations) - } - } -} - -func Test_removeRelationshipsWithArtifactIDs(t *testing.T) { - one := &pkg.Package{Name: "one", Version: "1.0"} - two := &pkg.Package{Name: "two", Version: "1.0"} - three := &pkg.Package{Name: "three", Version: "1.0"} - four := &pkg.Package{Name: "four", Version: "bla"} - five := &pkg.Package{Name: "five", Version: "1.0"} - - pkgs := make([]artifact.Identifiable, 0) - for _, p := range []*pkg.Package{one, two, three, four, five} { - // IDs are necessary for comparison - p.SetID() - pkgs = append(pkgs, p) - } - - type args struct { - remove map[artifact.ID]struct{} - relationships []artifact.Relationship - } - tests := []struct { - name string - args args - want []artifact.Relationship - }{ - { - name: "nothing-to-remove", - args: args{ - relationships: []artifact.Relationship{ - {From: one, To: two}, - }, - }, - want: []artifact.Relationship{ - {From: one, To: two}, - }, - }, - { - name: "remove-all-relationships", - args: args{ - remove: map[artifact.ID]struct{}{ - one.ID(): {}, - three.ID(): {}, - }, - relationships: []artifact.Relationship{ - {From: one, To: two}, - {From: two, To: three}, - {From: three, To: four}, - }, - }, - want: []artifact.Relationship(nil), - }, - { - name: "remove-half-of-relationships", - args: args{ - remove: map[artifact.ID]struct{}{ - one.ID(): {}, - }, - relationships: []artifact.Relationship{ - {From: one, To: two}, - {From: one, To: three}, - {From: two, To: three}, - {From: three, To: four}, - }, - }, - want: []artifact.Relationship{ - {From: two, To: three}, - {From: three, To: four}, - }, - }, - { - name: "remove-repeated-relationships", - args: args{ - remove: map[artifact.ID]struct{}{ - one.ID(): {}, - two.ID(): {}, - }, - relationships: []artifact.Relationship{ - {From: one, To: two}, - {From: one, To: three}, - {From: two, To: three}, - {From: two, To: three}, - {From: three, To: four}, - {From: four, To: five}, - }, - }, - want: []artifact.Relationship{ - {From: three, To: four}, - {From: four, To: five}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, removeRelationshipsWithArtifactIDs(tt.args.remove, tt.args.relationships), "removeRelationshipsWithArtifactIDs(%v, %v)", tt.args.remove, tt.args.relationships) - }) - } -} diff --git a/syft/pkg/cataloger/common/parser.go b/syft/pkg/cataloger/common/parser.go deleted file mode 100644 index b79834dbd..000000000 --- a/syft/pkg/cataloger/common/parser.go +++ /dev/null @@ -1,11 +0,0 @@ -package common - -import ( - "io" - - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/pkg" -) - -// ParserFn standardizes a function signature for parser functions that accept the virtual file path (not usable for file reads) and contents and return any discovered packages from that file -type ParserFn func(string, io.Reader) ([]*pkg.Package, []artifact.Relationship, error) diff --git a/syft/pkg/cataloger/common/test-fixtures/a-path.txt b/syft/pkg/cataloger/common/test-fixtures/a-path.txt deleted file mode 100644 index 67e954034..000000000 --- a/syft/pkg/cataloger/common/test-fixtures/a-path.txt +++ /dev/null @@ -1 +0,0 @@ -test-fixtures/a-path.txt file contents! \ No newline at end of file diff --git a/syft/pkg/cataloger/common/test-fixtures/another-path.txt b/syft/pkg/cataloger/common/test-fixtures/another-path.txt deleted file mode 100644 index 0d654f8fe..000000000 --- a/syft/pkg/cataloger/common/test-fixtures/another-path.txt +++ /dev/null @@ -1 +0,0 @@ -test-fixtures/another-path.txt file contents! \ No newline at end of file diff --git a/syft/pkg/cataloger/common/test-fixtures/empty.txt b/syft/pkg/cataloger/common/test-fixtures/empty.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/syft/pkg/cataloger/common/test-fixtures/last/path.txt b/syft/pkg/cataloger/common/test-fixtures/last/path.txt deleted file mode 100644 index 3d4a165ab..000000000 --- a/syft/pkg/cataloger/common/test-fixtures/last/path.txt +++ /dev/null @@ -1 +0,0 @@ -test-fixtures/last/path.txt file contents! \ No newline at end of file diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index 25f8edad8..70b0e6e5d 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -33,13 +33,15 @@ type CatalogTester struct { func NewCatalogTester() *CatalogTester { return &CatalogTester{ - wantErr: require.NoError, - locationComparer: func(x, y source.Location) bool { - return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath) - }, + wantErr: require.NoError, + locationComparer: DefaultLocationComparer, } } +func DefaultLocationComparer(x, y source.Location) bool { + return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath) +} + func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester { t.Helper() @@ -147,6 +149,7 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi p.compareOptions = append(p.compareOptions, cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes + cmpopts.SortSlices(pkg.Less), cmp.Comparer( func(x, y source.LocationSet) bool { xs := x.ToSlice() @@ -186,3 +189,32 @@ func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Pars NewCatalogTester().FromFile(t, fixturePath).WithEnv(env).Expects(expectedPkgs, expectedRelationships).TestParser(t, parser) } + +func AssertPackagesEqual(t *testing.T, a, b pkg.Package) { + t.Helper() + opts := []cmp.Option{ + cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes + cmp.Comparer( + func(x, y source.LocationSet) bool { + xs := x.ToSlice() + ys := y.ToSlice() + + if len(xs) != len(ys) { + return false + } + for i, xe := range xs { + ye := ys[i] + if !DefaultLocationComparer(xe, ye) { + return false + } + } + + return true + }, + ), + } + + if diff := cmp.Diff(a, b, opts...); diff != "" { + t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff) + } +} diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index b87cf5526..cc92798cf 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -3,7 +3,6 @@ package java import ( "crypto" "fmt" - "io" "os" "path" "strings" @@ -13,11 +12,11 @@ import ( "github.com/anchore/syft/syft/artifact" syftFile "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/common" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/source" ) -// integrity check -var _ common.ParserFn = parseJavaArchive +var _ generic.Parser = parseJavaArchive var archiveFormatGlobs = []string{ "**/*.jar", @@ -44,7 +43,7 @@ var javaArchiveHashes = []crypto.Hash{ type archiveParser struct { fileManifest file.ZipFileManifest - virtualPath string + location source.Location archivePath string contentPath string fileInfo archiveFilename @@ -52,8 +51,8 @@ type archiveParser struct { } // parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives. -func parseJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - parser, cleanupFn, err := newJavaArchiveParser(virtualPath, reader, true) +func parseJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + parser, cleanupFn, err := newJavaArchiveParser(reader, true) // note: even on error, we should always run cleanup functions defer cleanupFn() if err != nil { @@ -72,9 +71,9 @@ func uniquePkgKey(p *pkg.Package) string { // newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover // and parse nested archives or ignore them. -func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested bool) (*archiveParser, func(), error) { +func newJavaArchiveParser(reader source.LocationReadCloser, detectNested bool) (*archiveParser, func(), error) { // fetch the last element of the virtual path - virtualElements := strings.Split(virtualPath, ":") + virtualElements := strings.Split(reader.AccessPath(), ":") currentFilepath := virtualElements[len(virtualElements)-1] contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader) @@ -89,7 +88,7 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo return &archiveParser{ fileManifest: fileManifest, - virtualPath: virtualPath, + location: reader.Location, archivePath: archivePath, contentPath: contentPath, fileInfo: newJavaArchiveFilename(currentFilepath), @@ -98,14 +97,14 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo } // parse the loaded archive and return all packages found. -func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error) { - var pkgs []*pkg.Package +func (j *archiveParser) parse() ([]pkg.Package, []artifact.Relationship, error) { + var pkgs []pkg.Package var relationships []artifact.Relationship // find the parent package from the java manifest parentPkg, err := j.discoverMainPackage() if err != nil { - return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.virtualPath, err) + return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.location, err) } // find aux packages from pom.properties/pom.xml and potentially modify the existing parentPkg @@ -128,14 +127,14 @@ func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error) // lastly, add the parent package to the list (assuming the parent exists) if parentPkg != nil { - pkgs = append([]*pkg.Package{parentPkg}, pkgs...) + pkgs = append([]pkg.Package{*parentPkg}, pkgs...) } // add pURLs to all packages found // note: since package information may change after initial creation when parsing multiple locations within the // jar, we wait until the conclusion of the parsing process before synthesizing pURLs. - for _, p := range pkgs { - addPURL(p) + for i, p := range pkgs { + pkgs[i].PURL = packageURL(p.Name, p.Version, p.Metadata.(pkg.JavaMetadata)) } return pkgs, relationships, nil @@ -156,14 +155,14 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { // fetch the manifest file contents, err := file.ContentsFromZip(j.archivePath, manifestMatches...) if err != nil { - return nil, fmt.Errorf("unable to extract java manifests (%s): %w", j.virtualPath, err) + return nil, fmt.Errorf("unable to extract java manifests (%s): %w", j.location, err) } // parse the manifest file into a rich object manifestContents := contents[manifestMatches[0]] manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents)) if err != nil { - log.Warnf("failed to parse java manifest (%s): %+v", j.virtualPath, err) + log.Warnf("failed to parse java manifest (%s): %+v", j.location, err) return nil, nil } @@ -183,10 +182,11 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { Name: selectName(manifest, j.fileInfo), Version: selectVersion(manifest, j.fileInfo), Language: pkg.Java, + Locations: source.NewLocationSet(j.location), Type: j.fileInfo.pkgType(), MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - VirtualPath: j.virtualPath, + VirtualPath: j.location.AccessPath(), Manifest: manifest, ArchiveDigests: digests, }, @@ -197,21 +197,21 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { // parent package, returning all listed Java packages found for each pom // properties discovered and potentially updating the given parentPkg with new // data. -func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]*pkg.Package, error) { +func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]pkg.Package, error) { if parentPkg == nil { return nil, nil } - var pkgs []*pkg.Package + var pkgs []pkg.Package // pom.properties - properties, err := pomPropertiesByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomPropertiesGlob)) + properties, err := pomPropertiesByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(pomPropertiesGlob)) if err != nil { return nil, err } // pom.xml - projects, err := pomProjectByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomXMLGlob)) + projects, err := pomProjectByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(pomXMLGlob)) if err != nil { return nil, err } @@ -222,41 +222,41 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([ pomProject = &proj } - pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.virtualPath) + pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.location) if pkgFromPom != nil { - pkgs = append(pkgs, pkgFromPom) + pkgs = append(pkgs, *pkgFromPom) } } return pkgs, nil } -func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) { +func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { // we know that all java archives are zip formatted files, so we can use the shared zip helper - return discoverPkgsFromZip(j.virtualPath, j.archivePath, j.contentPath, j.fileManifest, parentPkg) + return discoverPkgsFromZip(j.location, j.archivePath, j.contentPath, j.fileManifest, parentPkg) } // discoverPkgsFromZip finds Java archives within Java archives, returning all listed Java packages found and // associating each discovered package to the given parent package. -func discoverPkgsFromZip(virtualPath, archivePath, contentPath string, fileManifest file.ZipFileManifest, parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromZip(location source.Location, archivePath, contentPath string, fileManifest file.ZipFileManifest, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { // search and parse pom.properties files & fetch the contents openers, err := file.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...) if err != nil { return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err) } - return discoverPkgsFromOpeners(virtualPath, openers, parentPkg) + return discoverPkgsFromOpeners(location, openers, parentPkg) } // discoverPkgsFromOpeners finds Java archives within the given files and associates them with the given parent package. -func discoverPkgsFromOpeners(virtualPath string, openers map[string]file.Opener, parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) { - var pkgs []*pkg.Package +func discoverPkgsFromOpeners(location source.Location, openers map[string]file.Opener, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { + var pkgs []pkg.Package var relationships []artifact.Relationship for pathWithinArchive, archiveOpener := range openers { - nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(virtualPath, pathWithinArchive, archiveOpener) + nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(location, pathWithinArchive, archiveOpener) if err != nil { - log.Warnf("unable to discover java packages from opener (%s): %+v", virtualPath, err) + log.WithFields("location", location.AccessPath()).Warnf("unable to discover java packages from opener: %+v", err) continue } @@ -278,7 +278,7 @@ func discoverPkgsFromOpeners(virtualPath string, openers map[string]file.Opener, } // discoverPkgsFromOpener finds Java archives within the given file. -func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener file.Opener) ([]*pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromOpener(location source.Location, pathWithinArchive string, archiveOpener file.Opener) ([]pkg.Package, []artifact.Relationship, error) { archiveReadCloser, err := archiveOpener.Open() if err != nil { return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err) @@ -289,8 +289,13 @@ func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener } }() - nestedPath := fmt.Sprintf("%s:%s", virtualPath, pathWithinArchive) - nestedPkgs, nestedRelationships, err := parseJavaArchive(nestedPath, archiveReadCloser) + nestedPath := fmt.Sprintf("%s:%s", location.AccessPath(), pathWithinArchive) + nestedLocation := source.NewLocationFromCoordinates(location.Coordinates) + nestedLocation.VirtualPath = nestedPath + nestedPkgs, nestedRelationships, err := parseJavaArchive(nil, nil, source.LocationReadCloser{ + Location: nestedLocation, + ReadCloser: archiveReadCloser, + }) if err != nil { return nil, nil, fmt.Errorf("unable to process nested java archive (%s): %w", pathWithinArchive, err) } @@ -298,7 +303,7 @@ func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener return nestedPkgs, nestedRelationships, nil } -func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProperties, error) { +func pomPropertiesByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProperties, error) { contentsOfMavenPropertiesFiles, err := file.ContentsFromZip(archivePath, extractPaths...) if err != nil { return nil, fmt.Errorf("unable to extract maven files: %w", err) @@ -308,7 +313,7 @@ func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []s for filePath, fileContents := range contentsOfMavenPropertiesFiles { pomProperties, err := parsePomProperties(filePath, strings.NewReader(fileContents)) if err != nil { - log.Warnf("failed to parse pom.properties virtualPath=%q path=%q: %+v", virtualPath, filePath, err) + log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.properties: %+v", err) continue } @@ -327,7 +332,7 @@ func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []s return propertiesByParentPath, nil } -func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProject, error) { +func pomProjectByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProject, error) { contentsOfMavenProjectFiles, err := file.ContentsFromZip(archivePath, extractPaths...) if err != nil { return nil, fmt.Errorf("unable to extract maven files: %w", err) @@ -337,7 +342,7 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri for filePath, fileContents := range contentsOfMavenProjectFiles { pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents)) if err != nil { - log.Warnf("failed to parse pom.xml virtualPath=%q path=%q: %+v", virtualPath, filePath, err) + log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.xml: %+v", err) continue } @@ -357,18 +362,19 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri // packagesFromPomProperties processes a single Maven POM properties for a given parent package, returning all listed Java packages found and // associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not. -func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, virtualPath string) *pkg.Package { +func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location source.Location) *pkg.Package { // keep the artifact name within the virtual path if this package does not match the parent package vPathSuffix := "" if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { vPathSuffix += ":" + pomProperties.ArtifactID } - virtualPath += vPathSuffix + virtualPath := location.AccessPath() + vPathSuffix // discovered props = new package p := pkg.Package{ Name: pomProperties.ArtifactID, Version: pomProperties.Version, + Locations: source.NewLocationSet(location), Language: pkg.Java, Type: pomProperties.PkgTypeIndicated(), MetadataType: pkg.JavaMetadataType, @@ -434,17 +440,3 @@ func updateParentPackage(p pkg.Package, parentPkg *pkg.Package) { parentPkg.Metadata = parentMetadata } } - -func addPURL(p *pkg.Package) { - purl := packageURL(*p) - if purl == "" { - return - } - - metadata, ok := p.Metadata.(pkg.JavaMetadata) - if !ok { - return - } - metadata.PURL = purl - p.Metadata = metadata -} diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index 30c67f0e9..480528b02 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -12,12 +12,13 @@ import ( "syscall" "testing" - "github.com/go-test/deep" "github.com/gookit/color" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" ) func generateJavaBuildFixture(t *testing.T, fixturePath string) { @@ -97,6 +98,7 @@ func TestParseJar(t *testing.T) { "example-jenkins-plugin": { Name: "example-jenkins-plugin", Version: "1.0-SNAPSHOT", + PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT", Language: pkg.Java, Type: pkg.JenkinsPluginPkg, MetadataType: pkg.JavaMetadataType, @@ -135,9 +137,7 @@ func TestParseJar(t *testing.T) { GroupID: "io.jenkins.plugins", ArtifactID: "example-jenkins-plugin", Version: "1.0-SNAPSHOT", - Extra: map[string]string{}, }, - PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT", }, }, }, @@ -148,6 +148,7 @@ func TestParseJar(t *testing.T) { "example-java-app-gradle": { Name: "example-java-app-gradle", Version: "0.1.0", + PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, @@ -158,7 +159,6 @@ func TestParseJar(t *testing.T) { "Manifest-Version": "1.0", }, }, - PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0", }, }, }, @@ -173,6 +173,7 @@ func TestParseJar(t *testing.T) { "example-java-app-maven": { Name: "example-java-app-maven", Version: "0.1.0", + PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, @@ -194,14 +195,13 @@ func TestParseJar(t *testing.T) { GroupID: "org.anchore", ArtifactID: "example-java-app-maven", Version: "0.1.0", - Extra: map[string]string{}, }, - PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0", }, }, "joda-time": { Name: "joda-time", Version: "2.9.2", + PURL: "pkg:maven/joda-time/joda-time@2.9.2", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, @@ -214,7 +214,6 @@ func TestParseJar(t *testing.T) { GroupID: "joda-time", ArtifactID: "joda-time", Version: "2.9.2", - Extra: map[string]string{}, }, PomProject: &pkg.PomProject{ Path: "META-INF/maven/joda-time/joda-time/pom.xml", @@ -225,7 +224,6 @@ func TestParseJar(t *testing.T) { Description: "Date and time library to replace JDK date handling", URL: "http://www.joda.org/joda-time/", }, - PURL: "pkg:maven/joda-time/joda-time@2.9.2", }, }, }, @@ -238,20 +236,23 @@ func TestParseJar(t *testing.T) { generateJavaBuildFixture(t, test.fixture) fixture, err := os.Open(test.fixture) - if err != nil { - t.Fatalf("failed to open fixture: %+v", err) + require.NoError(t, err) + + for k := range test.expected { + p := test.expected[k] + p.Locations.Add(source.NewLocation(test.fixture)) + test.expected[k] = p } - parser, cleanupFn, err := newJavaArchiveParser(fixture.Name(), fixture, false) + parser, cleanupFn, err := newJavaArchiveParser(source.LocationReadCloser{ + Location: source.NewLocation(fixture.Name()), + ReadCloser: fixture, + }, false) defer cleanupFn() - if err != nil { - t.Fatalf("should not have filed... %+v", err) - } + require.NoError(t, err) actual, _, err := parser.parse() - if err != nil { - t.Fatalf("failed to parse java archive: %+v", err) - } + require.NoError(t, err) if len(actual) != len(test.expected) { for _, a := range actual { @@ -262,8 +263,9 @@ func TestParseJar(t *testing.T) { var parent *pkg.Package for _, a := range actual { + a := a if strings.Contains(a.Name, "example-") { - parent = a + parent = &a } } @@ -301,13 +303,7 @@ func TestParseJar(t *testing.T) { // write censored data back a.Metadata = metadata - diffs := deep.Equal(&e, a) - if len(diffs) > 0 { - t.Errorf("diffs found for %q", a.Name) - for _, d := range diffs { - t.Errorf("diff: %+v", d) - } - } + pkgtest.AssertPackagesEqual(t, e, a) } }) } @@ -512,14 +508,13 @@ func TestParseNestedJar(t *testing.T) { generateJavaBuildFixture(t, test.fixture) fixture, err := os.Open(test.fixture) - if err != nil { - t.Fatalf("failed to open fixture: %+v", err) - } + require.NoError(t, err) - actual, _, err := parseJavaArchive(fixture.Name(), fixture) - if err != nil { - t.Fatalf("failed to parse java archive: %+v", err) - } + actual, _, err := parseJavaArchive(nil, nil, source.LocationReadCloser{ + Location: source.NewLocation(fixture.Name()), + ReadCloser: fixture, + }) + require.NoError(t, err) expectedNameVersionPairSet := internal.NewStringSet() @@ -536,7 +531,8 @@ func TestParseNestedJar(t *testing.T) { actualNameVersionPairSet := internal.NewStringSet() for _, a := range actual { - key := makeKey(a) + a := a + key := makeKey(&a) actualNameVersionPairSet.Add(key) if !expectedNameVersionPairSet.Contains(key) { t.Errorf("extra package: %s", a) @@ -554,7 +550,8 @@ func TestParseNestedJar(t *testing.T) { } for _, a := range actual { - actualKey := makeKey(a) + a := a + actualKey := makeKey(&a) metadata := a.Metadata.(pkg.JavaMetadata) if actualKey == "spring-boot|0.0.1-SNAPSHOT" { @@ -942,9 +939,26 @@ func Test_newPackageFromMavenData(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, virtualPath) - assert.Equal(t, test.expectedPackage, actualPackage, "new package doesn't match") - assert.Equal(t, test.expectedParent, *test.parent, "parent doesn't match") + locations := source.NewLocationSet(source.NewLocation(virtualPath)) + if test.expectedPackage != nil { + test.expectedPackage.Locations = locations + if test.expectedPackage.Metadata.(pkg.JavaMetadata).Parent != nil { + test.expectedPackage.Metadata.(pkg.JavaMetadata).Parent.Locations = locations + } + } + if test.parent != nil { + test.parent.Locations = locations + } + test.expectedParent.Locations = locations + + actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, source.NewLocation(virtualPath)) + if test.expectedPackage == nil { + require.Nil(t, actualPackage) + } else { + pkgtest.AssertPackagesEqual(t, *test.expectedPackage, *actualPackage) + } + + pkgtest.AssertPackagesEqual(t, test.expectedParent, *test.parent) }) } } diff --git a/syft/pkg/cataloger/java/cataloger.go b/syft/pkg/cataloger/java/cataloger.go index 5adefd492..c132b61c7 100644 --- a/syft/pkg/cataloger/java/cataloger.go +++ b/syft/pkg/cataloger/java/cataloger.go @@ -4,31 +4,30 @@ Package java provides a concrete Cataloger implementation for Java archives (jar package java import ( - "github.com/anchore/syft/syft/pkg/cataloger/common" + "github.com/anchore/syft/syft/pkg/cataloger/generic" ) // NewJavaCataloger returns a new Java archive cataloger object. -func NewJavaCataloger(cfg Config) *common.GenericCataloger { - globParsers := make(map[string]common.ParserFn) - - // java archive formats - for _, pattern := range archiveFormatGlobs { - globParsers[pattern] = parseJavaArchive - } +func NewJavaCataloger(cfg Config) *generic.Cataloger { + c := generic.NewCataloger("java-cataloger"). + WithParserByGlobs(parseJavaArchive, archiveFormatGlobs...) if cfg.SearchIndexedArchives { // java archives wrapped within zip files - for _, pattern := range genericZipGlobs { - globParsers[pattern] = parseZipWrappedJavaArchive - } + c.WithParserByGlobs(parseZipWrappedJavaArchive, genericZipGlobs...) } if cfg.SearchUnindexedArchives { // java archives wrapped within tar files - for _, pattern := range genericTarGlobs { - globParsers[pattern] = parseTarWrappedJavaArchive - } + c.WithParserByGlobs(parseTarWrappedJavaArchive, genericTarGlobs...) } - - return common.NewGenericCataloger(nil, globParsers, "java-cataloger") + return c +} + +// NewJavaPomCataloger returns a cataloger capable of parsing +// dependencies from a pom.xml file. +// Pom files list dependencies that maybe not be locally installed yet. +func NewJavaPomCataloger() *generic.Cataloger { + return generic.NewCataloger("java-pom-cataloger"). + WithParserByGlobs(parserPomXML, pomXMLDirGlob) } diff --git a/syft/pkg/cataloger/java/package_url.go b/syft/pkg/cataloger/java/package_url.go index 9d5cccd3e..b091ac383 100644 --- a/syft/pkg/cataloger/java/package_url.go +++ b/syft/pkg/cataloger/java/package_url.go @@ -7,9 +7,9 @@ import ( ) // PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec) -func packageURL(p pkg.Package) string { - var groupID = p.Name - groupIDs := cpe.GroupIDsFromJavaPackage(p) +func packageURL(name, version string, metadata pkg.JavaMetadata) string { + var groupID = name + groupIDs := cpe.GroupIDsFromJavaMetadata(metadata) if len(groupIDs) > 0 { groupID = groupIDs[0] } @@ -17,8 +17,8 @@ func packageURL(p pkg.Package) string { pURL := packageurl.NewPackageURL( packageurl.TypeMaven, // TODO: should we filter down by package types here? groupID, - p.Name, - p.Version, + name, + version, nil, // TODO: there are probably several qualifiers that can be specified here "") return pURL.ToString() diff --git a/syft/pkg/cataloger/java/package_url_test.go b/syft/pkg/cataloger/java/package_url_test.go index b3785a4df..ac5eef6a0 100644 --- a/syft/pkg/cataloger/java/package_url_test.go +++ b/syft/pkg/cataloger/java/package_url_test.go @@ -41,7 +41,7 @@ func Test_packageURL(t *testing.T) { } for _, tt := range tests { t.Run(tt.expect, func(t *testing.T) { - assert.Equal(t, tt.expect, packageURL(tt.pkg)) + assert.Equal(t, tt.expect, packageURL(tt.pkg.Name, tt.pkg.Version, tt.pkg.Metadata.(pkg.JavaMetadata))) }) } } diff --git a/syft/pkg/cataloger/java/parse_pom_properties.go b/syft/pkg/cataloger/java/parse_pom_properties.go index 576c7d248..248e0de9c 100644 --- a/syft/pkg/cataloger/java/parse_pom_properties.go +++ b/syft/pkg/cataloger/java/parse_pom_properties.go @@ -43,11 +43,6 @@ func parsePomProperties(path string, reader io.Reader) (*pkg.PomProperties, erro return nil, fmt.Errorf("unable to parse pom.properties: %w", err) } - // don't allow for a nil collection, ensure it is empty - if props.Extra == nil { - props.Extra = make(map[string]string) - } - props.Path = path return &props, nil diff --git a/syft/pkg/cataloger/java/parse_pom_properties_test.go b/syft/pkg/cataloger/java/parse_pom_properties_test.go index 63b9e4c0c..0a2abc5b4 100644 --- a/syft/pkg/cataloger/java/parse_pom_properties_test.go +++ b/syft/pkg/cataloger/java/parse_pom_properties_test.go @@ -19,7 +19,6 @@ func TestParseJavaPomProperties(t *testing.T) { GroupID: "org.anchore", ArtifactID: "example-java-app-maven", Version: "0.1.0", - Extra: map[string]string{}, }, }, { @@ -41,7 +40,6 @@ func TestParseJavaPomProperties(t *testing.T) { GroupID: "org.anchore", ArtifactID: "example-java-app-maven", Version: "0.1.0", - Extra: map[string]string{}, }, }, { @@ -50,7 +48,6 @@ func TestParseJavaPomProperties(t *testing.T) { GroupID: "org.anchore", ArtifactID: "example-java:app-maven", Version: "0.1.0:something", - Extra: map[string]string{}, }, }, { @@ -59,7 +56,6 @@ func TestParseJavaPomProperties(t *testing.T) { GroupID: "org.anchore", ArtifactID: "example-java=app-maven", Version: "0.1.0=something", - Extra: map[string]string{}, }, }, } diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index 35c0630b9..eb37dd720 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -13,6 +13,8 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/source" ) const pomXMLGlob = "*pom.xml" @@ -20,15 +22,15 @@ const pomXMLDirGlob = "**/pom.xml" var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]") -func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - pom, err := decodePomXML(content) +func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + pom, err := decodePomXML(reader) if err != nil { return nil, nil, err } - var pkgs []*pkg.Package + var pkgs []pkg.Package for _, dep := range pom.Dependencies { - p := newPackageFromPom(pom, dep) + p := newPackageFromPom(pom, dep, reader.Location) if p.Name == "" { continue } @@ -60,22 +62,28 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject { } } -func newPackageFromPom(pom gopom.Project, dep gopom.Dependency) *pkg.Package { - p := &pkg.Package{ - Name: dep.ArtifactID, - Version: resolveProperty(pom, dep.Version), - Language: pkg.Java, - Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet? - MetadataType: pkg.JavaMetadataType, - FoundBy: javaPomCataloger, - Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{ - GroupID: resolveProperty(pom, dep.GroupID), - }, +func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...source.Location) pkg.Package { + m := pkg.JavaMetadata{ + PomProperties: &pkg.PomProperties{ + GroupID: resolveProperty(pom, dep.GroupID), }, } - p.Metadata = pkg.JavaMetadata{PURL: packageURL(*p)} + name := dep.ArtifactID + version := resolveProperty(pom, dep.Version) + + p := pkg.Package{ + Name: name, + Version: version, + Locations: source.NewLocationSet(locations...), + PURL: packageURL(name, version, m), + Language: pkg.Java, + Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet? + MetadataType: pkg.JavaMetadataType, + Metadata: m, + } + + p.SetID() return p } diff --git a/syft/pkg/cataloger/java/parse_pom_xml_test.go b/syft/pkg/cataloger/java/parse_pom_xml_test.go index bbfc0595b..2e4d7a846 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml_test.go +++ b/syft/pkg/cataloger/java/parse_pom_xml_test.go @@ -8,36 +8,38 @@ import ( "github.com/vifraa/gopom" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" ) func Test_parserPomXML(t *testing.T) { tests := []struct { input string - expected []*pkg.Package + expected []pkg.Package }{ { input: "test-fixtures/pom/pom.xml", - expected: []*pkg.Package{ + expected: []pkg.Package{ { Name: "joda-time", Version: "2.9.2", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/com.joda/joda-time@2.9.2", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/com.joda/joda-time@2.9.2", + PomProperties: &pkg.PomProperties{GroupID: "com.joda"}, }, }, { Name: "junit", Version: "4.12", - FoundBy: "java-pom-cataloger", + PURL: "pkg:maven/junit/junit@4.12", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/junit/junit@4.12", + PomProperties: &pkg.PomProperties{GroupID: "junit"}, }, }, }, @@ -46,13 +48,10 @@ func Test_parserPomXML(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { - fixture, err := os.Open(test.input) - assert.NoError(t, err) - - actual, relationships, err := parserPomXML(fixture.Name(), fixture) - assert.NoError(t, err) - assert.Nil(t, relationships) - assert.Equal(t, test.expected, actual) + for i := range test.expected { + test.expected[i].Locations.Add(source.NewLocation(test.input)) + } + pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil) }) } } @@ -60,119 +59,119 @@ func Test_parserPomXML(t *testing.T) { func Test_parseCommonsTextPomXMLProject(t *testing.T) { tests := []struct { input string - expected []*pkg.Package + expected []pkg.Package }{ { input: "test-fixtures/pom/commons-text.pom.xml", - expected: []*pkg.Package{ + expected: []pkg.Package{ { Name: "commons-lang3", Version: "3.12.0", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", + PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"}, }, }, { Name: "junit-jupiter", Version: "", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.junit.jupiter/junit-jupiter", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.junit.jupiter/junit-jupiter", + PomProperties: &pkg.PomProperties{GroupID: "org.junit.jupiter"}, }, }, { Name: "assertj-core", Version: "3.23.1", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.assertj/assertj-core@3.23.1", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.assertj/assertj-core@3.23.1", + PomProperties: &pkg.PomProperties{GroupID: "org.assertj"}, }, }, { Name: "commons-io", Version: "2.11.0", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/commons-io/commons-io@2.11.0", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/commons-io/commons-io@2.11.0", + PomProperties: &pkg.PomProperties{GroupID: "commons-io"}, }, }, { Name: "mockito-inline", Version: "4.8.0", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0", + PomProperties: &pkg.PomProperties{GroupID: "org.mockito"}, }, }, { Name: "js", Version: "22.0.0.2", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2", + PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"}, }, }, { Name: "js-scriptengine", Version: "22.0.0.2", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2", + PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"}, }, }, { Name: "commons-rng-simple", Version: "1.4", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4", + PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"}, }, }, { Name: "jmh-core", Version: "1.35", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35", + PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"}, }, }, { Name: "jmh-generator-annprocess", Version: "1.35", - FoundBy: javaPomCataloger, + PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35", Language: pkg.Java, Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35", + PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"}, }, }, }, @@ -181,13 +180,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { - fixture, err := os.Open(test.input) - assert.NoError(t, err) - - actual, relationships, err := parserPomXML(fixture.Name(), fixture) - assert.NoError(t, err) - assert.Nil(t, relationships) - assert.Equal(t, test.expected, actual) + for i := range test.expected { + test.expected[i].Locations.Add(source.NewLocation(test.input)) + } + pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil) }) } } diff --git a/syft/pkg/cataloger/java/pom_cataloger.go b/syft/pkg/cataloger/java/pom_cataloger.go deleted file mode 100644 index 753f5d2c3..000000000 --- a/syft/pkg/cataloger/java/pom_cataloger.go +++ /dev/null @@ -1,17 +0,0 @@ -package java - -import "github.com/anchore/syft/syft/pkg/cataloger/common" - -const javaPomCataloger = "java-pom-cataloger" - -// NewJavaPomCataloger returns a cataloger capable of parsing -// dependencies from a pom.xml file. -// Pom files list dependencies that maybe not be locally installed yet. -func NewJavaPomCataloger() *common.GenericCataloger { - globParsers := make(map[string]common.ParserFn) - - // java project files - globParsers[pomXMLDirGlob] = parserPomXML - - return common.NewGenericCataloger(nil, globParsers, javaPomCataloger) -} diff --git a/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go b/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go index fd6091515..882493b01 100644 --- a/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go +++ b/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go @@ -2,17 +2,14 @@ package java import ( "fmt" - "io" "github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/common" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/source" ) -// integrity check -var _ common.ParserFn = parseTarWrappedJavaArchive - var genericTarGlobs = []string{ "**/*.tar", // gzipped tar @@ -45,8 +42,8 @@ var genericTarGlobs = []string{ // note: for compressed tars this is an extremely expensive operation and can lead to performance degradation. This is // due to the fact that there is no central directory header (say as in zip), which means that in order to get // a file listing within the archive you must decompress the entire archive and seek through all of the entries. -func parseTarWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader) +func parseTarWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader) // note: even on error, we should always run cleanup functions defer cleanupFn() if err != nil { @@ -54,14 +51,14 @@ func parseTarWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa } // look for java archives within the tar archive - return discoverPkgsFromTar(virtualPath, archivePath, contentPath) + return discoverPkgsFromTar(reader.Location, archivePath, contentPath) } -func discoverPkgsFromTar(virtualPath, archivePath, contentPath string) ([]*pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromTar(location source.Location, archivePath, contentPath string) ([]pkg.Package, []artifact.Relationship, error) { openers, err := file.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...) if err != nil { return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err) } - return discoverPkgsFromOpeners(virtualPath, openers, nil) + return discoverPkgsFromOpeners(location, openers, nil) } diff --git a/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go b/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go index 707359995..6f40c175d 100644 --- a/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go +++ b/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/source" ) func Test_parseTarWrappedJavaArchive(t *testing.T) { @@ -38,7 +40,10 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actualPkgs, _, err := parseTarWrappedJavaArchive(test.fixture, fixture) + actualPkgs, _, err := parseTarWrappedJavaArchive(nil, nil, source.LocationReadCloser{ + Location: source.NewLocation(test.fixture), + ReadCloser: fixture, + }) require.NoError(t, err) var actualNames []string diff --git a/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go b/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go index 0b8c9b641..dffe5df74 100644 --- a/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go +++ b/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go @@ -2,17 +2,14 @@ package java import ( "fmt" - "io" "github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/common" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/source" ) -// integrity check -var _ common.ParserFn = parseZipWrappedJavaArchive - var genericZipGlobs = []string{ "**/*.zip", } @@ -20,8 +17,8 @@ var genericZipGlobs = []string{ // TODO: when the generic archive cataloger is implemented, this should be removed (https://github.com/anchore/syft/issues/246) // parseZipWrappedJavaArchive is a parser function for java archive contents contained within arbitrary zip files. -func parseZipWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader) +func parseZipWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader) // note: even on error, we should always run cleanup functions defer cleanupFn() if err != nil { @@ -38,5 +35,5 @@ func parseZipWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa } // look for java archives within the zip archive - return discoverPkgsFromZip(virtualPath, archivePath, contentPath, fileManifest, nil) + return discoverPkgsFromZip(reader.Location, archivePath, contentPath, fileManifest, nil) } diff --git a/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go b/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go index 5f062fd25..aa1e51089 100644 --- a/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go +++ b/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/source" ) func Test_parseZipWrappedJavaArchive(t *testing.T) { @@ -31,7 +33,10 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actualPkgs, _, err := parseZipWrappedJavaArchive(test.fixture, fixture) + actualPkgs, _, err := parseZipWrappedJavaArchive(nil, nil, source.LocationReadCloser{ + Location: source.NewLocation(test.fixture), + ReadCloser: fixture, + }) require.NoError(t, err) var actualNames []string diff --git a/syft/pkg/java_metadata.go b/syft/pkg/java_metadata.go index 779845b54..2e134bf11 100644 --- a/syft/pkg/java_metadata.go +++ b/syft/pkg/java_metadata.go @@ -5,11 +5,8 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/linux" ) -var _ urlIdentifier = (*JavaMetadata)(nil) - var jenkinsPluginPomPropertiesGroupIDs = []string{ "io.jenkins.plugins", "org.jenkins.plugins", @@ -25,7 +22,6 @@ type JavaMetadata struct { PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"` PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"` ArchiveDigests []file.Digest `hash:"ignore" json:"digest,omitempty"` - PURL string `hash:"ignore" json:"-"` // pURLs and CPEs are ignored for package IDs Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy). } @@ -36,7 +32,7 @@ type PomProperties struct { GroupID string `mapstructure:"groupId" json:"groupId" cyclonedx:"groupID"` ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"` Version string `mapstructure:"version" json:"version"` - Extra map[string]string `mapstructure:",remain" json:"extraFields"` + Extra map[string]string `mapstructure:",remain" json:"extraFields,omitempty"` } // PomProject represents fields of interest extracted from a Java archive's pom.xml file. See https://maven.apache.org/ref/3.6.3/maven-model/maven.html for more details. @@ -72,8 +68,3 @@ type JavaManifest struct { Main map[string]string `json:"main,omitempty"` NamedSections map[string]map[string]string `json:"namedSections,omitempty"` } - -// PackageURL returns the PURL for the specific Maven package (see https://github.com/package-url/purl-spec) -func (m JavaMetadata) PackageURL(_ *linux.Release) string { - return m.PURL -} diff --git a/syft/pkg/package.go b/syft/pkg/package.go index 4ee75aa64..5bf278af5 100644 --- a/syft/pkg/package.go +++ b/syft/pkg/package.go @@ -6,6 +6,7 @@ package pkg import ( "fmt" "sort" + "strings" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" @@ -21,11 +22,11 @@ type Package struct { FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package Locations source.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package) Licenses []string // licenses discovered with the package metadata - Language Language `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 `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) - MetadataType MetadataType `cyclonedx:"metadataType"` // the shape of the additional data in the "metadata" field + 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 `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) + MetadataType MetadataType `cyclonedx:"metadataType"` // the shape of the additional data in the "metadata" field Metadata interface{} // additional data found while parsing the package source } @@ -82,23 +83,43 @@ func IsValid(p *Package) bool { return p != nil && p.Name != "" } +//nolint:gocognit +func Less(i, j Package) bool { + if i.Name == j.Name { + if i.Version == j.Version { + iLocations := i.Locations.ToSlice() + jLocations := j.Locations.ToSlice() + if i.Type == j.Type { + maxLen := len(iLocations) + if len(jLocations) > maxLen { + maxLen = len(jLocations) + } + for l := 0; l < maxLen; l++ { + if len(iLocations) < l+1 || len(jLocations) < l+1 { + if len(iLocations) == len(jLocations) { + break + } + return len(iLocations) < len(jLocations) + } + if iLocations[l].RealPath == jLocations[l].RealPath { + continue + } + return iLocations[l].RealPath < jLocations[l].RealPath + } + // compare remaining metadata as a final fallback + // note: we cannot guarantee that IDs (which digests the metadata) are stable enough to sort on + // when there are potentially missing elements there is too much reduction in the dimensions to + // lean on ID comparison. The best fallback is to look at the string representation of the metadata. + return strings.Compare(fmt.Sprintf("%#v", i.Metadata), fmt.Sprintf("%#v", j.Metadata)) < 0 + } + return i.Type < j.Type + } + return i.Version < j.Version + } + return i.Name < j.Name +} func Sort(pkgs []Package) { sort.SliceStable(pkgs, func(i, j int) bool { - if pkgs[i].Name == pkgs[j].Name { - if pkgs[i].Version == pkgs[j].Version { - iLocations := pkgs[i].Locations.ToSlice() - jLocations := pkgs[j].Locations.ToSlice() - if pkgs[i].Type == pkgs[j].Type && len(iLocations) > 0 && len(jLocations) > 0 { - if iLocations[0].String() == jLocations[0].String() { - // compare IDs as a final fallback - return pkgs[i].ID() < pkgs[j].ID() - } - return iLocations[0].String() < jLocations[0].String() - } - return pkgs[i].Type < pkgs[j].Type - } - return pkgs[i].Version < pkgs[j].Version - } - return pkgs[i].Name < pkgs[j].Name + return Less(pkgs[i], pkgs[j]) }) } diff --git a/syft/pkg/package_test.go b/syft/pkg/package_test.go index 732f053ad..7a0a33340 100644 --- a/syft/pkg/package_test.go +++ b/syft/pkg/package_test.go @@ -183,12 +183,12 @@ func TestIDUniqueness(t *testing.T) { expectedIDComparison: assert.Equal, }, { - name: "language is reflected", + name: "language is NOT reflected", transform: func(pkg Package) Package { pkg.Language = Rust return pkg }, - expectedIDComparison: assert.NotEqual, + expectedIDComparison: assert.Equal, }, { name: "metadata mutation is reflected", diff --git a/syft/pkg/php_composer_json_metadata.go b/syft/pkg/php_composer_json_metadata.go index 11b529d2b..dc0a6e804 100644 --- a/syft/pkg/php_composer_json_metadata.go +++ b/syft/pkg/php_composer_json_metadata.go @@ -1,14 +1,5 @@ package pkg -import ( - "strings" - - "github.com/anchore/packageurl-go" - "github.com/anchore/syft/syft/linux" -) - -var _ urlIdentifier = (*PhpComposerJSONMetadata)(nil) - // PhpComposerJSONMetadata represents information found from composer v1/v2 "installed.json" files as well as composer.lock files type PhpComposerJSONMetadata struct { Name string `json:"name"` @@ -42,29 +33,3 @@ type PhpComposerAuthors struct { Email string `json:"email,omitempty"` Homepage string `json:"homepage,omitempty"` } - -func (m PhpComposerJSONMetadata) PackageURL(_ *linux.Release) string { - var name, vendor string - fields := strings.Split(m.Name, "/") - switch len(fields) { - case 0: - return "" - case 1: - name = m.Name - case 2: - vendor = fields[0] - name = fields[1] - default: - vendor = fields[0] - name = strings.Join(fields[1:], "-") - } - - pURL := packageurl.NewPackageURL( - packageurl.TypeComposer, - vendor, - name, - m.Version, - nil, - "") - return pURL.ToString() -} diff --git a/syft/pkg/php_composer_json_metadata_test.go b/syft/pkg/php_composer_json_metadata_test.go deleted file mode 100644 index ee38ad871..000000000 --- a/syft/pkg/php_composer_json_metadata_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package pkg - -import ( - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/anchore/syft/syft/linux" -) - -func TestPhpComposerJsonMetadata_pURL(t *testing.T) { - tests := []struct { - name string - distro *linux.Release - metadata PhpComposerJSONMetadata - expected string - }{ - { - name: "with extractable vendor", - metadata: PhpComposerJSONMetadata{ - Name: "ven/name", - Version: "1.0.1", - }, - expected: "pkg:composer/ven/name@1.0.1", - }, - { - name: "name with slashes (invalid)", - metadata: PhpComposerJSONMetadata{ - Name: "ven/name/component", - Version: "1.0.1", - }, - expected: "pkg:composer/ven/name-component@1.0.1", - }, - { - name: "unknown vendor", - metadata: PhpComposerJSONMetadata{ - Name: "name", - Version: "1.0.1", - }, - expected: "pkg:composer/name@1.0.1", - }, - { - name: "ignores distro", - distro: &linux.Release{ - ID: "rhel", - VersionID: "8.4", - }, - metadata: PhpComposerJSONMetadata{ - Name: "ven/name", - Version: "1.0.1", - }, - expected: "pkg:composer/ven/name@1.0.1", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := test.metadata.PackageURL(test.distro) - if actual != test.expected { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(test.expected, actual, true) - t.Errorf("diff: %s", dmp.DiffPrettyText(diffs)) - } - }) - } -} diff --git a/syft/pkg/url.go b/syft/pkg/url.go index bfb689b2e..e54a1dbc3 100644 --- a/syft/pkg/url.go +++ b/syft/pkg/url.go @@ -21,44 +21,6 @@ const ( purlGradlePkgType = "gradle" ) -type urlIdentifier interface { - PackageURL(*linux.Release) string -} - -func URL(p Package, release *linux.Release) string { - if p.Metadata != nil { - if i, ok := p.Metadata.(urlIdentifier); ok { - return i.PackageURL(release) - } - } - - // the remaining cases are primarily reserved for packages without metadata struct instances - - var purlType = p.Type.PackageURLType() - var name = p.Name - var namespace = "" - - switch { - case purlType == "": - purlType = packageurl.TypeGeneric - case p.Type == NpmPkg: - fields := strings.SplitN(p.Name, "/", 2) - if len(fields) > 1 { - namespace = fields[0] - name = fields[1] - } - } - // generate a purl from the package data - return packageurl.NewPackageURL( - purlType, - namespace, - name, - p.Version, - nil, - "", - ).ToString() -} - func PURLQualifiers(vars map[string]string, release *linux.Release) (q packageurl.Qualifiers) { keys := make([]string, 0, len(vars)) for k := range vars { diff --git a/syft/pkg/url_test.go b/syft/pkg/url_test.go deleted file mode 100644 index eaab158d0..000000000 --- a/syft/pkg/url_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package pkg - -import ( - "testing" - - "github.com/scylladb/go-set/strset" - "github.com/sergi/go-diff/diffmatchpatch" - "github.com/stretchr/testify/assert" - - "github.com/anchore/syft/syft/linux" -) - -func TestPackageURL(t *testing.T) { - tests := []struct { - name string - pkg Package - distro *linux.Release - expected string - }{ - { - name: "java", - pkg: Package{ - Name: "bad-name", - Version: "bad-v0.1.0", - Type: JavaPkg, - Metadata: JavaMetadata{ - PomProperties: &PomProperties{}, - PURL: "pkg:maven/g.id/a@v", // assembled by the java cataloger - }, - }, - - expected: "pkg:maven/g.id/a@v", - }, - { - name: "jenkins-plugin", - pkg: Package{ - Name: "bad-name", - Version: "bad-v0.1.0", - Type: JenkinsPluginPkg, - Metadata: JavaMetadata{ - PomProperties: &PomProperties{}, - PURL: "pkg:maven/g.id/a@v", // assembled by the java cataloger - }, - }, - - expected: "pkg:maven/g.id/a@v", - }, - } - - var pkgTypes []string - var expectedTypes = strset.New() - for _, ty := range AllPkgs { - expectedTypes.Add(string(ty)) - } - - // testing microsoft packages is not valid for purl at this time - expectedTypes.Remove(string(KbPkg)) - expectedTypes.Remove(string(PortagePkg)) - expectedTypes.Remove(string(AlpmPkg)) - expectedTypes.Remove(string(ApkPkg)) - expectedTypes.Remove(string(ConanPkg)) - expectedTypes.Remove(string(DartPubPkg)) - expectedTypes.Remove(string(DotnetPkg)) - expectedTypes.Remove(string(DebPkg)) - expectedTypes.Remove(string(GoModulePkg)) - expectedTypes.Remove(string(HackagePkg)) - expectedTypes.Remove(string(BinaryPkg)) - expectedTypes.Remove(string(PhpComposerPkg)) - expectedTypes.Remove(string(PythonPkg)) - expectedTypes.Remove(string(RpmPkg)) - expectedTypes.Remove(string(GemPkg)) - expectedTypes.Remove(string(NpmPkg)) - expectedTypes.Remove(string(RustPkg)) - expectedTypes.Remove(string(CocoapodsPkg)) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if test.pkg.Type != "" && !contains(pkgTypes, string(test.pkg.Type)) { - pkgTypes = append(pkgTypes, string(test.pkg.Type)) - } - actual := URL(test.pkg, test.distro) - if actual != test.expected { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(test.expected, actual, true) - t.Errorf("diff: %s", dmp.DiffPrettyText(diffs)) - } - }) - } - assert.ElementsMatch(t, expectedTypes.List(), pkgTypes, "missing one or more package types to test against (maybe a package type was added?)") -} - -func contains(values []string, val string) bool { - for _, v := range values { - if val == v { - return true - } - } - - return false -} diff --git a/test/cli/json_schema_test.go b/test/cli/json_schema_test.go index 508e7c5fa..6757490a3 100644 --- a/test/cli/json_schema_test.go +++ b/test/cli/json_schema_test.go @@ -73,6 +73,7 @@ func TestJSONSchema(t *testing.T) { } func validateJsonAgainstSchema(t testing.TB, json string) { + t.Helper() fullSchemaPath := path.Join(repoRoot(t), jsonSchemaPath, fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion)) schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath)) documentLoader := gojsonschema.NewStringLoader(json) diff --git a/test/integration/encode_decode_cycle_test.go b/test/integration/encode_decode_cycle_test.go index 5b9e82dfb..110ae5de7 100644 --- a/test/integration/encode_decode_cycle_test.go +++ b/test/integration/encode_decode_cycle_test.go @@ -86,7 +86,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) { s1 := string(by1) s2 := string(by2) if diff := cmp.Diff(s1, s2); diff != "" { - t.Errorf("Encode/Decode mismatch (-want +got):\n%s", diff) + t.Errorf("Encode/Decode mismatch (-want +got) [image %q]:\n%s", image, diff) } } else if !assert.True(t, bytes.Equal(by1, by2)) { dmp := diffmatchpatch.New()