Update java generic cataloger (#1329)

* remove centralize pURL generation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* port java cataloger to new generic cataloger pattern

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove common.GenericCataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update format test fixtures to reflect ID updates

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix package sort instability for encode-decode-encode cycles

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2022-11-09 09:55:54 -05:00 committed by GitHub
parent f3528132a7
commit d7a51a69dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1872 additions and 895 deletions

2
go.mod
View File

@ -7,7 +7,6 @@ require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/acobaugh/osrelease v0.1.0 github.com/acobaugh/osrelease v0.1.0
github.com/adrg/xdg v0.3.3 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-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b 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/docker/docker v20.10.17+incompatible
github.com/google/go-containerregistry v0.11.0 github.com/google/go-containerregistry v0.11.0
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f 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/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/sassoftware/go-rpmutils v0.2.0 github.com/sassoftware/go-rpmutils v0.2.0

4
go.sum
View File

@ -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/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/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/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/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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/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/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/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/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 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/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=

View File

@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // 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. // 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"
) )

View File

@ -10,7 +10,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/alecthomas/jsonschema" "github.com/invopop/jsonschema"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model" syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model"
@ -56,7 +56,7 @@ func main() {
func build() *jsonschema.Schema { func build() *jsonschema.Schema {
reflector := &jsonschema.Reflector{ reflector := &jsonschema.Reflector{
AllowAdditionalProperties: true, AllowAdditionalProperties: true,
TypeNamer: func(r reflect.Type) string { Namer: func(r reflect.Type) string {
return strings.TrimPrefix(r.Name(), "JSON") return strings.TrimPrefix(r.Name(), "JSON")
}, },
} }
@ -88,7 +88,7 @@ func build() *jsonschema.Schema {
} }
for _, name := range metadataNames { for _, name := range metadataNames {
metadataTypes = append(metadataTypes, map[string]string{ metadataTypes = append(metadataTypes, map[string]string{
"$ref": fmt.Sprintf("#/definitions/%s", name), "$ref": fmt.Sprintf("#/$defs/%s", name),
}) })
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{ {
"bomFormat": "CycloneDX", "bomFormat": "CycloneDX",
"specVersion": "1.4", "specVersion": "1.4",
"serialNumber": "urn:uuid:f426926b-4867-4b52-9142-23997f685f2c", "serialNumber": "urn:uuid:cfd602eb-022b-4a61-84d4-50d34612bd84",
"version": 1, "version": 1,
"metadata": { "metadata": {
"timestamp": "2022-10-24T09:54:37-04:00", "timestamp": "2022-11-07T09:11:21-05:00",
"tools": [ "tools": [
{ {
"vendor": "anchore", "vendor": "anchore",
@ -20,7 +20,7 @@
}, },
"components": [ "components": [
{ {
"bom-ref": "e624319940d8d36a", "bom-ref": "1b1d0be59ac59d2c",
"type": "library", "type": "library",
"name": "package-1", "name": "package-1",
"version": "1.0.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", "type": "library",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",

View File

@ -1,10 +1,10 @@
{ {
"bomFormat": "CycloneDX", "bomFormat": "CycloneDX",
"specVersion": "1.4", "specVersion": "1.4",
"serialNumber": "urn:uuid:41bbbcc7-694d-4b07-a678-0afb67dabdf9", "serialNumber": "urn:uuid:ced49687-6384-48fa-a930-ef83a258bf77",
"version": 1, "version": 1,
"metadata": { "metadata": {
"timestamp": "2022-10-24T09:54:37-04:00", "timestamp": "2022-11-07T09:11:21-05:00",
"tools": [ "tools": [
{ {
"vendor": "anchore", "vendor": "anchore",
@ -21,7 +21,7 @@
}, },
"components": [ "components": [
{ {
"bom-ref": "5ffee24fb164cffc", "bom-ref": "66ba429119b8bec6",
"type": "library", "type": "library",
"name": "package-1", "name": "package-1",
"version": "1.0.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", "type": "library",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:19df9583-d8b7-4683-81a6-e57cc8841321" version="1"> <bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2939b822-b9cb-489d-8a8b-4431b755031d" version="1">
<metadata> <metadata>
<timestamp>2022-10-24T09:54:54-04:00</timestamp> <timestamp>2022-11-07T09:11:06-05:00</timestamp>
<tools> <tools>
<tool> <tool>
<vendor>anchore</vendor> <vendor>anchore</vendor>
@ -14,7 +14,7 @@
</component> </component>
</metadata> </metadata>
<components> <components>
<component bom-ref="e624319940d8d36a" type="library"> <component bom-ref="1b1d0be59ac59d2c" type="library">
<name>package-1</name> <name>package-1</name>
<version>1.0.1</version> <version>1.0.1</version>
<licenses> <licenses>
@ -32,7 +32,7 @@
<property name="syft:location:0:path">/some/path/pkg1</property> <property name="syft:location:0:path">/some/path/pkg1</property>
</properties> </properties>
</component> </component>
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=b8645f4ac2a0891e" type="library"> <component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3" type="library">
<name>package-2</name> <name>package-2</name>
<version>2.0.1</version> <version>2.0.1</version>
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe> <cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:5342511c-3580-4cae-b373-20bbf14ba7a3" version="1"> <bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2896b5ce-2016-49e8-a422-239d662846c7" version="1">
<metadata> <metadata>
<timestamp>2022-10-24T09:54:54-04:00</timestamp> <timestamp>2022-11-07T09:11:06-05:00</timestamp>
<tools> <tools>
<tool> <tool>
<vendor>anchore</vendor> <vendor>anchore</vendor>
@ -15,7 +15,7 @@
</component> </component>
</metadata> </metadata>
<components> <components>
<component bom-ref="5ffee24fb164cffc" type="library"> <component bom-ref="66ba429119b8bec6" type="library">
<name>package-1</name> <name>package-1</name>
<version>1.0.1</version> <version>1.0.1</version>
<licenses> <licenses>
@ -34,7 +34,7 @@
<property name="syft:location:0:path">/somefile-1.txt</property> <property name="syft:location:0:path">/somefile-1.txt</property>
</properties> </properties>
</component> </component>
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=8b16570b2b4155c3" type="library"> <component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4" type="library">
<name>package-2</name> <name>package-2</name>
<version>2.0.1</version> <version>2.0.1</version>
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe> <cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>

View File

@ -3,7 +3,7 @@
"name": "/some/path", "name": "/some/path",
"spdxVersion": "SPDX-2.2", "spdxVersion": "SPDX-2.2",
"creationInfo": { "creationInfo": {
"created": "2022-10-24T13:54:19.225779Z", "created": "2022-11-07T14:11:33.934417Z",
"creators": [ "creators": [
"Organization: Anchore, Inc", "Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus" "Tool: syft-v0.42.0-bogus"
@ -11,10 +11,10 @@
"licenseListVersion": "3.18" "licenseListVersion": "3.18"
}, },
"dataLicense": "CC0-1.0", "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": [ "packages": [
{ {
"SPDXID": "SPDXRef-e624319940d8d36a", "SPDXID": "SPDXRef-1b1d0be59ac59d2c",
"name": "package-1", "name": "package-1",
"licenseConcluded": "MIT", "licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -36,7 +36,7 @@
"versionInfo": "1.0.1" "versionInfo": "1.0.1"
}, },
{ {
"SPDXID": "SPDXRef-b8645f4ac2a0891e", "SPDXID": "SPDXRef-db4abfe497c180d3",
"name": "package-2", "name": "package-2",
"licenseConcluded": "NONE", "licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",

View File

@ -3,7 +3,7 @@
"name": "user-image-input", "name": "user-image-input",
"spdxVersion": "SPDX-2.2", "spdxVersion": "SPDX-2.2",
"creationInfo": { "creationInfo": {
"created": "2022-10-24T13:54:19.477217Z", "created": "2022-11-07T14:11:33.941498Z",
"creators": [ "creators": [
"Organization: Anchore, Inc", "Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus" "Tool: syft-v0.42.0-bogus"
@ -11,10 +11,10 @@
"licenseListVersion": "3.18" "licenseListVersion": "3.18"
}, },
"dataLicense": "CC0-1.0", "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": [ "packages": [
{ {
"SPDXID": "SPDXRef-5ffee24fb164cffc", "SPDXID": "SPDXRef-66ba429119b8bec6",
"name": "package-1", "name": "package-1",
"licenseConcluded": "MIT", "licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -36,7 +36,7 @@
"versionInfo": "1.0.1" "versionInfo": "1.0.1"
}, },
{ {
"SPDXID": "SPDXRef-8b16570b2b4155c3", "SPDXID": "SPDXRef-958443e2d9304af4",
"name": "package-2", "name": "package-2",
"licenseConcluded": "NONE", "licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",

View File

@ -3,7 +3,7 @@
"name": "user-image-input", "name": "user-image-input",
"spdxVersion": "SPDX-2.2", "spdxVersion": "SPDX-2.2",
"creationInfo": { "creationInfo": {
"created": "2022-10-24T13:54:19.48428Z", "created": "2022-11-07T14:11:33.947742Z",
"creators": [ "creators": [
"Organization: Anchore, Inc", "Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus" "Tool: syft-v0.42.0-bogus"
@ -11,10 +11,10 @@
"licenseListVersion": "3.18" "licenseListVersion": "3.18"
}, },
"dataLicense": "CC0-1.0", "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": [ "packages": [
{ {
"SPDXID": "SPDXRef-5ffee24fb164cffc", "SPDXID": "SPDXRef-66ba429119b8bec6",
"name": "package-1", "name": "package-1",
"licenseConcluded": "MIT", "licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -44,7 +44,7 @@
"versionInfo": "1.0.1" "versionInfo": "1.0.1"
}, },
{ {
"SPDXID": "SPDXRef-8b16570b2b4155c3", "SPDXID": "SPDXRef-958443e2d9304af4",
"name": "package-2", "name": "package-2",
"licenseConcluded": "NONE", "licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -118,32 +118,32 @@
], ],
"relationships": [ "relationships": [
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c" "relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c"
}, },
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174" "relatedSpdxElement": "SPDXRef-839d99ee67d9d174"
}, },
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6" "relatedSpdxElement": "SPDXRef-9c2f7510199b17f6"
}, },
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-c641caa71518099f" "relatedSpdxElement": "SPDXRef-c641caa71518099f"
}, },
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f" "relatedSpdxElement": "SPDXRef-c6f5b29dca12661f"
}, },
{ {
"spdxElementId": "SPDXRef-5ffee24fb164cffc", "spdxElementId": "SPDXRef-66ba429119b8bec6",
"relationshipType": "CONTAINS", "relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd" "relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd"
} }

View File

@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
DataLicense: CC0-1.0 DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT SPDXID: SPDXRef-DOCUMENT
DocumentName: . 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 LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus Creator: Tool: syft-v0.42.0-bogus
Created: 2022-10-24T13:53:53Z Created: 2022-11-07T14:11:42Z
##### Package: @at-sign ##### Package: @at-sign
PackageName: @at-sign PackageName: @at-sign
SPDXID: SPDXRef-Package---at-sign-fe69bc18c2698fc4 SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
PackageLicenseConcluded: NONE PackageLicenseConcluded: NONE
@ -21,7 +21,7 @@ PackageCopyrightText: NOASSERTION
##### Package: some/slashes ##### Package: some/slashes
PackageName: some/slashes PackageName: some/slashes
SPDXID: SPDXRef-Package--some-slashes-57ed206c09e6e5f4 SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
PackageLicenseConcluded: NONE PackageLicenseConcluded: NONE
@ -31,7 +31,7 @@ PackageCopyrightText: NOASSERTION
##### Package: under_scores ##### Package: under_scores
PackageName: under_scores PackageName: under_scores
SPDXID: SPDXRef-Package--under-scores-8b7505907fdaf19d SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
PackageLicenseConcluded: NONE PackageLicenseConcluded: NONE

View File

@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
DataLicense: CC0-1.0 DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT SPDXID: SPDXRef-DOCUMENT
DocumentName: /some/path 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 LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus Creator: Tool: syft-v0.42.0-bogus
Created: 2022-10-24T13:53:52Z Created: 2022-11-07T14:11:42Z
##### Package: package-2 ##### Package: package-2
PackageName: package-2 PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-b8645f4ac2a0891e SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
PackageVersion: 2.0.1 PackageVersion: 2.0.1
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1 ##### Package: package-1
PackageName: package-1 PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-e624319940d8d36a SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
PackageVersion: 1.0.1 PackageVersion: 1.0.1
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false

View File

@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
DataLicense: CC0-1.0 DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT SPDXID: SPDXRef-DOCUMENT
DocumentName: user-image-input 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 LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus Creator: Tool: syft-v0.42.0-bogus
Created: 2022-10-24T13:53:53Z Created: 2022-11-07T14:11:42Z
##### Package: package-2 ##### Package: package-2
PackageName: package-2 PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-8b16570b2b4155c3 SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
PackageVersion: 2.0.1 PackageVersion: 2.0.1
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1 ##### Package: package-1
PackageName: package-1 PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-5ffee24fb164cffc SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
PackageVersion: 1.0.1 PackageVersion: 1.0.1
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "e624319940d8d36a", "id": "1b1d0be59ac59d2c",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -36,7 +36,7 @@
} }
}, },
{ {
"id": "b8645f4ac2a0891e", "id": "db4abfe497c180d3",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -89,7 +89,7 @@
} }
}, },
"schema": { "schema": {
"version": "5.0.1", "version": "5.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "8373dcf05581b932", "id": "304a5a8e5958a49d",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -31,7 +31,7 @@
} }
}, },
{ {
"id": "c3d4da40f387eec7", "id": "9fd0b9f41034991d",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -185,7 +185,7 @@
} }
}, },
"schema": { "schema": {
"version": "5.0.1", "version": "5.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "5ffee24fb164cffc", "id": "66ba429119b8bec6",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -32,7 +32,7 @@
} }
}, },
{ {
"id": "8b16570b2b4155c3", "id": "958443e2d9304af4",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -112,7 +112,7 @@
} }
}, },
"schema": { "schema": {
"version": "5.0.1", "version": "5.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
} }
} }

View File

@ -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 // we might have binary classified CPE already with the package so we want to append here
p.CPEs = append(p.CPEs, cpe.Generate(p)...) 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 // 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 // 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 // we could not identify the language at either stage and set UnknownLanguage

View File

@ -181,6 +181,10 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
return nil return nil
} }
return GroupIDsFromJavaMetadata(metadata)
}
func GroupIDsFromJavaMetadata(metadata pkg.JavaMetadata) (groupIDs []string) {
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...) groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...) groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
groupIDs = append(groupIDs, groupIDsFromJavaManifest(metadata.Manifest)...) groupIDs = append(groupIDs, groupIDsFromJavaManifest(metadata.Manifest)...)

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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)

View File

@ -1 +0,0 @@
test-fixtures/a-path.txt file contents!

View File

@ -1 +0,0 @@
test-fixtures/another-path.txt file contents!

View File

@ -1 +0,0 @@
test-fixtures/last/path.txt file contents!

View File

@ -34,12 +34,14 @@ type CatalogTester struct {
func NewCatalogTester() *CatalogTester { func NewCatalogTester() *CatalogTester {
return &CatalogTester{ return &CatalogTester{
wantErr: require.NoError, wantErr: require.NoError,
locationComparer: func(x, y source.Location) bool { locationComparer: DefaultLocationComparer,
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
},
} }
} }
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 { func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
t.Helper() t.Helper()
@ -147,6 +149,7 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
p.compareOptions = append(p.compareOptions, p.compareOptions = append(p.compareOptions,
cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes
cmpopts.SortSlices(pkg.Less),
cmp.Comparer( cmp.Comparer(
func(x, y source.LocationSet) bool { func(x, y source.LocationSet) bool {
xs := x.ToSlice() 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) 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)
}
}

View File

@ -3,7 +3,6 @@ package java
import ( import (
"crypto" "crypto"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"strings" "strings"
@ -13,11 +12,11 @@ import (
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
syftFile "github.com/anchore/syft/syft/file" syftFile "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "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 _ generic.Parser = parseJavaArchive
var _ common.ParserFn = parseJavaArchive
var archiveFormatGlobs = []string{ var archiveFormatGlobs = []string{
"**/*.jar", "**/*.jar",
@ -44,7 +43,7 @@ var javaArchiveHashes = []crypto.Hash{
type archiveParser struct { type archiveParser struct {
fileManifest file.ZipFileManifest fileManifest file.ZipFileManifest
virtualPath string location source.Location
archivePath string archivePath string
contentPath string contentPath string
fileInfo archiveFilename 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. // 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) { func parseJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
parser, cleanupFn, err := newJavaArchiveParser(virtualPath, reader, true) parser, cleanupFn, err := newJavaArchiveParser(reader, true)
// note: even on error, we should always run cleanup functions // note: even on error, we should always run cleanup functions
defer cleanupFn() defer cleanupFn()
if err != nil { 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 // newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover
// and parse nested archives or ignore them. // 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 // fetch the last element of the virtual path
virtualElements := strings.Split(virtualPath, ":") virtualElements := strings.Split(reader.AccessPath(), ":")
currentFilepath := virtualElements[len(virtualElements)-1] currentFilepath := virtualElements[len(virtualElements)-1]
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader) contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader)
@ -89,7 +88,7 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo
return &archiveParser{ return &archiveParser{
fileManifest: fileManifest, fileManifest: fileManifest,
virtualPath: virtualPath, location: reader.Location,
archivePath: archivePath, archivePath: archivePath,
contentPath: contentPath, contentPath: contentPath,
fileInfo: newJavaArchiveFilename(currentFilepath), 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. // parse the loaded archive and return all packages found.
func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error) { func (j *archiveParser) parse() ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []*pkg.Package var pkgs []pkg.Package
var relationships []artifact.Relationship var relationships []artifact.Relationship
// find the parent package from the java manifest // find the parent package from the java manifest
parentPkg, err := j.discoverMainPackage() parentPkg, err := j.discoverMainPackage()
if err != nil { 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 // 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) // lastly, add the parent package to the list (assuming the parent exists)
if parentPkg != nil { if parentPkg != nil {
pkgs = append([]*pkg.Package{parentPkg}, pkgs...) pkgs = append([]pkg.Package{*parentPkg}, pkgs...)
} }
// add pURLs to all packages found // add pURLs to all packages found
// note: since package information may change after initial creation when parsing multiple locations within the // 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. // jar, we wait until the conclusion of the parsing process before synthesizing pURLs.
for _, p := range pkgs { for i, p := range pkgs {
addPURL(p) pkgs[i].PURL = packageURL(p.Name, p.Version, p.Metadata.(pkg.JavaMetadata))
} }
return pkgs, relationships, nil return pkgs, relationships, nil
@ -156,14 +155,14 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
// fetch the manifest file // fetch the manifest file
contents, err := file.ContentsFromZip(j.archivePath, manifestMatches...) contents, err := file.ContentsFromZip(j.archivePath, manifestMatches...)
if err != nil { 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 // parse the manifest file into a rich object
manifestContents := contents[manifestMatches[0]] manifestContents := contents[manifestMatches[0]]
manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents)) manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents))
if err != nil { 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 return nil, nil
} }
@ -183,10 +182,11 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
Name: selectName(manifest, j.fileInfo), Name: selectName(manifest, j.fileInfo),
Version: selectVersion(manifest, j.fileInfo), Version: selectVersion(manifest, j.fileInfo),
Language: pkg.Java, Language: pkg.Java,
Locations: source.NewLocationSet(j.location),
Type: j.fileInfo.pkgType(), Type: j.fileInfo.pkgType(),
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
VirtualPath: j.virtualPath, VirtualPath: j.location.AccessPath(),
Manifest: manifest, Manifest: manifest,
ArchiveDigests: digests, ArchiveDigests: digests,
}, },
@ -197,21 +197,21 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
// parent package, returning all listed Java packages found for each pom // parent package, returning all listed Java packages found for each pom
// properties discovered and potentially updating the given parentPkg with new // properties discovered and potentially updating the given parentPkg with new
// data. // data.
func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]*pkg.Package, error) { func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]pkg.Package, error) {
if parentPkg == nil { if parentPkg == nil {
return nil, nil return nil, nil
} }
var pkgs []*pkg.Package var pkgs []pkg.Package
// pom.properties // 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 { if err != nil {
return nil, err return nil, err
} }
// pom.xml // 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 { if err != nil {
return nil, err return nil, err
} }
@ -222,41 +222,41 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([
pomProject = &proj pomProject = &proj
} }
pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.virtualPath) pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.location)
if pkgFromPom != nil { if pkgFromPom != nil {
pkgs = append(pkgs, pkgFromPom) pkgs = append(pkgs, *pkgFromPom)
} }
} }
return pkgs, nil 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 // 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 // discoverPkgsFromZip finds Java archives within Java archives, returning all listed Java packages found and
// associating each discovered package to the given parent package. // 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 // search and parse pom.properties files & fetch the contents
openers, err := file.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...) openers, err := file.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err) 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. // 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) { func discoverPkgsFromOpeners(location source.Location, openers map[string]file.Opener, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []*pkg.Package var pkgs []pkg.Package
var relationships []artifact.Relationship var relationships []artifact.Relationship
for pathWithinArchive, archiveOpener := range openers { for pathWithinArchive, archiveOpener := range openers {
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(virtualPath, pathWithinArchive, archiveOpener) nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(location, pathWithinArchive, archiveOpener)
if err != nil { 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 continue
} }
@ -278,7 +278,7 @@ func discoverPkgsFromOpeners(virtualPath string, openers map[string]file.Opener,
} }
// discoverPkgsFromOpener finds Java archives within the given file. // 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() archiveReadCloser, err := archiveOpener.Open()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err) 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) nestedPath := fmt.Sprintf("%s:%s", location.AccessPath(), pathWithinArchive)
nestedPkgs, nestedRelationships, err := parseJavaArchive(nestedPath, archiveReadCloser) nestedLocation := source.NewLocationFromCoordinates(location.Coordinates)
nestedLocation.VirtualPath = nestedPath
nestedPkgs, nestedRelationships, err := parseJavaArchive(nil, nil, source.LocationReadCloser{
Location: nestedLocation,
ReadCloser: archiveReadCloser,
})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to process nested java archive (%s): %w", pathWithinArchive, err) 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 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...) contentsOfMavenPropertiesFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to extract maven files: %w", err) 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 { for filePath, fileContents := range contentsOfMavenPropertiesFiles {
pomProperties, err := parsePomProperties(filePath, strings.NewReader(fileContents)) pomProperties, err := parsePomProperties(filePath, strings.NewReader(fileContents))
if err != nil { 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 continue
} }
@ -327,7 +332,7 @@ func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []s
return propertiesByParentPath, nil 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...) contentsOfMavenProjectFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to extract maven files: %w", err) 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 { for filePath, fileContents := range contentsOfMavenProjectFiles {
pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents)) pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents))
if err != nil { 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 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 // 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. // 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 // keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := "" vPathSuffix := ""
if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) {
vPathSuffix += ":" + pomProperties.ArtifactID vPathSuffix += ":" + pomProperties.ArtifactID
} }
virtualPath += vPathSuffix virtualPath := location.AccessPath() + vPathSuffix
// discovered props = new package // discovered props = new package
p := pkg.Package{ p := pkg.Package{
Name: pomProperties.ArtifactID, Name: pomProperties.ArtifactID,
Version: pomProperties.Version, Version: pomProperties.Version,
Locations: source.NewLocationSet(location),
Language: pkg.Java, Language: pkg.Java,
Type: pomProperties.PkgTypeIndicated(), Type: pomProperties.PkgTypeIndicated(),
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
@ -434,17 +440,3 @@ func updateParentPackage(p pkg.Package, parentPkg *pkg.Package) {
parentPkg.Metadata = parentMetadata 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
}

View File

@ -12,12 +12,13 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/go-test/deep"
"github.com/gookit/color" "github.com/gookit/color"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg" "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) { func generateJavaBuildFixture(t *testing.T, fixturePath string) {
@ -97,6 +98,7 @@ func TestParseJar(t *testing.T) {
"example-jenkins-plugin": { "example-jenkins-plugin": {
Name: "example-jenkins-plugin", Name: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT", Version: "1.0-SNAPSHOT",
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
@ -135,9 +137,7 @@ func TestParseJar(t *testing.T) {
GroupID: "io.jenkins.plugins", GroupID: "io.jenkins.plugins",
ArtifactID: "example-jenkins-plugin", ArtifactID: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT", 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": { "example-java-app-gradle": {
Name: "example-java-app-gradle", Name: "example-java-app-gradle",
Version: "0.1.0", Version: "0.1.0",
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
@ -158,7 +159,6 @@ func TestParseJar(t *testing.T) {
"Manifest-Version": "1.0", "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": { "example-java-app-maven": {
Name: "example-java-app-maven", Name: "example-java-app-maven",
Version: "0.1.0", Version: "0.1.0",
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
@ -194,14 +195,13 @@ func TestParseJar(t *testing.T) {
GroupID: "org.anchore", GroupID: "org.anchore",
ArtifactID: "example-java-app-maven", ArtifactID: "example-java-app-maven",
Version: "0.1.0", Version: "0.1.0",
Extra: map[string]string{},
}, },
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
}, },
}, },
"joda-time": { "joda-time": {
Name: "joda-time", Name: "joda-time",
Version: "2.9.2", Version: "2.9.2",
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
@ -214,7 +214,6 @@ func TestParseJar(t *testing.T) {
GroupID: "joda-time", GroupID: "joda-time",
ArtifactID: "joda-time", ArtifactID: "joda-time",
Version: "2.9.2", Version: "2.9.2",
Extra: map[string]string{},
}, },
PomProject: &pkg.PomProject{ PomProject: &pkg.PomProject{
Path: "META-INF/maven/joda-time/joda-time/pom.xml", 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", Description: "Date and time library to replace JDK date handling",
URL: "http://www.joda.org/joda-time/", 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) generateJavaBuildFixture(t, test.fixture)
fixture, err := os.Open(test.fixture) fixture, err := os.Open(test.fixture)
if err != nil { require.NoError(t, err)
t.Fatalf("failed to open fixture: %+v", 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() defer cleanupFn()
if err != nil { require.NoError(t, err)
t.Fatalf("should not have filed... %+v", err)
}
actual, _, err := parser.parse() actual, _, err := parser.parse()
if err != nil { require.NoError(t, err)
t.Fatalf("failed to parse java archive: %+v", err)
}
if len(actual) != len(test.expected) { if len(actual) != len(test.expected) {
for _, a := range actual { for _, a := range actual {
@ -262,8 +263,9 @@ func TestParseJar(t *testing.T) {
var parent *pkg.Package var parent *pkg.Package
for _, a := range actual { for _, a := range actual {
a := a
if strings.Contains(a.Name, "example-") { if strings.Contains(a.Name, "example-") {
parent = a parent = &a
} }
} }
@ -301,13 +303,7 @@ func TestParseJar(t *testing.T) {
// write censored data back // write censored data back
a.Metadata = metadata a.Metadata = metadata
diffs := deep.Equal(&e, a) pkgtest.AssertPackagesEqual(t, e, a)
if len(diffs) > 0 {
t.Errorf("diffs found for %q", a.Name)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
} }
}) })
} }
@ -512,14 +508,13 @@ func TestParseNestedJar(t *testing.T) {
generateJavaBuildFixture(t, test.fixture) generateJavaBuildFixture(t, test.fixture)
fixture, err := os.Open(test.fixture) fixture, err := os.Open(test.fixture)
if err != nil { require.NoError(t, err)
t.Fatalf("failed to open fixture: %+v", err)
}
actual, _, err := parseJavaArchive(fixture.Name(), fixture) actual, _, err := parseJavaArchive(nil, nil, source.LocationReadCloser{
if err != nil { Location: source.NewLocation(fixture.Name()),
t.Fatalf("failed to parse java archive: %+v", err) ReadCloser: fixture,
} })
require.NoError(t, err)
expectedNameVersionPairSet := internal.NewStringSet() expectedNameVersionPairSet := internal.NewStringSet()
@ -536,7 +531,8 @@ func TestParseNestedJar(t *testing.T) {
actualNameVersionPairSet := internal.NewStringSet() actualNameVersionPairSet := internal.NewStringSet()
for _, a := range actual { for _, a := range actual {
key := makeKey(a) a := a
key := makeKey(&a)
actualNameVersionPairSet.Add(key) actualNameVersionPairSet.Add(key)
if !expectedNameVersionPairSet.Contains(key) { if !expectedNameVersionPairSet.Contains(key) {
t.Errorf("extra package: %s", a) t.Errorf("extra package: %s", a)
@ -554,7 +550,8 @@ func TestParseNestedJar(t *testing.T) {
} }
for _, a := range actual { for _, a := range actual {
actualKey := makeKey(a) a := a
actualKey := makeKey(&a)
metadata := a.Metadata.(pkg.JavaMetadata) metadata := a.Metadata.(pkg.JavaMetadata)
if actualKey == "spring-boot|0.0.1-SNAPSHOT" { if actualKey == "spring-boot|0.0.1-SNAPSHOT" {
@ -942,9 +939,26 @@ func Test_newPackageFromMavenData(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, virtualPath) locations := source.NewLocationSet(source.NewLocation(virtualPath))
assert.Equal(t, test.expectedPackage, actualPackage, "new package doesn't match") if test.expectedPackage != nil {
assert.Equal(t, test.expectedParent, *test.parent, "parent doesn't match") 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)
}) })
} }
} }

View File

@ -4,31 +4,30 @@ Package java provides a concrete Cataloger implementation for Java archives (jar
package java package java
import ( 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. // NewJavaCataloger returns a new Java archive cataloger object.
func NewJavaCataloger(cfg Config) *common.GenericCataloger { func NewJavaCataloger(cfg Config) *generic.Cataloger {
globParsers := make(map[string]common.ParserFn) c := generic.NewCataloger("java-cataloger").
WithParserByGlobs(parseJavaArchive, archiveFormatGlobs...)
// java archive formats
for _, pattern := range archiveFormatGlobs {
globParsers[pattern] = parseJavaArchive
}
if cfg.SearchIndexedArchives { if cfg.SearchIndexedArchives {
// java archives wrapped within zip files // java archives wrapped within zip files
for _, pattern := range genericZipGlobs { c.WithParserByGlobs(parseZipWrappedJavaArchive, genericZipGlobs...)
globParsers[pattern] = parseZipWrappedJavaArchive
}
} }
if cfg.SearchUnindexedArchives { if cfg.SearchUnindexedArchives {
// java archives wrapped within tar files // java archives wrapped within tar files
for _, pattern := range genericTarGlobs { c.WithParserByGlobs(parseTarWrappedJavaArchive, genericTarGlobs...)
globParsers[pattern] = parseTarWrappedJavaArchive
} }
return c
} }
return common.NewGenericCataloger(nil, globParsers, "java-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() *generic.Cataloger {
return generic.NewCataloger("java-pom-cataloger").
WithParserByGlobs(parserPomXML, pomXMLDirGlob)
} }

View File

@ -7,9 +7,9 @@ import (
) )
// PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec) // PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec)
func packageURL(p pkg.Package) string { func packageURL(name, version string, metadata pkg.JavaMetadata) string {
var groupID = p.Name var groupID = name
groupIDs := cpe.GroupIDsFromJavaPackage(p) groupIDs := cpe.GroupIDsFromJavaMetadata(metadata)
if len(groupIDs) > 0 { if len(groupIDs) > 0 {
groupID = groupIDs[0] groupID = groupIDs[0]
} }
@ -17,8 +17,8 @@ func packageURL(p pkg.Package) string {
pURL := packageurl.NewPackageURL( pURL := packageurl.NewPackageURL(
packageurl.TypeMaven, // TODO: should we filter down by package types here? packageurl.TypeMaven, // TODO: should we filter down by package types here?
groupID, groupID,
p.Name, name,
p.Version, version,
nil, // TODO: there are probably several qualifiers that can be specified here nil, // TODO: there are probably several qualifiers that can be specified here
"") "")
return pURL.ToString() return pURL.ToString()

View File

@ -41,7 +41,7 @@ func Test_packageURL(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.expect, func(t *testing.T) { 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)))
}) })
} }
} }

View File

@ -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) 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 props.Path = path
return &props, nil return &props, nil

View File

@ -19,7 +19,6 @@ func TestParseJavaPomProperties(t *testing.T) {
GroupID: "org.anchore", GroupID: "org.anchore",
ArtifactID: "example-java-app-maven", ArtifactID: "example-java-app-maven",
Version: "0.1.0", Version: "0.1.0",
Extra: map[string]string{},
}, },
}, },
{ {
@ -41,7 +40,6 @@ func TestParseJavaPomProperties(t *testing.T) {
GroupID: "org.anchore", GroupID: "org.anchore",
ArtifactID: "example-java-app-maven", ArtifactID: "example-java-app-maven",
Version: "0.1.0", Version: "0.1.0",
Extra: map[string]string{},
}, },
}, },
{ {
@ -50,7 +48,6 @@ func TestParseJavaPomProperties(t *testing.T) {
GroupID: "org.anchore", GroupID: "org.anchore",
ArtifactID: "example-java:app-maven", ArtifactID: "example-java:app-maven",
Version: "0.1.0:something", Version: "0.1.0:something",
Extra: map[string]string{},
}, },
}, },
{ {
@ -59,7 +56,6 @@ func TestParseJavaPomProperties(t *testing.T) {
GroupID: "org.anchore", GroupID: "org.anchore",
ArtifactID: "example-java=app-maven", ArtifactID: "example-java=app-maven",
Version: "0.1.0=something", Version: "0.1.0=something",
Extra: map[string]string{},
}, },
}, },
} }

