diff --git a/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryPresenter.golden b/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryPresenter.golden index fec66e759..c0eb44cd3 100644 --- a/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryPresenter.golden +++ b/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryPresenter.golden @@ -3,7 +3,7 @@ "name": "/some/path", "spdxVersion": "SPDX-2.2", "creationInfo": { - "created": "2021-12-15T23:56:14.459753Z", + "created": "2021-12-20T19:12:47.869816Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-[not provided]" @@ -11,10 +11,10 @@ "licenseListVersion": "3.15" }, "dataLicense": "CC0-1.0", - "documentNamespace": "https://anchore.com/syft/dir/some/path-7ed51d00-2c50-4c6d-aedc-271ed41009cb", + "documentNamespace": "https://anchore.com/syft/dir/some/path-4b896ded-7852-4e31-b764-136b53bdf346", "packages": [ { - "SPDXID": "SPDXRef-96e6e51fe8ba6d8b", + "SPDXID": "SPDXRef-1d97af55efe9512f", "name": "package-1", "licenseConcluded": "MIT", "downloadLocation": "NOASSERTION", diff --git a/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImagePresenter.golden b/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImagePresenter.golden index ce2afcc71..e44ef6cf1 100644 --- a/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImagePresenter.golden +++ b/internal/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImagePresenter.golden @@ -3,7 +3,7 @@ "name": "user-image-input", "spdxVersion": "SPDX-2.2", "creationInfo": { - "created": "2021-12-15T23:56:14.468453Z", + "created": "2021-12-20T19:13:07.647486Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-[not provided]" @@ -11,10 +11,10 @@ "licenseListVersion": "3.15" }, "dataLicense": "CC0-1.0", - "documentNamespace": "https://anchore.com/syft/image/user-image-input-f7c12e3a-8390-4f0d-a4a9-7d756e7e8d7d", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-174da656-1824-4bd3-8604-28919f8a65bc", "packages": [ { - "SPDXID": "SPDXRef-b8995af4e6171091", + "SPDXID": "SPDXRef-d16127444133b5c1", "name": "package-1", "licenseConcluded": "MIT", "downloadLocation": "NOASSERTION", @@ -36,7 +36,7 @@ "versionInfo": "1.0.1" }, { - "SPDXID": "SPDXRef-73f796c846875b9e", + "SPDXID": "SPDXRef-24907357f3705420", "name": "package-2", "licenseConcluded": "NONE", "downloadLocation": "NOASSERTION", diff --git a/internal/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/internal/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index c1b1d2b79..c483fa49b 100644 Binary files a/internal/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden and b/internal/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden differ diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryPresenter.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryPresenter.golden index d00832f45..f95a016a6 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryPresenter.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryPresenter.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "96e6e51fe8ba6d8b", + "id": "1d97af55efe9512f", "name": "package-1", "version": "1.0.1", "type": "python", diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 131d3bed4..147b6abb4 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "2a5c2dadd6f80c07", + "id": "d9a7c58726ab4bef", "name": "package-1", "version": "1.0.1", "type": "python", diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestImagePresenter.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestImagePresenter.golden index 724299861..4eb879a99 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestImagePresenter.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestImagePresenter.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "b8995af4e6171091", + "id": "d16127444133b5c1", "name": "package-1", "version": "1.0.1", "type": "python", @@ -9,7 +9,7 @@ "locations": [ { "path": "/somefile-1.txt", - "layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59" + "layerID": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab" } ], "licenses": [ @@ -32,7 +32,7 @@ } }, { - "id": "73f796c846875b9e", + "id": "24907357f3705420", "name": "package-2", "version": "2.0.1", "type": "deb", @@ -40,7 +40,7 @@ "locations": [ { "path": "/somefile-2.txt", - "layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec" + "layerID": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67" } ], "licenses": [], @@ -67,7 +67,7 @@ "type": "image", "target": { "userInput": "user-image-input", - "imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca", + "imageID": "sha256:9624b89704d23fa5f61b427379d172dac91dc7a508c4d7dea7aed0e04a4cf39e", "manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "tags": [ @@ -77,17 +77,17 @@ "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59", + "digest": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab", "size": 22 }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec", + "digest": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67", "size": 16 } ], - "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19", - "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==", + "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1Njo5NjI0Yjg5NzA0ZDIzZmE1ZjYxYjQyNzM3OWQxNzJkYWM5MWRjN2E1MDhjNGQ3ZGVhN2FlZDBlMDRhNGNmMzllIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjoxNmU2NDU0MWYyZGRmNTlhOTAzOTFjZTdiYjhhZjkwMzEzZjdkMzczZjIxMDVkODhmM2QzMjY3YjcyZTBlYmFiIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmRlNmMyMzVmNzZlYTI0Yzg1MDNlYzA4ODkxNDQ1YjVkNmE4YmRmODI0OTExN2VkOGQ4YjBiNmZiM2ViZTRmNjcifV19", + "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTItMDFUMTI6MTM6NDMuNDAxMzkxNFoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMi0wMVQxMjoxMzo0My4zNDM2MzQyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMi0wMVQxMjoxMzo0My40MDEzOTE0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6MTZlNjQ1NDFmMmRkZjU5YTkwMzkxY2U3YmI4YWY5MDMxM2Y3ZDM3M2YyMTA1ZDg4ZjNkMzI2N2I3MmUwZWJhYiIsInNoYTI1NjpkZTZjMjM1Zjc2ZWEyNGM4NTAzZWMwODg5MTQ0NWI1ZDZhOGJkZjgyNDkxMTdlZDhkOGIwYjZmYjNlYmU0ZjY3Il19fQ==", "repoDigests": [] } }, diff --git a/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index c1b1d2b79..c483fa49b 100644 Binary files a/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden and b/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden differ diff --git a/syft/pkg/cataloger/package_url_test.go b/syft/pkg/cataloger/package_url_test.go index 61ae73f6f..e0faf8c5d 100644 --- a/syft/pkg/cataloger/package_url_test.go +++ b/syft/pkg/cataloger/package_url_test.go @@ -10,11 +10,13 @@ import ( func TestPackageURL(t *testing.T) { tests := []struct { + name string pkg pkg.Package distro *distro.Distro expected string }{ { + name: "golang", pkg: pkg.Package{ Name: "github.com/anchore/syft", Version: "v0.1.0", @@ -23,14 +25,38 @@ func TestPackageURL(t *testing.T) { expected: "pkg:golang/github.com/anchore/syft@v0.1.0", }, { + name: "pip with vcs url", pkg: pkg.Package{ - Name: "name", - Version: "v0.1.0", + Name: "bad-name", + Version: "bad-v0.1.0", Type: pkg.PythonPkg, + Metadata: pkg.PythonPackageMetadata{ + Name: "name", + Version: "v0.1.0", + DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{ + VCS: "git", + URL: "https://github.com/test/test.git", + CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + }, + }, + expected: "pkg:pypi/name@v0.1.0?vcs_url=git+https:%2F%2Fgithub.com%2Ftest%2Ftest.git@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + { + name: "pip without vcs url", + pkg: pkg.Package{ + Name: "bad-name", + Version: "bad-v0.1.0", + Type: pkg.PythonPkg, + Metadata: pkg.PythonPackageMetadata{ + Name: "name", + Version: "v0.1.0", + }, }, expected: "pkg:pypi/name@v0.1.0", }, { + name: "gem", pkg: pkg.Package{ Name: "name", Version: "v0.1.0", @@ -39,6 +65,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:gem/name@v0.1.0", }, { + name: "npm", pkg: pkg.Package{ Name: "name", Version: "v0.1.0", @@ -47,6 +74,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:npm/name@v0.1.0", }, { + name: "deb with arch", distro: &distro.Distro{ Type: distro.Ubuntu, }, @@ -63,6 +91,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:deb/ubuntu/name@v0.1.0?arch=amd64", }, { + name: "deb with epoch", distro: &distro.Distro{ Type: distro.CentOS, }, @@ -81,6 +110,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64&epoch=2", }, { + name: "deb with nil epoch", distro: &distro.Distro{ Type: distro.CentOS, }, @@ -99,6 +129,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64", }, { + name: "deb with unknown distro", distro: &distro.Distro{ Type: distro.UnknownDistroType, }, @@ -110,6 +141,7 @@ func TestPackageURL(t *testing.T) { expected: "pkg:deb/name@v0.1.0", }, { + name: "cargo", pkg: pkg.Package{ Name: "name", Version: "v0.1.0", @@ -120,7 +152,7 @@ func TestPackageURL(t *testing.T) { } for _, test := range tests { - t.Run(string(test.pkg.Type)+"|"+test.expected, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { actual := generatePackageURL(test.pkg, test.distro) if actual != test.expected { dmp := diffmatchpatch.New() diff --git a/syft/pkg/cataloger/python/package_cataloger.go b/syft/pkg/cataloger/python/package_cataloger.go index f8a3a1228..ac0a214bc 100644 --- a/syft/pkg/cataloger/python/package_cataloger.go +++ b/syft/pkg/cataloger/python/package_cataloger.go @@ -2,7 +2,9 @@ package python import ( "bufio" + "encoding/json" "fmt" + "io/ioutil" "path/filepath" "github.com/anchore/syft/internal" @@ -152,6 +154,40 @@ func (c *PackageCataloger) fetchTopLevelPackages(resolver source.FileResolver, m return pkgs, sources, nil } +func (c *PackageCataloger) fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Location) (d *pkg.PythonDirectURLOriginInfo, sources []source.Location, err error) { + parentDir := filepath.Dir(metadataLocation.RealPath) + directURLPath := filepath.Join(parentDir, "direct_url.json") + directURLLocation := resolver.RelativeFileByPath(metadataLocation, directURLPath) + + if directURLLocation == nil { + return nil, nil, nil + } + + sources = append(sources, *directURLLocation) + + directURLContents, err := resolver.FileContentsByLocation(*directURLLocation) + if err != nil { + return nil, nil, err + } + defer internal.CloseAndLogError(directURLContents, directURLLocation.VirtualPath) + + buffer, err := ioutil.ReadAll(directURLContents) + if err != nil { + return nil, nil, err + } + + var directURLJson pkg.DirectURLOrigin + if err := json.Unmarshal(buffer, &directURLJson); err != nil { + return nil, nil, err + } + + return &pkg.PythonDirectURLOriginInfo{ + URL: directURLJson.URL, + CommitID: directURLJson.VCSInfo.CommitID, + VCS: directURLJson.VCSInfo.VCS, + }, sources, nil +} + // assembleEggOrWheelMetadata discovers and accumulates python package metadata from multiple file sources and returns a single metadata object as well as a list of files where the metadata was derived from. func (c *PackageCataloger) assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*pkg.PythonPackageMetadata, []source.Location, error) { var sources = []source.Location{metadataLocation} @@ -183,5 +219,13 @@ func (c *PackageCataloger) assembleEggOrWheelMetadata(resolver source.FileResolv sources = append(sources, s...) metadata.TopLevelPackages = p + // attach any direct-url package data found for the given wheel/egg installation + d, s, err := c.fetchDirectURLData(resolver, metadataLocation) + if err != nil { + return nil, nil, err + } + sources = append(sources, s...) + metadata.DirectURLOrigin = d + return &metadata, sources, nil } diff --git a/syft/pkg/cataloger/python/package_cataloger_test.go b/syft/pkg/cataloger/python/package_cataloger_test.go index 8766c42f6..ccca539a9 100644 --- a/syft/pkg/cataloger/python/package_cataloger_test.go +++ b/syft/pkg/cataloger/python/package_cataloger_test.go @@ -55,6 +55,7 @@ func TestPythonPackageWheelCataloger(t *testing.T) { "test-fixtures/dist-info/METADATA", "test-fixtures/dist-info/RECORD", "test-fixtures/dist-info/top_level.txt", + "test-fixtures/dist-info/direct_url.json", }, expectedPackage: pkg.Package{ Name: "Pygments", @@ -82,6 +83,7 @@ func TestPythonPackageWheelCataloger(t *testing.T) { {Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"}, }, TopLevelPackages: []string{"pygments", "something_else"}, + DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }, }, }, diff --git a/syft/pkg/cataloger/python/test-fixtures/dist-info/direct_url.json b/syft/pkg/cataloger/python/test-fixtures/dist-info/direct_url.json new file mode 100644 index 000000000..5d42a36e3 --- /dev/null +++ b/syft/pkg/cataloger/python/test-fixtures/dist-info/direct_url.json @@ -0,0 +1 @@ +{"url": "https://github.com/python-test/test.git", "vcs_info": {"commit_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "vcs": "git"}} \ No newline at end of file diff --git a/syft/pkg/python_package_metadata.go b/syft/pkg/python_package_metadata.go index f059dfac3..1b1418e73 100644 --- a/syft/pkg/python_package_metadata.go +++ b/syft/pkg/python_package_metadata.go @@ -1,8 +1,10 @@ package pkg import ( + "fmt" "sort" + "github.com/anchore/packageurl-go" "github.com/scylladb/go-set/strset" ) @@ -21,17 +23,45 @@ type PythonFileRecord struct { Size string `json:"size,omitempty"` } +type PythonDirectURLOriginInfo struct { + URL string `json:"url"` + CommitID string `json:"commitId,omitempty"` + VCS string `json:"vcs,omitempty"` +} + // PythonPackageMetadata represents all captured data for a python egg or wheel package. type PythonPackageMetadata struct { - Name string `json:"name" mapstruct:"Name"` - Version string `json:"version" mapstruct:"Version"` - License string `json:"license" mapstruct:"License"` - Author string `json:"author" mapstruct:"Author"` - AuthorEmail string `json:"authorEmail" mapstruct:"Authoremail"` - Platform string `json:"platform" mapstruct:"Platform"` - Files []PythonFileRecord `json:"files,omitempty"` - SitePackagesRootPath string `json:"sitePackagesRootPath"` - TopLevelPackages []string `json:"topLevelPackages,omitempty"` + Name string `json:"name" mapstruct:"Name"` + Version string `json:"version" mapstruct:"Version"` + License string `json:"license" mapstruct:"License"` + Author string `json:"author" mapstruct:"Author"` + AuthorEmail string `json:"authorEmail" mapstruct:"Authoremail"` + Platform string `json:"platform" mapstruct:"Platform"` + Files []PythonFileRecord `json:"files,omitempty"` + SitePackagesRootPath string `json:"sitePackagesRootPath"` + TopLevelPackages []string `json:"topLevelPackages,omitempty"` + DirectURLOrigin *PythonDirectURLOriginInfo `json:"directUrlOrigin,omitempty"` +} + +type DirectURLOrigin struct { + URL string `json:"url"` + VCSInfo VCSInfo `json:"vcs_info"` + ArchiveInfo ArchiveInfo `json:"archive_info"` + DirInfo DirInfo `json:"dir_info"` +} + +type DirInfo struct { + Editable bool `json:"editable"` +} + +type ArchiveInfo struct { + Hash string `json:"hash"` +} + +type VCSInfo struct { + CommitID string `json:"commit_id"` + VCS string `json:"vcs"` + RequestedRevision string `json:"requested_revision"` } func (m PythonPackageMetadata) OwnedFiles() (result []string) { @@ -45,3 +75,33 @@ func (m PythonPackageMetadata) OwnedFiles() (result []string) { sort.Strings(result) return result } + +func (m PythonPackageMetadata) PackageURL() string { + // generate a purl from the package data + pURL := packageurl.NewPackageURL( + packageurl.TypePyPi, + "", + m.Name, + m.Version, + m.purlQualifiers(), + "") + + return pURL.ToString() +} + +func (m PythonPackageMetadata) purlQualifiers() packageurl.Qualifiers { + q := packageurl.Qualifiers{} + if m.DirectURLOrigin != nil { + q = append(q, m.DirectURLOrigin.vcsURLQualifier()...) + } + return q +} + +func (p PythonDirectURLOriginInfo) vcsURLQualifier() packageurl.Qualifiers { + if p.VCS != "" { + // Taken from https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs + // packageurl-go still doesn't support all qualifier names + return packageurl.Qualifiers{{Key: "vcs_url", Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}} + } + return packageurl.Qualifiers{} +}