mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
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:
parent
f3528132a7
commit
d7a51a69dd
2
go.mod
2
go.mod
@ -7,7 +7,6 @@ require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/adrg/xdg v0.3.3
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
@ -57,6 +56,7 @@ require (
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/google/go-containerregistry v0.11.0
|
||||
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/sassoftware/go-rpmutils v0.2.0
|
||||
|
||||
4
go.sum
4
go.sum
@ -223,8 +223,6 @@ github.com/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
|
||||
github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 h1:Lw9q+WyJLFOR+AULchS5/2GKfM+6gOh4szzizdfH3MU=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@ -1220,6 +1218,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
|
||||
github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So=
|
||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
|
||||
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
|
||||
@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "5.0.1"
|
||||
JSONSchemaVersion = "5.1.0"
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/jsonschema"
|
||||
"github.com/invopop/jsonschema"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
@ -56,7 +56,7 @@ func main() {
|
||||
func build() *jsonschema.Schema {
|
||||
reflector := &jsonschema.Reflector{
|
||||
AllowAdditionalProperties: true,
|
||||
TypeNamer: func(r reflect.Type) string {
|
||||
Namer: func(r reflect.Type) string {
|
||||
return strings.TrimPrefix(r.Name(), "JSON")
|
||||
},
|
||||
}
|
||||
@ -88,7 +88,7 @@ func build() *jsonschema.Schema {
|
||||
}
|
||||
for _, name := range metadataNames {
|
||||
metadataTypes = append(metadataTypes, map[string]string{
|
||||
"$ref": fmt.Sprintf("#/definitions/%s", name),
|
||||
"$ref": fmt.Sprintf("#/$defs/%s", name),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
1511
schema/json/schema-5.1.0.json
Normal file
1511
schema/json/schema-5.1.0.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:f426926b-4867-4b52-9142-23997f685f2c",
|
||||
"serialNumber": "urn:uuid:cfd602eb-022b-4a61-84d4-50d34612bd84",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-10-24T09:54:37-04:00",
|
||||
"timestamp": "2022-11-07T09:11:21-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@ -20,7 +20,7 @@
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "e624319940d8d36a",
|
||||
"bom-ref": "1b1d0be59ac59d2c",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@ -57,7 +57,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=b8645f4ac2a0891e",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:41bbbcc7-694d-4b07-a678-0afb67dabdf9",
|
||||
"serialNumber": "urn:uuid:ced49687-6384-48fa-a930-ef83a258bf77",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-10-24T09:54:37-04:00",
|
||||
"timestamp": "2022-11-07T09:11:21-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "5ffee24fb164cffc",
|
||||
"bom-ref": "66ba429119b8bec6",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@ -62,7 +62,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=8b16570b2b4155c3",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?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>
|
||||
<timestamp>2022-10-24T09:54:54-04:00</timestamp>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@ -14,7 +14,7 @@
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="e624319940d8d36a" type="library">
|
||||
<component bom-ref="1b1d0be59ac59d2c" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
@ -32,7 +32,7 @@
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</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>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?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>
|
||||
<timestamp>2022-10-24T09:54:54-04:00</timestamp>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@ -15,7 +15,7 @@
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="5ffee24fb164cffc" type="library">
|
||||
<component bom-ref="66ba429119b8bec6" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
@ -34,7 +34,7 @@
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</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>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-10-24T13:54:19.225779Z",
|
||||
"created": "2022-11-07T14:11:33.934417Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
@ -11,10 +11,10 @@
|
||||
"licenseListVersion": "3.18"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-cd89c782-240b-461e-81a1-63863e02642f",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-938e09e5-4fcc-4e3a-8833-a8b59e923bdc",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-e624319940d8d36a",
|
||||
"SPDXID": "SPDXRef-1b1d0be59ac59d2c",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -36,7 +36,7 @@
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-b8645f4ac2a0891e",
|
||||
"SPDXID": "SPDXRef-db4abfe497c180d3",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-10-24T13:54:19.477217Z",
|
||||
"created": "2022-11-07T14:11:33.941498Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
@ -11,10 +11,10 @@
|
||||
"licenseListVersion": "3.18"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-0b40ce75-7e54-4760-bd9d-4fa833b352dd",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-797c013c-1d76-4d3e-9391-521152bfc87d",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-5ffee24fb164cffc",
|
||||
"SPDXID": "SPDXRef-66ba429119b8bec6",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -36,7 +36,7 @@
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-8b16570b2b4155c3",
|
||||
"SPDXID": "SPDXRef-958443e2d9304af4",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2022-10-24T13:54:19.48428Z",
|
||||
"created": "2022-11-07T14:11:33.947742Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
@ -11,10 +11,10 @@
|
||||
"licenseListVersion": "3.18"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-1a4dc179-1222-463c-b4e9-619131af7e97",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-30117c3a-546f-45b7-a1a8-91f2ecd8d2aa",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-5ffee24fb164cffc",
|
||||
"SPDXID": "SPDXRef-66ba429119b8bec6",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -44,7 +44,7 @@
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-8b16570b2b4155c3",
|
||||
"SPDXID": "SPDXRef-958443e2d9304af4",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -118,32 +118,32 @@
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-c641caa71518099f"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-5ffee24fb164cffc",
|
||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd"
|
||||
}
|
||||
|
||||
@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: .
|
||||
DocumentNamespace: https://anchore.com/syft/dir/4593d944-756e-49aa-af4e-b1a5acf09b97
|
||||
DocumentNamespace: https://anchore.com/syft/dir/a4820ad7-d106-497f-bda7-e694e9ad1050
|
||||
LicenseListVersion: 3.18
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-10-24T13:53:53Z
|
||||
Created: 2022-11-07T14:11:42Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
PackageName: @at-sign
|
||||
SPDXID: SPDXRef-Package---at-sign-fe69bc18c2698fc4
|
||||
SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
@ -21,7 +21,7 @@ PackageCopyrightText: NOASSERTION
|
||||
##### Package: some/slashes
|
||||
|
||||
PackageName: some/slashes
|
||||
SPDXID: SPDXRef-Package--some-slashes-57ed206c09e6e5f4
|
||||
SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
@ -31,7 +31,7 @@ PackageCopyrightText: NOASSERTION
|
||||
##### Package: under_scores
|
||||
|
||||
PackageName: under_scores
|
||||
SPDXID: SPDXRef-Package--under-scores-8b7505907fdaf19d
|
||||
SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
|
||||
@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-a4e58523-00d0-4135-9d21-cf586fbd340c
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-01844fe2-60ea-45fd-bb92-df9f5660330d
|
||||
LicenseListVersion: 3.18
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-10-24T13:53:52Z
|
||||
Created: 2022-11-07T14:11:42Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-b8645f4ac2a0891e
|
||||
SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-e624319940d8d36a
|
||||
SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
||||
@ -2,16 +2,16 @@ SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-639f628a-5f8b-4050-a69e-90c85f0d7837
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-32ec506e-82d8-4da6-991d-e1000e4e562e
|
||||
LicenseListVersion: 3.18
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-10-24T13:53:53Z
|
||||
Created: 2022-11-07T14:11:42Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-8b16570b2b4155c3
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@ -24,7 +24,7 @@ ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-5ffee24fb164cffc
|
||||
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "e624319940d8d36a",
|
||||
"id": "1b1d0be59ac59d2c",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b8645f4ac2a0891e",
|
||||
"id": "db4abfe497c180d3",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "5.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json"
|
||||
"version": "5.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "8373dcf05581b932",
|
||||
"id": "304a5a8e5958a49d",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -31,7 +31,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c3d4da40f387eec7",
|
||||
"id": "9fd0b9f41034991d",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -185,7 +185,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "5.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json"
|
||||
"version": "5.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "5ffee24fb164cffc",
|
||||
"id": "66ba429119b8bec6",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8b16570b2b4155c3",
|
||||
"id": "958443e2d9304af4",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "5.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.0.1.json"
|
||||
"version": "5.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-5.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +67,6 @@ func Catalog(resolver source.FileResolver, release *linux.Release, catalogers ..
|
||||
// we might have binary classified CPE already with the package so we want to append here
|
||||
p.CPEs = append(p.CPEs, cpe.Generate(p)...)
|
||||
|
||||
// generate PURL (note: this is excluded from package ID, so is safe to mutate)
|
||||
if p.PURL == "" {
|
||||
p.PURL = pkg.URL(p, release)
|
||||
}
|
||||
|
||||
// if we were not able to identify the language we have an opportunity
|
||||
// to try and get this value from the PURL. Worst case we assert that
|
||||
// we could not identify the language at either stage and set UnknownLanguage
|
||||
|
||||
@ -181,6 +181,10 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GroupIDsFromJavaMetadata(metadata)
|
||||
}
|
||||
|
||||
func GroupIDsFromJavaMetadata(metadata pkg.JavaMetadata) (groupIDs []string) {
|
||||
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromJavaManifest(metadata.Manifest)...)
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -1 +0,0 @@
|
||||
test-fixtures/a-path.txt file contents!
|
||||
@ -1 +0,0 @@
|
||||
test-fixtures/another-path.txt file contents!
|
||||
@ -1 +0,0 @@
|
||||
test-fixtures/last/path.txt file contents!
|
||||
@ -34,12 +34,14 @@ type CatalogTester struct {
|
||||
func NewCatalogTester() *CatalogTester {
|
||||
return &CatalogTester{
|
||||
wantErr: require.NoError,
|
||||
locationComparer: func(x, y source.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
},
|
||||
locationComparer: DefaultLocationComparer,
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultLocationComparer(x, y source.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
@ -147,6 +149,7 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
||||
|
||||
p.compareOptions = append(p.compareOptions,
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes
|
||||
cmpopts.SortSlices(pkg.Less),
|
||||
cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
@ -186,3 +189,32 @@ func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Pars
|
||||
|
||||
NewCatalogTester().FromFile(t, fixturePath).WithEnv(env).Expects(expectedPkgs, expectedRelationships).TestParser(t, parser)
|
||||
}
|
||||
|
||||
func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
||||
t.Helper()
|
||||
opts := []cmp.Option{
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes
|
||||
cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !DefaultLocationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(a, b, opts...); diff != "" {
|
||||
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package java
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@ -13,11 +12,11 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
syftFile "github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseJavaArchive
|
||||
var _ generic.Parser = parseJavaArchive
|
||||
|
||||
var archiveFormatGlobs = []string{
|
||||
"**/*.jar",
|
||||
@ -44,7 +43,7 @@ var javaArchiveHashes = []crypto.Hash{
|
||||
|
||||
type archiveParser struct {
|
||||
fileManifest file.ZipFileManifest
|
||||
virtualPath string
|
||||
location source.Location
|
||||
archivePath string
|
||||
contentPath string
|
||||
fileInfo archiveFilename
|
||||
@ -52,8 +51,8 @@ type archiveParser struct {
|
||||
}
|
||||
|
||||
// parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives.
|
||||
func parseJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
parser, cleanupFn, err := newJavaArchiveParser(virtualPath, reader, true)
|
||||
func parseJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
parser, cleanupFn, err := newJavaArchiveParser(reader, true)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
@ -72,9 +71,9 @@ func uniquePkgKey(p *pkg.Package) string {
|
||||
|
||||
// newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover
|
||||
// and parse nested archives or ignore them.
|
||||
func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested bool) (*archiveParser, func(), error) {
|
||||
func newJavaArchiveParser(reader source.LocationReadCloser, detectNested bool) (*archiveParser, func(), error) {
|
||||
// fetch the last element of the virtual path
|
||||
virtualElements := strings.Split(virtualPath, ":")
|
||||
virtualElements := strings.Split(reader.AccessPath(), ":")
|
||||
currentFilepath := virtualElements[len(virtualElements)-1]
|
||||
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader)
|
||||
@ -89,7 +88,7 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo
|
||||
|
||||
return &archiveParser{
|
||||
fileManifest: fileManifest,
|
||||
virtualPath: virtualPath,
|
||||
location: reader.Location,
|
||||
archivePath: archivePath,
|
||||
contentPath: contentPath,
|
||||
fileInfo: newJavaArchiveFilename(currentFilepath),
|
||||
@ -98,14 +97,14 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo
|
||||
}
|
||||
|
||||
// parse the loaded archive and return all packages found.
|
||||
func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []*pkg.Package
|
||||
func (j *archiveParser) parse() ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
// find the parent package from the java manifest
|
||||
parentPkg, err := j.discoverMainPackage()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.virtualPath, err)
|
||||
return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.location, err)
|
||||
}
|
||||
|
||||
// find aux packages from pom.properties/pom.xml and potentially modify the existing parentPkg
|
||||
@ -128,14 +127,14 @@ func (j *archiveParser) parse() ([]*pkg.Package, []artifact.Relationship, error)
|
||||
|
||||
// lastly, add the parent package to the list (assuming the parent exists)
|
||||
if parentPkg != nil {
|
||||
pkgs = append([]*pkg.Package{parentPkg}, pkgs...)
|
||||
pkgs = append([]pkg.Package{*parentPkg}, pkgs...)
|
||||
}
|
||||
|
||||
// add pURLs to all packages found
|
||||
// note: since package information may change after initial creation when parsing multiple locations within the
|
||||
// jar, we wait until the conclusion of the parsing process before synthesizing pURLs.
|
||||
for _, p := range pkgs {
|
||||
addPURL(p)
|
||||
for i, p := range pkgs {
|
||||
pkgs[i].PURL = packageURL(p.Name, p.Version, p.Metadata.(pkg.JavaMetadata))
|
||||
}
|
||||
|
||||
return pkgs, relationships, nil
|
||||
@ -156,14 +155,14 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
// fetch the manifest file
|
||||
contents, err := file.ContentsFromZip(j.archivePath, manifestMatches...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract java manifests (%s): %w", j.virtualPath, err)
|
||||
return nil, fmt.Errorf("unable to extract java manifests (%s): %w", j.location, err)
|
||||
}
|
||||
|
||||
// parse the manifest file into a rich object
|
||||
manifestContents := contents[manifestMatches[0]]
|
||||
manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents))
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse java manifest (%s): %+v", j.virtualPath, err)
|
||||
log.Warnf("failed to parse java manifest (%s): %+v", j.location, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -183,10 +182,11 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
Language: pkg.Java,
|
||||
Locations: source.NewLocationSet(j.location),
|
||||
Type: j.fileInfo.pkgType(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: j.virtualPath,
|
||||
VirtualPath: j.location.AccessPath(),
|
||||
Manifest: manifest,
|
||||
ArchiveDigests: digests,
|
||||
},
|
||||
@ -197,21 +197,21 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
// parent package, returning all listed Java packages found for each pom
|
||||
// properties discovered and potentially updating the given parentPkg with new
|
||||
// data.
|
||||
func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]*pkg.Package, error) {
|
||||
func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([]pkg.Package, error) {
|
||||
if parentPkg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var pkgs []*pkg.Package
|
||||
var pkgs []pkg.Package
|
||||
|
||||
// pom.properties
|
||||
properties, err := pomPropertiesByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomPropertiesGlob))
|
||||
properties, err := pomPropertiesByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(pomPropertiesGlob))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pom.xml
|
||||
projects, err := pomProjectByParentPath(j.archivePath, j.virtualPath, j.fileManifest.GlobMatch(pomXMLGlob))
|
||||
projects, err := pomProjectByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(pomXMLGlob))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -222,41 +222,41 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([
|
||||
pomProject = &proj
|
||||
}
|
||||
|
||||
pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.virtualPath)
|
||||
pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.location)
|
||||
if pkgFromPom != nil {
|
||||
pkgs = append(pkgs, pkgFromPom)
|
||||
pkgs = append(pkgs, *pkgFromPom)
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// we know that all java archives are zip formatted files, so we can use the shared zip helper
|
||||
return discoverPkgsFromZip(j.virtualPath, j.archivePath, j.contentPath, j.fileManifest, parentPkg)
|
||||
return discoverPkgsFromZip(j.location, j.archivePath, j.contentPath, j.fileManifest, parentPkg)
|
||||
}
|
||||
|
||||
// discoverPkgsFromZip finds Java archives within Java archives, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package.
|
||||
func discoverPkgsFromZip(virtualPath, archivePath, contentPath string, fileManifest file.ZipFileManifest, parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromZip(location source.Location, archivePath, contentPath string, fileManifest file.ZipFileManifest, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// search and parse pom.properties files & fetch the contents
|
||||
openers, err := file.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err)
|
||||
}
|
||||
|
||||
return discoverPkgsFromOpeners(virtualPath, openers, parentPkg)
|
||||
return discoverPkgsFromOpeners(location, openers, parentPkg)
|
||||
}
|
||||
|
||||
// discoverPkgsFromOpeners finds Java archives within the given files and associates them with the given parent package.
|
||||
func discoverPkgsFromOpeners(virtualPath string, openers map[string]file.Opener, parentPkg *pkg.Package) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []*pkg.Package
|
||||
func discoverPkgsFromOpeners(location source.Location, openers map[string]file.Opener, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
for pathWithinArchive, archiveOpener := range openers {
|
||||
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(virtualPath, pathWithinArchive, archiveOpener)
|
||||
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(location, pathWithinArchive, archiveOpener)
|
||||
if err != nil {
|
||||
log.Warnf("unable to discover java packages from opener (%s): %+v", virtualPath, err)
|
||||
log.WithFields("location", location.AccessPath()).Warnf("unable to discover java packages from opener: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ func discoverPkgsFromOpeners(virtualPath string, openers map[string]file.Opener,
|
||||
}
|
||||
|
||||
// discoverPkgsFromOpener finds Java archives within the given file.
|
||||
func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener file.Opener) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromOpener(location source.Location, pathWithinArchive string, archiveOpener file.Opener) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
archiveReadCloser, err := archiveOpener.Open()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
|
||||
@ -289,8 +289,13 @@ func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener
|
||||
}
|
||||
}()
|
||||
|
||||
nestedPath := fmt.Sprintf("%s:%s", virtualPath, pathWithinArchive)
|
||||
nestedPkgs, nestedRelationships, err := parseJavaArchive(nestedPath, archiveReadCloser)
|
||||
nestedPath := fmt.Sprintf("%s:%s", location.AccessPath(), pathWithinArchive)
|
||||
nestedLocation := source.NewLocationFromCoordinates(location.Coordinates)
|
||||
nestedLocation.VirtualPath = nestedPath
|
||||
nestedPkgs, nestedRelationships, err := parseJavaArchive(nil, nil, source.LocationReadCloser{
|
||||
Location: nestedLocation,
|
||||
ReadCloser: archiveReadCloser,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to process nested java archive (%s): %w", pathWithinArchive, err)
|
||||
}
|
||||
@ -298,7 +303,7 @@ func discoverPkgsFromOpener(virtualPath, pathWithinArchive string, archiveOpener
|
||||
return nestedPkgs, nestedRelationships, nil
|
||||
}
|
||||
|
||||
func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProperties, error) {
|
||||
func pomPropertiesByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProperties, error) {
|
||||
contentsOfMavenPropertiesFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract maven files: %w", err)
|
||||
@ -308,7 +313,7 @@ func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []s
|
||||
for filePath, fileContents := range contentsOfMavenPropertiesFiles {
|
||||
pomProperties, err := parsePomProperties(filePath, strings.NewReader(fileContents))
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse pom.properties virtualPath=%q path=%q: %+v", virtualPath, filePath, err)
|
||||
log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.properties: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -327,7 +332,7 @@ func pomPropertiesByParentPath(archivePath, virtualPath string, extractPaths []s
|
||||
return propertiesByParentPath, nil
|
||||
}
|
||||
|
||||
func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []string) (map[string]pkg.PomProject, error) {
|
||||
func pomProjectByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProject, error) {
|
||||
contentsOfMavenProjectFiles, err := file.ContentsFromZip(archivePath, extractPaths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract maven files: %w", err)
|
||||
@ -337,7 +342,7 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri
|
||||
for filePath, fileContents := range contentsOfMavenProjectFiles {
|
||||
pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents))
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse pom.xml virtualPath=%q path=%q: %+v", virtualPath, filePath, err)
|
||||
log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.xml: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -357,18 +362,19 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri
|
||||
|
||||
// packagesFromPomProperties processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
|
||||
func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, virtualPath string) *pkg.Package {
|
||||
func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location source.Location) *pkg.Package {
|
||||
// keep the artifact name within the virtual path if this package does not match the parent package
|
||||
vPathSuffix := ""
|
||||
if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) {
|
||||
vPathSuffix += ":" + pomProperties.ArtifactID
|
||||
}
|
||||
virtualPath += vPathSuffix
|
||||
virtualPath := location.AccessPath() + vPathSuffix
|
||||
|
||||
// discovered props = new package
|
||||
p := pkg.Package{
|
||||
Name: pomProperties.ArtifactID,
|
||||
Version: pomProperties.Version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Language: pkg.Java,
|
||||
Type: pomProperties.PkgTypeIndicated(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@ -434,17 +440,3 @@ func updateParentPackage(p pkg.Package, parentPkg *pkg.Package) {
|
||||
parentPkg.Metadata = parentMetadata
|
||||
}
|
||||
}
|
||||
|
||||
func addPURL(p *pkg.Package) {
|
||||
purl := packageURL(*p)
|
||||
if purl == "" {
|
||||
return
|
||||
}
|
||||
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
metadata.PURL = purl
|
||||
p.Metadata = metadata
|
||||
}
|
||||
|
||||
@ -12,12 +12,13 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/gookit/color"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func generateJavaBuildFixture(t *testing.T, fixturePath string) {
|
||||
@ -97,6 +98,7 @@ func TestParseJar(t *testing.T) {
|
||||
"example-jenkins-plugin": {
|
||||
Name: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@ -135,9 +137,7 @@ func TestParseJar(t *testing.T) {
|
||||
GroupID: "io.jenkins.plugins",
|
||||
ArtifactID: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -148,6 +148,7 @@ func TestParseJar(t *testing.T) {
|
||||
"example-java-app-gradle": {
|
||||
Name: "example-java-app-gradle",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@ -158,7 +159,6 @@ func TestParseJar(t *testing.T) {
|
||||
"Manifest-Version": "1.0",
|
||||
},
|
||||
},
|
||||
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -173,6 +173,7 @@ func TestParseJar(t *testing.T) {
|
||||
"example-java-app-maven": {
|
||||
Name: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@ -194,14 +195,13 @@ func TestParseJar(t *testing.T) {
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
},
|
||||
},
|
||||
"joda-time": {
|
||||
Name: "joda-time",
|
||||
Version: "2.9.2",
|
||||
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
@ -214,7 +214,6 @@ func TestParseJar(t *testing.T) {
|
||||
GroupID: "joda-time",
|
||||
ArtifactID: "joda-time",
|
||||
Version: "2.9.2",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
PomProject: &pkg.PomProject{
|
||||
Path: "META-INF/maven/joda-time/joda-time/pom.xml",
|
||||
@ -225,7 +224,6 @@ func TestParseJar(t *testing.T) {
|
||||
Description: "Date and time library to replace JDK date handling",
|
||||
URL: "http://www.joda.org/joda-time/",
|
||||
},
|
||||
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -238,20 +236,23 @@ func TestParseJar(t *testing.T) {
|
||||
generateJavaBuildFixture(t, test.fixture)
|
||||
|
||||
fixture, err := os.Open(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
require.NoError(t, err)
|
||||
|
||||
for k := range test.expected {
|
||||
p := test.expected[k]
|
||||
p.Locations.Add(source.NewLocation(test.fixture))
|
||||
test.expected[k] = p
|
||||
}
|
||||
|
||||
parser, cleanupFn, err := newJavaArchiveParser(fixture.Name(), fixture, false)
|
||||
parser, cleanupFn, err := newJavaArchiveParser(source.LocationReadCloser{
|
||||
Location: source.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
}, false)
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
t.Fatalf("should not have filed... %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, _, err := parser.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse java archive: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(actual) != len(test.expected) {
|
||||
for _, a := range actual {
|
||||
@ -262,8 +263,9 @@ func TestParseJar(t *testing.T) {
|
||||
|
||||
var parent *pkg.Package
|
||||
for _, a := range actual {
|
||||
a := a
|
||||
if strings.Contains(a.Name, "example-") {
|
||||
parent = a
|
||||
parent = &a
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,13 +303,7 @@ func TestParseJar(t *testing.T) {
|
||||
// write censored data back
|
||||
a.Metadata = metadata
|
||||
|
||||
diffs := deep.Equal(&e, a)
|
||||
if len(diffs) > 0 {
|
||||
t.Errorf("diffs found for %q", a.Name)
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
pkgtest.AssertPackagesEqual(t, e, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -512,14 +508,13 @@ func TestParseNestedJar(t *testing.T) {
|
||||
generateJavaBuildFixture(t, test.fixture)
|
||||
|
||||
fixture, err := os.Open(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, _, err := parseJavaArchive(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse java archive: %+v", err)
|
||||
}
|
||||
actual, _, err := parseJavaArchive(nil, nil, source.LocationReadCloser{
|
||||
Location: source.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedNameVersionPairSet := internal.NewStringSet()
|
||||
|
||||
@ -536,7 +531,8 @@ func TestParseNestedJar(t *testing.T) {
|
||||
|
||||
actualNameVersionPairSet := internal.NewStringSet()
|
||||
for _, a := range actual {
|
||||
key := makeKey(a)
|
||||
a := a
|
||||
key := makeKey(&a)
|
||||
actualNameVersionPairSet.Add(key)
|
||||
if !expectedNameVersionPairSet.Contains(key) {
|
||||
t.Errorf("extra package: %s", a)
|
||||
@ -554,7 +550,8 @@ func TestParseNestedJar(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
actualKey := makeKey(a)
|
||||
a := a
|
||||
actualKey := makeKey(&a)
|
||||
|
||||
metadata := a.Metadata.(pkg.JavaMetadata)
|
||||
if actualKey == "spring-boot|0.0.1-SNAPSHOT" {
|
||||
@ -942,9 +939,26 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, virtualPath)
|
||||
assert.Equal(t, test.expectedPackage, actualPackage, "new package doesn't match")
|
||||
assert.Equal(t, test.expectedParent, *test.parent, "parent doesn't match")
|
||||
locations := source.NewLocationSet(source.NewLocation(virtualPath))
|
||||
if test.expectedPackage != nil {
|
||||
test.expectedPackage.Locations = locations
|
||||
if test.expectedPackage.Metadata.(pkg.JavaMetadata).Parent != nil {
|
||||
test.expectedPackage.Metadata.(pkg.JavaMetadata).Parent.Locations = locations
|
||||
}
|
||||
}
|
||||
if test.parent != nil {
|
||||
test.parent.Locations = locations
|
||||
}
|
||||
test.expectedParent.Locations = locations
|
||||
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, source.NewLocation(virtualPath))
|
||||
if test.expectedPackage == nil {
|
||||
require.Nil(t, actualPackage)
|
||||
} else {
|
||||
pkgtest.AssertPackagesEqual(t, *test.expectedPackage, *actualPackage)
|
||||
}
|
||||
|
||||
pkgtest.AssertPackagesEqual(t, test.expectedParent, *test.parent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,31 +4,30 @@ Package java provides a concrete Cataloger implementation for Java archives (jar
|
||||
package java
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewJavaCataloger returns a new Java archive cataloger object.
|
||||
func NewJavaCataloger(cfg Config) *common.GenericCataloger {
|
||||
globParsers := make(map[string]common.ParserFn)
|
||||
|
||||
// java archive formats
|
||||
for _, pattern := range archiveFormatGlobs {
|
||||
globParsers[pattern] = parseJavaArchive
|
||||
}
|
||||
func NewJavaCataloger(cfg Config) *generic.Cataloger {
|
||||
c := generic.NewCataloger("java-cataloger").
|
||||
WithParserByGlobs(parseJavaArchive, archiveFormatGlobs...)
|
||||
|
||||
if cfg.SearchIndexedArchives {
|
||||
// java archives wrapped within zip files
|
||||
for _, pattern := range genericZipGlobs {
|
||||
globParsers[pattern] = parseZipWrappedJavaArchive
|
||||
}
|
||||
c.WithParserByGlobs(parseZipWrappedJavaArchive, genericZipGlobs...)
|
||||
}
|
||||
|
||||
if cfg.SearchUnindexedArchives {
|
||||
// java archives wrapped within tar files
|
||||
for _, pattern := range genericTarGlobs {
|
||||
globParsers[pattern] = parseTarWrappedJavaArchive
|
||||
c.WithParserByGlobs(parseTarWrappedJavaArchive, genericTarGlobs...)
|
||||
}
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "java-cataloger")
|
||||
return c
|
||||
}
|
||||
|
||||
// NewJavaPomCataloger returns a cataloger capable of parsing
|
||||
// dependencies from a pom.xml file.
|
||||
// Pom files list dependencies that maybe not be locally installed yet.
|
||||
func NewJavaPomCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("java-pom-cataloger").
|
||||
WithParserByGlobs(parserPomXML, pomXMLDirGlob)
|
||||
}
|
||||
|
||||
@ -7,9 +7,9 @@ import (
|
||||
)
|
||||
|
||||
// PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(p pkg.Package) string {
|
||||
var groupID = p.Name
|
||||
groupIDs := cpe.GroupIDsFromJavaPackage(p)
|
||||
func packageURL(name, version string, metadata pkg.JavaMetadata) string {
|
||||
var groupID = name
|
||||
groupIDs := cpe.GroupIDsFromJavaMetadata(metadata)
|
||||
if len(groupIDs) > 0 {
|
||||
groupID = groupIDs[0]
|
||||
}
|
||||
@ -17,8 +17,8 @@ func packageURL(p pkg.Package) string {
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeMaven, // TODO: should we filter down by package types here?
|
||||
groupID,
|
||||
p.Name,
|
||||
p.Version,
|
||||
name,
|
||||
version,
|
||||
nil, // TODO: there are probably several qualifiers that can be specified here
|
||||
"")
|
||||
return pURL.ToString()
|
||||
|
||||
@ -41,7 +41,7 @@ func Test_packageURL(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expect, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expect, packageURL(tt.pkg))
|
||||
assert.Equal(t, tt.expect, packageURL(tt.pkg.Name, tt.pkg.Version, tt.pkg.Metadata.(pkg.JavaMetadata)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,11 +43,6 @@ func parsePomProperties(path string, reader io.Reader) (*pkg.PomProperties, erro
|
||||
return nil, fmt.Errorf("unable to parse pom.properties: %w", err)
|
||||
}
|
||||
|
||||
// don't allow for a nil collection, ensure it is empty
|
||||
if props.Extra == nil {
|
||||
props.Extra = make(map[string]string)
|
||||
}
|
||||
|
||||
props.Path = path
|
||||
|
||||
return &props, nil
|
||||
|
||||
@ -19,7 +19,6 @@ func TestParseJavaPomProperties(t *testing.T) {
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -41,7 +40,6 @@ func TestParseJavaPomProperties(t *testing.T) {
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -50,7 +48,6 @@ func TestParseJavaPomProperties(t *testing.T) {
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java:app-maven",
|
||||
Version: "0.1.0:something",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -59,7 +56,6 @@ func TestParseJavaPomProperties(t *testing.T) {
|
||||
GroupID: "org.anchore",
|
||||
ArtifactID: "example-java=app-maven",
|
||||
Version: "0.1.0=something",
|
||||
Extra: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import (
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const pomXMLGlob = "*pom.xml"
|
||||
@ -20,15 +22,15 @@ const pomXMLDirGlob = "**/pom.xml"
|
||||
|
||||
var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]")
|
||||
|
||||
func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
pom, err := decodePomXML(content)
|
||||
func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
pom, err := decodePomXML(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var pkgs []*pkg.Package
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range pom.Dependencies {
|
||||
p := newPackageFromPom(pom, dep)
|
||||
p := newPackageFromPom(pom, dep, reader.Location)
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
@ -60,22 +62,28 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
|
||||
}
|
||||
}
|
||||
|
||||
func newPackageFromPom(pom gopom.Project, dep gopom.Dependency) *pkg.Package {
|
||||
p := &pkg.Package{
|
||||
Name: dep.ArtifactID,
|
||||
Version: resolveProperty(pom, dep.Version),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet?
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
FoundBy: javaPomCataloger,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...source.Location) pkg.Package {
|
||||
m := pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: resolveProperty(pom, dep.GroupID),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p.Metadata = pkg.JavaMetadata{PURL: packageURL(*p)}
|
||||
name := dep.ArtifactID
|
||||
version := resolveProperty(pom, dep.Version)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
PURL: packageURL(name, version, m),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet?
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
@ -8,36 +8,38 @@ import (
|
||||
"github.com/vifraa/gopom"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_parserPomXML(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []*pkg.Package
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
input: "test-fixtures/pom/pom.xml",
|
||||
expected: []*pkg.Package{
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "joda-time",
|
||||
Version: "2.9.2",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/com.joda/joda-time@2.9.2",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/com.joda/joda-time@2.9.2",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "com.joda"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "junit",
|
||||
Version: "4.12",
|
||||
FoundBy: "java-pom-cataloger",
|
||||
PURL: "pkg:maven/junit/junit@4.12",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/junit/junit@4.12",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "junit"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -46,13 +48,10 @@ func Test_parserPomXML(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
fixture, err := os.Open(test.input)
|
||||
assert.NoError(t, err)
|
||||
|
||||
actual, relationships, err := parserPomXML(fixture.Name(), fixture)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, relationships)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
for i := range test.expected {
|
||||
test.expected[i].Locations.Add(source.NewLocation(test.input))
|
||||
}
|
||||
pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -60,119 +59,119 @@ func Test_parserPomXML(t *testing.T) {
|
||||
func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []*pkg.Package
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
input: "test-fixtures/pom/commons-text.pom.xml",
|
||||
expected: []*pkg.Package{
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "commons-lang3",
|
||||
Version: "3.12.0",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "junit-jupiter",
|
||||
Version: "",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.junit.jupiter/junit-jupiter",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.junit.jupiter/junit-jupiter",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.junit.jupiter"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "assertj-core",
|
||||
Version: "3.23.1",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.assertj/assertj-core@3.23.1",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.assertj/assertj-core@3.23.1",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.assertj"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commons-io",
|
||||
Version: "2.11.0",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/commons-io/commons-io@2.11.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/commons-io/commons-io@2.11.0",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "commons-io"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "mockito-inline",
|
||||
Version: "4.8.0",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.mockito"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "js",
|
||||
Version: "22.0.0.2",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "js-scriptengine",
|
||||
Version: "22.0.0.2",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commons-rng-simple",
|
||||
Version: "1.4",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "jmh-core",
|
||||
Version: "1.35",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "jmh-generator-annprocess",
|
||||
Version: "1.35",
|
||||
FoundBy: javaPomCataloger,
|
||||
PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35",
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -181,13 +180,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
fixture, err := os.Open(test.input)
|
||||
assert.NoError(t, err)
|
||||
|
||||
actual, relationships, err := parserPomXML(fixture.Name(), fixture)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, relationships)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
for i := range test.expected {
|
||||
test.expected[i].Locations.Add(source.NewLocation(test.input))
|
||||
}
|
||||
pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -2,17 +2,14 @@ package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseTarWrappedJavaArchive
|
||||
|
||||
var genericTarGlobs = []string{
|
||||
"**/*.tar",
|
||||
// gzipped tar
|
||||
@ -45,8 +42,8 @@ var genericTarGlobs = []string{
|
||||
// note: for compressed tars this is an extremely expensive operation and can lead to performance degradation. This is
|
||||
// due to the fact that there is no central directory header (say as in zip), which means that in order to get
|
||||
// a file listing within the archive you must decompress the entire archive and seek through all of the entries.
|
||||
func parseTarWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader)
|
||||
func parseTarWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
@ -54,14 +51,14 @@ func parseTarWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa
|
||||
}
|
||||
|
||||
// look for java archives within the tar archive
|
||||
return discoverPkgsFromTar(virtualPath, archivePath, contentPath)
|
||||
return discoverPkgsFromTar(reader.Location, archivePath, contentPath)
|
||||
}
|
||||
|
||||
func discoverPkgsFromTar(virtualPath, archivePath, contentPath string) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromTar(location source.Location, archivePath, contentPath string) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
openers, err := file.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err)
|
||||
}
|
||||
|
||||
return discoverPkgsFromOpeners(virtualPath, openers, nil)
|
||||
return discoverPkgsFromOpeners(location, openers, nil)
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
@ -38,7 +40,10 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actualPkgs, _, err := parseTarWrappedJavaArchive(test.fixture, fixture)
|
||||
actualPkgs, _, err := parseTarWrappedJavaArchive(nil, nil, source.LocationReadCloser{
|
||||
Location: source.NewLocation(test.fixture),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var actualNames []string
|
||||
|
||||
@ -2,17 +2,14 @@ package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseZipWrappedJavaArchive
|
||||
|
||||
var genericZipGlobs = []string{
|
||||
"**/*.zip",
|
||||
}
|
||||
@ -20,8 +17,8 @@ var genericZipGlobs = []string{
|
||||
// TODO: when the generic archive cataloger is implemented, this should be removed (https://github.com/anchore/syft/issues/246)
|
||||
|
||||
// parseZipWrappedJavaArchive is a parser function for java archive contents contained within arbitrary zip files.
|
||||
func parseZipWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(virtualPath, reader)
|
||||
func parseZipWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
@ -38,5 +35,5 @@ func parseZipWrappedJavaArchive(virtualPath string, reader io.Reader) ([]*pkg.Pa
|
||||
}
|
||||
|
||||
// look for java archives within the zip archive
|
||||
return discoverPkgsFromZip(virtualPath, archivePath, contentPath, fileManifest, nil)
|
||||
return discoverPkgsFromZip(reader.Location, archivePath, contentPath, fileManifest, nil)
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
@ -31,7 +33,10 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actualPkgs, _, err := parseZipWrappedJavaArchive(test.fixture, fixture)
|
||||
actualPkgs, _, err := parseZipWrappedJavaArchive(nil, nil, source.LocationReadCloser{
|
||||
Location: source.NewLocation(test.fixture),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var actualNames []string
|
||||
|
||||
@ -5,11 +5,8 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
|
||||
var _ urlIdentifier = (*JavaMetadata)(nil)
|
||||
|
||||
var jenkinsPluginPomPropertiesGroupIDs = []string{
|
||||
"io.jenkins.plugins",
|
||||
"org.jenkins.plugins",
|
||||
@ -25,7 +22,6 @@ type JavaMetadata struct {
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty" cyclonedx:"-"`
|
||||
PomProject *PomProject `mapstructure:"PomProject" json:"pomProject,omitempty"`
|
||||
ArchiveDigests []file.Digest `hash:"ignore" json:"digest,omitempty"`
|
||||
PURL string `hash:"ignore" json:"-"` // pURLs and CPEs are ignored for package IDs
|
||||
Parent *Package `hash:"ignore" json:"-"` // note: the parent cannot be included in the minimal definition of uniqueness since this field is not reproducible in an encode-decode cycle (is lossy).
|
||||
}
|
||||
|
||||
@ -36,7 +32,7 @@ type PomProperties struct {
|
||||
GroupID string `mapstructure:"groupId" json:"groupId" cyclonedx:"groupID"`
|
||||
ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extraFields"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extraFields,omitempty"`
|
||||
}
|
||||
|
||||
// PomProject represents fields of interest extracted from a Java archive's pom.xml file. See https://maven.apache.org/ref/3.6.3/maven-model/maven.html for more details.
|
||||
@ -72,8 +68,3 @@ type JavaManifest struct {
|
||||
Main map[string]string `json:"main,omitempty"`
|
||||
NamedSections map[string]map[string]string `json:"namedSections,omitempty"`
|
||||
}
|
||||
|
||||
// PackageURL returns the PURL for the specific Maven package (see https://github.com/package-url/purl-spec)
|
||||
func (m JavaMetadata) PackageURL(_ *linux.Release) string {
|
||||
return m.PURL
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ package pkg
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"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
|
||||
Locations source.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package)
|
||||
Licenses []string // licenses discovered with the package metadata
|
||||
Language Language `cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc)
|
||||
Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc)
|
||||
Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc)
|
||||
CPEs []CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields)
|
||||
PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec)
|
||||
@ -82,23 +83,43 @@ func IsValid(p *Package) bool {
|
||||
return p != nil && p.Name != ""
|
||||
}
|
||||
|
||||
//nolint:gocognit
|
||||
func Less(i, j Package) bool {
|
||||
if i.Name == j.Name {
|
||||
if i.Version == j.Version {
|
||||
iLocations := i.Locations.ToSlice()
|
||||
jLocations := j.Locations.ToSlice()
|
||||
if i.Type == j.Type {
|
||||
maxLen := len(iLocations)
|
||||
if len(jLocations) > maxLen {
|
||||
maxLen = len(jLocations)
|
||||
}
|
||||
for l := 0; l < maxLen; l++ {
|
||||
if len(iLocations) < l+1 || len(jLocations) < l+1 {
|
||||
if len(iLocations) == len(jLocations) {
|
||||
break
|
||||
}
|
||||
return len(iLocations) < len(jLocations)
|
||||
}
|
||||
if iLocations[l].RealPath == jLocations[l].RealPath {
|
||||
continue
|
||||
}
|
||||
return iLocations[l].RealPath < jLocations[l].RealPath
|
||||
}
|
||||
// compare remaining metadata as a final fallback
|
||||
// note: we cannot guarantee that IDs (which digests the metadata) are stable enough to sort on
|
||||
// when there are potentially missing elements there is too much reduction in the dimensions to
|
||||
// lean on ID comparison. The best fallback is to look at the string representation of the metadata.
|
||||
return strings.Compare(fmt.Sprintf("%#v", i.Metadata), fmt.Sprintf("%#v", j.Metadata)) < 0
|
||||
}
|
||||
return i.Type < j.Type
|
||||
}
|
||||
return i.Version < j.Version
|
||||
}
|
||||
return i.Name < j.Name
|
||||
}
|
||||
func Sort(pkgs []Package) {
|
||||
sort.SliceStable(pkgs, func(i, j int) bool {
|
||||
if pkgs[i].Name == pkgs[j].Name {
|
||||
if pkgs[i].Version == pkgs[j].Version {
|
||||
iLocations := pkgs[i].Locations.ToSlice()
|
||||
jLocations := pkgs[j].Locations.ToSlice()
|
||||
if pkgs[i].Type == pkgs[j].Type && len(iLocations) > 0 && len(jLocations) > 0 {
|
||||
if iLocations[0].String() == jLocations[0].String() {
|
||||
// compare IDs as a final fallback
|
||||
return pkgs[i].ID() < pkgs[j].ID()
|
||||
}
|
||||
return iLocations[0].String() < jLocations[0].String()
|
||||
}
|
||||
return pkgs[i].Type < pkgs[j].Type
|
||||
}
|
||||
return pkgs[i].Version < pkgs[j].Version
|
||||
}
|
||||
return pkgs[i].Name < pkgs[j].Name
|
||||
return Less(pkgs[i], pkgs[j])
|
||||
})
|
||||
}
|
||||
|
||||
@ -183,12 +183,12 @@ func TestIDUniqueness(t *testing.T) {
|
||||
expectedIDComparison: assert.Equal,
|
||||
},
|
||||
{
|
||||
name: "language is reflected",
|
||||
name: "language is NOT reflected",
|
||||
transform: func(pkg Package) Package {
|
||||
pkg.Language = Rust
|
||||
return pkg
|
||||
},
|
||||
expectedIDComparison: assert.NotEqual,
|
||||
expectedIDComparison: assert.Equal,
|
||||
},
|
||||
{
|
||||
name: "metadata mutation is reflected",
|
||||
|
||||
@ -1,14 +1,5 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
|
||||
var _ urlIdentifier = (*PhpComposerJSONMetadata)(nil)
|
||||
|
||||
// PhpComposerJSONMetadata represents information found from composer v1/v2 "installed.json" files as well as composer.lock files
|
||||
type PhpComposerJSONMetadata struct {
|
||||
Name string `json:"name"`
|
||||
@ -42,29 +33,3 @@ type PhpComposerAuthors struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
}
|
||||
|
||||
func (m PhpComposerJSONMetadata) PackageURL(_ *linux.Release) string {
|
||||
var name, vendor string
|
||||
fields := strings.Split(m.Name, "/")
|
||||
switch len(fields) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
name = m.Name
|
||||
case 2:
|
||||
vendor = fields[0]
|
||||
name = fields[1]
|
||||
default:
|
||||
vendor = fields[0]
|
||||
name = strings.Join(fields[1:], "-")
|
||||
}
|
||||
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeComposer,
|
||||
vendor,
|
||||
name,
|
||||
m.Version,
|
||||
nil,
|
||||
"")
|
||||
return pURL.ToString()
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -21,44 +21,6 @@ const (
|
||||
purlGradlePkgType = "gradle"
|
||||
)
|
||||
|
||||
type urlIdentifier interface {
|
||||
PackageURL(*linux.Release) string
|
||||
}
|
||||
|
||||
func URL(p Package, release *linux.Release) string {
|
||||
if p.Metadata != nil {
|
||||
if i, ok := p.Metadata.(urlIdentifier); ok {
|
||||
return i.PackageURL(release)
|
||||
}
|
||||
}
|
||||
|
||||
// the remaining cases are primarily reserved for packages without metadata struct instances
|
||||
|
||||
var purlType = p.Type.PackageURLType()
|
||||
var name = p.Name
|
||||
var namespace = ""
|
||||
|
||||
switch {
|
||||
case purlType == "":
|
||||
purlType = packageurl.TypeGeneric
|
||||
case p.Type == NpmPkg:
|
||||
fields := strings.SplitN(p.Name, "/", 2)
|
||||
if len(fields) > 1 {
|
||||
namespace = fields[0]
|
||||
name = fields[1]
|
||||
}
|
||||
}
|
||||
// generate a purl from the package data
|
||||
return packageurl.NewPackageURL(
|
||||
purlType,
|
||||
namespace,
|
||||
name,
|
||||
p.Version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func PURLQualifiers(vars map[string]string, release *linux.Release) (q packageurl.Qualifiers) {
|
||||
keys := make([]string, 0, len(vars))
|
||||
for k := range vars {
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -73,6 +73,7 @@ func TestJSONSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func validateJsonAgainstSchema(t testing.TB, json string) {
|
||||
t.Helper()
|
||||
fullSchemaPath := path.Join(repoRoot(t), jsonSchemaPath, fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion))
|
||||
schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath))
|
||||
documentLoader := gojsonschema.NewStringLoader(json)
|
||||
|
||||
@ -86,7 +86,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
s1 := string(by1)
|
||||
s2 := string(by2)
|
||||
if diff := cmp.Diff(s1, s2); diff != "" {
|
||||
t.Errorf("Encode/Decode mismatch (-want +got):\n%s", diff)
|
||||
t.Errorf("Encode/Decode mismatch (-want +got) [image %q]:\n%s", image, diff)
|
||||
}
|
||||
} else if !assert.True(t, bytes.Equal(by1, by2)) {
|
||||
dmp := diffmatchpatch.New()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user