View File

@ -13,6 +13,8 @@ import (
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
) )
const pomXMLGlob = "*pom.xml" const pomXMLGlob = "*pom.xml"
@ -20,15 +22,15 @@ const pomXMLDirGlob = "**/pom.xml"
var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]") var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]")
func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pom, err := decodePomXML(content) pom, err := decodePomXML(reader)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
var pkgs []*pkg.Package var pkgs []pkg.Package
for _, dep := range pom.Dependencies { for _, dep := range pom.Dependencies {
p := newPackageFromPom(pom, dep) p := newPackageFromPom(pom, dep, reader.Location)
if p.Name == "" { if p.Name == "" {
continue continue
} }
@ -60,22 +62,28 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
} }
} }
func newPackageFromPom(pom gopom.Project, dep gopom.Dependency) *pkg.Package { func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...source.Location) pkg.Package {
p := &pkg.Package{ m := pkg.JavaMetadata{
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{ PomProperties: &pkg.PomProperties{
GroupID: resolveProperty(pom, dep.GroupID), 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 return p
} }

View File

@ -8,36 +8,38 @@ import (
"github.com/vifraa/gopom" "github.com/vifraa/gopom"
"github.com/anchore/syft/syft/pkg" "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) { func Test_parserPomXML(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expected []*pkg.Package expected []pkg.Package
}{ }{
{ {
input: "test-fixtures/pom/pom.xml", input: "test-fixtures/pom/pom.xml",
expected: []*pkg.Package{ expected: []pkg.Package{
{ {
Name: "joda-time", Name: "joda-time",
Version: "2.9.2", Version: "2.9.2",
FoundBy: javaPomCataloger, PURL: "pkg:maven/com.joda/joda-time@2.9.2",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/com.joda/joda-time@2.9.2", PomProperties: &pkg.PomProperties{GroupID: "com.joda"},
}, },
}, },
{ {
Name: "junit", Name: "junit",
Version: "4.12", Version: "4.12",
FoundBy: "java-pom-cataloger", PURL: "pkg:maven/junit/junit@4.12",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ 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 { for _, test := range tests {
t.Run(test.input, func(t *testing.T) { t.Run(test.input, func(t *testing.T) {
fixture, err := os.Open(test.input) for i := range test.expected {
assert.NoError(t, err) test.expected[i].Locations.Add(source.NewLocation(test.input))
}
actual, relationships, err := parserPomXML(fixture.Name(), fixture) pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil)
assert.NoError(t, err)
assert.Nil(t, relationships)
assert.Equal(t, test.expected, actual)
}) })
} }
} }
@ -60,119 +59,119 @@ func Test_parserPomXML(t *testing.T) {
func Test_parseCommonsTextPomXMLProject(t *testing.T) { func Test_parseCommonsTextPomXMLProject(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expected []*pkg.Package expected []pkg.Package
}{ }{
{ {
input: "test-fixtures/pom/commons-text.pom.xml", input: "test-fixtures/pom/commons-text.pom.xml",
expected: []*pkg.Package{ expected: []pkg.Package{
{ {
Name: "commons-lang3", Name: "commons-lang3",
Version: "3.12.0", Version: "3.12.0",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"},
}, },
}, },
{ {
Name: "junit-jupiter", Name: "junit-jupiter",
Version: "", Version: "",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.junit.jupiter/junit-jupiter",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.junit.jupiter/junit-jupiter", PomProperties: &pkg.PomProperties{GroupID: "org.junit.jupiter"},
}, },
}, },
{ {
Name: "assertj-core", Name: "assertj-core",
Version: "3.23.1", Version: "3.23.1",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.assertj/assertj-core@3.23.1",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.assertj/assertj-core@3.23.1", PomProperties: &pkg.PomProperties{GroupID: "org.assertj"},
}, },
}, },
{ {
Name: "commons-io", Name: "commons-io",
Version: "2.11.0", Version: "2.11.0",
FoundBy: javaPomCataloger, PURL: "pkg:maven/commons-io/commons-io@2.11.0",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/commons-io/commons-io@2.11.0", PomProperties: &pkg.PomProperties{GroupID: "commons-io"},
}, },
}, },
{ {
Name: "mockito-inline", Name: "mockito-inline",
Version: "4.8.0", Version: "4.8.0",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0", PomProperties: &pkg.PomProperties{GroupID: "org.mockito"},
}, },
}, },
{ {
Name: "js", Name: "js",
Version: "22.0.0.2", Version: "22.0.0.2",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2", PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"},
}, },
}, },
{ {
Name: "js-scriptengine", Name: "js-scriptengine",
Version: "22.0.0.2", Version: "22.0.0.2",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ 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", Name: "commons-rng-simple",
Version: "1.4", Version: "1.4",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4", PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"},
}, },
}, },
{ {
Name: "jmh-core", Name: "jmh-core",
Version: "1.35", Version: "1.35",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35", PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"},
}, },
}, },
{ {
Name: "jmh-generator-annprocess", Name: "jmh-generator-annprocess",
Version: "1.35", Version: "1.35",
FoundBy: javaPomCataloger, PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{ 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 { for _, test := range tests {
t.Run(test.input, func(t *testing.T) { t.Run(test.input, func(t *testing.T) {
fixture, err := os.Open(test.input) for i := range test.expected {
assert.NoError(t, err) test.expected[i].Locations.Add(source.NewLocation(test.input))
}
actual, relationships, err := parserPomXML(fixture.Name(), fixture) pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil)
assert.NoError(t, err)
assert.Nil(t, relationships)
assert.Equal(t, test.expected, actual)
}) })
} }
} }

View File

@ -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)
}

View File

@ -2,17 +2,14 @@ package java
import ( import (
"fmt" "fmt"
"io"
"github.com/anchore/syft/internal/file" "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg" "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{ var genericTarGlobs = []string{
"**/*.tar", "**/*.tar",
// gzipped 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 // 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 // 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. // 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) { func parseTarWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader) contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
// note: even on error, we should always run cleanup functions // note: even on error, we should always run cleanup functions
defer cleanupFn() defer cleanupFn()
if err != nil { if err != nil {
@ -54,14 +51,14 @@ func parseTarWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa
} }
// look for java archives within the tar archive // 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...) openers, err := file.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err) return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err)
} }
return discoverPkgsFromOpeners(virtualPath, openers, nil) return discoverPkgsFromOpeners(location, openers, nil)
} }

View File

@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/source"
) )
func Test_parseTarWrappedJavaArchive(t *testing.T) { func Test_parseTarWrappedJavaArchive(t *testing.T) {
@ -38,7 +40,10 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
t.Fatalf("failed to open fixture: %+v", err) 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) require.NoError(t, err)
var actualNames []string var actualNames []string

View File

@ -2,17 +2,14 @@ package java
import ( import (
"fmt" "fmt"
"io"
"github.com/anchore/syft/internal/file" "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg" "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{ var genericZipGlobs = []string{
"**/*.zip", "**/*.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) // 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. // 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) { func parseZipWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader) contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
// note: even on error, we should always run cleanup functions // note: even on error, we should always run cleanup functions
defer cleanupFn() defer cleanupFn()
if err != nil { if err != nil {
@ -38,5 +35,5 @@ func parseZipWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa
} }
// look for java archives within the zip archive // look for java archives within the zip archive
return discoverPkgsFromZip(virtualPath, archivePath, contentPath, fileManifest, nil) return discoverPkgsFromZip(reader.Location, archivePath, contentPath, fileManifest, nil)
} }

View File

@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/source"
) )
func Test_parseZipWrappedJavaArchive(t *testing.T) { func Test_parseZipWrappedJavaArchive(t *testing.T) {
@ -31,7 +33,10 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) {
t.Fatalf("failed to open fixture: %+v", err) 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) require.NoError(t, err)
var actualNames []string var actualNames []string

View File

@ -5,11 +5,8 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
) )
var _ urlIdentifier = (*JavaMetadata)(nil)
var jenkinsPluginPomPropertiesGroupIDs = []string{ var jenkinsPluginPomPropertiesGroupIDs = []string{
"io.jenkins.plugins", "io.jenkins.plugins",
"org.jenkins.plugins", "org.jenkins.plugins",
@ -25,7 +22,6 @@ type JavaMetadata struct {
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"` PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"`
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"` PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
ArchiveDigests []file.Digest `hash:"ignore" json:"digest,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). 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"` GroupID string `mapstructure:"groupId" json:"groupId" cyclonedx:"groupID"`
ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"` ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"`
Version string `mapstructure:"version" json:"version"` 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. // 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"` Main map[string]string `json:"main,omitempty"`
NamedSections map[string]map[string]string `json:"namedSections,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
}

View File

@ -6,6 +6,7 @@ package pkg
import ( import (
"fmt" "fmt"
"sort" "sort"
"strings"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
@ -21,7 +22,7 @@ type Package struct {
FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package 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) 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 Licenses []string // licenses discovered with the package metadata
Language Language `cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc) 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) 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) 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) PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec)
@ -82,23 +83,43 @@ func IsValid(p *Package) bool {
return p != nil && p.Name != "" 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) { func Sort(pkgs []Package) {
sort.SliceStable(pkgs, func(i, j int) bool { sort.SliceStable(pkgs, func(i, j int) bool {
if pkgs[i].Name == pkgs[j].Name { return Less(pkgs[i], pkgs[j])
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
}) })
} }

View File

@ -183,12 +183,12 @@ func TestIDUniqueness(t *testing.T) {
expectedIDComparison: assert.Equal, expectedIDComparison: assert.Equal,
}, },
{ {
name: "language is reflected", name: "language is NOT reflected",
transform: func(pkg Package) Package { transform: func(pkg Package) Package {
pkg.Language = Rust pkg.Language = Rust
return pkg return pkg
}, },
expectedIDComparison: assert.NotEqual, expectedIDComparison: assert.Equal,
}, },
{ {
name: "metadata mutation is reflected", name: "metadata mutation is reflected",

View File

@ -1,14 +1,5 @@
package pkg 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 // PhpComposerJSONMetadata represents information found from composer v1/v2 "installed.json" files as well as composer.lock files
type PhpComposerJSONMetadata struct { type PhpComposerJSONMetadata struct {
Name string `json:"name"` Name string `json:"name"`
@ -42,29 +33,3 @@ type PhpComposerAuthors struct {
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
Homepage string `json:"homepage,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()
}

View File

@ -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))
}
})
}
}

View File

@ -21,44 +21,6 @@ const (
purlGradlePkgType = "gradle" 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) { func PURLQualifiers(vars map[string]string, release *linux.Release) (q packageurl.Qualifiers) {
keys := make([]string, 0, len(vars)) keys := make([]string, 0, len(vars))
for k := range vars { for k := range vars {

View File

@ -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
}

View File

@ -73,6 +73,7 @@ func TestJSONSchema(t *testing.T) {
} }
func validateJsonAgainstSchema(t testing.TB, json string) { func validateJsonAgainstSchema(t testing.TB, json string) {
t.Helper()
fullSchemaPath := path.Join(repoRoot(t), jsonSchemaPath, fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion)) fullSchemaPath := path.Join(repoRoot(t), jsonSchemaPath, fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion))
schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath)) schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath))
documentLoader := gojsonschema.NewStringLoader(json) documentLoader := gojsonschema.NewStringLoader(json)

View File

@ -86,7 +86,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
s1 := string(by1) s1 := string(by1)
s2 := string(by2) s2 := string(by2)
if diff := cmp.Diff(s1, s2); diff != "" { 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)) { } else if !assert.True(t, bytes.Equal(by1, by2)) {
dmp := diffmatchpatch.New() dmp := diffmatchpatch.New()