From e1c1832f8413de04f9c6a47c1f22b3a526473231 Mon Sep 17 00:00:00 2001 From: Tristan Farkas Date: Tue, 25 Jul 2023 20:35:21 +0200 Subject: [PATCH] Add cataloger for Swift Package Manager. (#1919) Signed-off-by: Tristan Farkas --- CONTRIBUTORS.md | 4 + README.md | 4 +- internal/constants.go | 2 +- schema/json/schema-9.0.2.json | 1931 +++++++++++++++++ .../formats/common/spdxhelpers/source_info.go | 2 + .../common/spdxhelpers/source_info_test.go | 8 + syft/internal/packagemetadata/generated.go | 2 +- syft/pkg/cataloger/cataloger.go | 2 + syft/pkg/cataloger/swift/cataloger.go | 7 +- syft/pkg/cataloger/swift/package.go | 40 +- syft/pkg/cataloger/swift/package_test.go | 4 +- .../cataloger/swift/parse_package_resolved.go | 134 ++ .../swift/parse_package_resolved_test.go | 82 + .../pkg/cataloger/swift/parse_podfile_lock.go | 2 +- .../swift/test-fixtures/Package.resolved | 50 + syft/pkg/language.go | 2 +- syft/pkg/language_test.go | 10 +- syft/pkg/metadata.go | 3 + syft/pkg/swiftpackagemanager_metadata.go | 5 + syft/pkg/type.go | 6 + syft/pkg/type_test.go | 4 + .../catalog_packages_cases_test.go | 12 + test/integration/catalog_packages_test.go | 1 + .../image-pkg-coverage/swift/Package.resolved | 50 + 24 files changed, 2353 insertions(+), 14 deletions(-) create mode 100644 schema/json/schema-9.0.2.json create mode 100644 syft/pkg/cataloger/swift/parse_package_resolved.go create mode 100644 syft/pkg/cataloger/swift/parse_package_resolved_test.go create mode 100644 syft/pkg/cataloger/swift/test-fixtures/Package.resolved create mode 100644 syft/pkg/swiftpackagemanager_metadata.go create mode 100644 test/integration/test-fixtures/image-pkg-coverage/swift/Package.resolved diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 3d97f34ad..3f773335e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,3 +5,7 @@ The following Syft components were contributed by external authors/organizations ## GraalVM Native Image A cataloger contributed by Oracle Corporation that extracts packages given within GraalVM Native Image SBOMs. + +## Swift Package Manager + +A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager. \ No newline at end of file diff --git a/README.md b/README.md index 70c293f67..b35af21ec 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http - Red Hat (rpm) - Ruby (gem) - Rust (cargo.lock) -- Swift (cocoapods) +- Swift (cocoapods, swift-package-manager) ## Installation @@ -211,6 +211,7 @@ You can override the list of enabled/disabled catalogers by using the "cataloger - ruby-gemfile - rust-cargo-lock - sbom +- swift-package-manager ##### Non Default: - cargo-auditable-binary @@ -521,6 +522,7 @@ platform: "" # - ruby-gemspec-cataloger # - rust-cargo-lock-cataloger # - sbom-cataloger +# - spm-cataloger catalogers: # cataloging packages is exposed through the packages and power-user subcommands diff --git a/internal/constants.go b/internal/constants.go index e61d94348..da0a0bb1f 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "9.0.1" + JSONSchemaVersion = "9.0.2" ) diff --git a/schema/json/schema-9.0.2.json b/schema/json/schema-9.0.2.json new file mode 100644 index 000000000..1e0dc4341 --- /dev/null +++ b/schema/json/schema-9.0.2.json @@ -0,0 +1,1931 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/9.0.2/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "AlpmMetadata": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "ApkMetadata": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "BinaryMetadata": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CargoPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoapodsMetadata": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "ConanLockMetadata": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "type": "string" + }, + "build_requires": { + "type": "string" + }, + "py_requires": { + "type": "string" + }, + "options": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ConanMetadata": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "secrets": { + "items": { + "$ref": "#/$defs/Secrets" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPortableExecutableMetadata": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "DpkgMetadata": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GemMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "GolangBinMetadata": { + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GolangModMetadata": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "JavaMetadata": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/PomProperties" + }, + "pomProject": { + "$ref": "#/$defs/PomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "KbPackageMetadata": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelMetadata": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModuleMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "MixLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "NixStoreMetadata": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "NpmPackageJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "NpmPackageLockJSONMetadata": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmMetadata" + }, + { + "$ref": "#/$defs/ApkMetadata" + }, + { + "$ref": "#/$defs/BinaryMetadata" + }, + { + "$ref": "#/$defs/CargoPackageMetadata" + }, + { + "$ref": "#/$defs/CocoapodsMetadata" + }, + { + "$ref": "#/$defs/ConanLockMetadata" + }, + { + "$ref": "#/$defs/ConanMetadata" + }, + { + "$ref": "#/$defs/DartPubMetadata" + }, + { + "$ref": "#/$defs/DotnetDepsMetadata" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableMetadata" + }, + { + "$ref": "#/$defs/DpkgMetadata" + }, + { + "$ref": "#/$defs/GemMetadata" + }, + { + "$ref": "#/$defs/GolangBinMetadata" + }, + { + "$ref": "#/$defs/GolangModMetadata" + }, + { + "$ref": "#/$defs/HackageMetadata" + }, + { + "$ref": "#/$defs/JavaMetadata" + }, + { + "$ref": "#/$defs/KbPackageMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelModuleMetadata" + }, + { + "$ref": "#/$defs/MixLockMetadata" + }, + { + "$ref": "#/$defs/NixStoreMetadata" + }, + { + "$ref": "#/$defs/NpmPackageJSONMetadata" + }, + { + "$ref": "#/$defs/NpmPackageLockJSONMetadata" + }, + { + "$ref": "#/$defs/PhpComposerJSONMetadata" + }, + { + "$ref": "#/$defs/PortageMetadata" + }, + { + "$ref": "#/$defs/PythonPackageMetadata" + }, + { + "$ref": "#/$defs/PythonPipfileLockMetadata" + }, + { + "$ref": "#/$defs/PythonRequirementsMetadata" + }, + { + "$ref": "#/$defs/RDescriptionFileMetadata" + }, + { + "$ref": "#/$defs/RebarLockMetadata" + }, + { + "$ref": "#/$defs/RpmMetadata" + }, + { + "$ref": "#/$defs/SwiftPackageManagerMetadata" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "PomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/PomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "PomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PortageMetadata": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipfileLockMetadata": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonRequirementsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "extras", + "versionConstraint", + "url", + "markers" + ] + }, + "RDescriptionFileMetadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "RebarLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmdbFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "modularityLabel", + "files" + ] + }, + "RpmdbFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SearchResult": { + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ] + }, + "Secrets": { + "properties": { + "location": { + "$ref": "#/$defs/Coordinates" + }, + "secrets": { + "items": { + "$ref": "#/$defs/SearchResult" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "location", + "secrets" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerMetadata": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/syft/formats/common/spdxhelpers/source_info.go b/syft/formats/common/spdxhelpers/source_info.go index 8aeb5b356..2ec80786e 100644 --- a/syft/formats/common/spdxhelpers/source_info.go +++ b/syft/formats/common/spdxhelpers/source_info.go @@ -54,6 +54,8 @@ func SourceInfo(p pkg.Package) string { answer = "acquired package info from nix store path" case pkg.Rpkg: answer = "acquired package info from R-package DESCRIPTION file" + case pkg.SwiftPkg: + answer = "acquired package info from resolved Swift package manifest" default: answer = "acquired package info from the following paths" } diff --git a/syft/formats/common/spdxhelpers/source_info_test.go b/syft/formats/common/spdxhelpers/source_info_test.go index a7b2dc142..5b30b95fc 100644 --- a/syft/formats/common/spdxhelpers/source_info_test.go +++ b/syft/formats/common/spdxhelpers/source_info_test.go @@ -231,6 +231,14 @@ func Test_SourceInfo(t *testing.T) { "acquired package info from R-package DESCRIPTION file", }, }, + { + input: pkg.Package{ + Type: pkg.SwiftPkg, + }, + expected: []string{ + "from resolved Swift package manifest", + }, + }, } var pkgTypes []pkg.Type for _, test := range tests { diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index d614bce0a..6d0cc2887 100644 --- a/syft/internal/packagemetadata/generated.go +++ b/syft/internal/packagemetadata/generated.go @@ -6,5 +6,5 @@ import "github.com/anchore/syft/syft/pkg" // AllTypes returns a list of all pkg metadata types that syft supports (that are represented in the pkg.Package.Metadata field). func AllTypes() []any { - return []any{pkg.AlpmMetadata{}, pkg.ApkMetadata{}, pkg.BinaryMetadata{}, pkg.CargoPackageMetadata{}, pkg.CocoapodsMetadata{}, pkg.ConanLockMetadata{}, pkg.ConanMetadata{}, pkg.DartPubMetadata{}, pkg.DotnetDepsMetadata{}, pkg.DotnetPortableExecutableMetadata{}, pkg.DpkgMetadata{}, pkg.GemMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.HackageMetadata{}, pkg.JavaMetadata{}, pkg.KbPackageMetadata{}, pkg.LinuxKernelMetadata{}, pkg.LinuxKernelModuleMetadata{}, pkg.MixLockMetadata{}, pkg.NixStoreMetadata{}, pkg.NpmPackageJSONMetadata{}, pkg.NpmPackageLockJSONMetadata{}, pkg.PhpComposerJSONMetadata{}, pkg.PortageMetadata{}, pkg.PythonPackageMetadata{}, pkg.PythonPipfileLockMetadata{}, pkg.PythonRequirementsMetadata{}, pkg.RDescriptionFileMetadata{}, pkg.RebarLockMetadata{}, pkg.RpmMetadata{}} + return []any{pkg.AlpmMetadata{}, pkg.ApkMetadata{}, pkg.BinaryMetadata{}, pkg.CargoPackageMetadata{}, pkg.CocoapodsMetadata{}, pkg.ConanLockMetadata{}, pkg.ConanMetadata{}, pkg.DartPubMetadata{}, pkg.DotnetDepsMetadata{}, pkg.DotnetPortableExecutableMetadata{}, pkg.DpkgMetadata{}, pkg.GemMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.HackageMetadata{}, pkg.JavaMetadata{}, pkg.KbPackageMetadata{}, pkg.LinuxKernelMetadata{}, pkg.LinuxKernelModuleMetadata{}, pkg.MixLockMetadata{}, pkg.NixStoreMetadata{}, pkg.NpmPackageJSONMetadata{}, pkg.NpmPackageLockJSONMetadata{}, pkg.PhpComposerJSONMetadata{}, pkg.PortageMetadata{}, pkg.PythonPackageMetadata{}, pkg.PythonPipfileLockMetadata{}, pkg.PythonRequirementsMetadata{}, pkg.RDescriptionFileMetadata{}, pkg.RebarLockMetadata{}, pkg.RpmMetadata{}, pkg.SwiftPackageManagerMetadata{}} } diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index acb6e9e59..d7ff4cbdd 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -93,6 +93,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger { rust.NewCargoLockCataloger(), sbom.NewSBOMCataloger(), swift.NewCocoapodsCataloger(), + swift.NewSwiftPackageManagerCataloger(), }, cfg.Catalogers) } @@ -134,6 +135,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger { rust.NewCargoLockCataloger(), sbom.NewSBOMCataloger(), swift.NewCocoapodsCataloger(), + swift.NewSwiftPackageManagerCataloger(), }, cfg.Catalogers) } diff --git a/syft/pkg/cataloger/swift/cataloger.go b/syft/pkg/cataloger/swift/cataloger.go index 5ce504b74..a890b8581 100644 --- a/syft/pkg/cataloger/swift/cataloger.go +++ b/syft/pkg/cataloger/swift/cataloger.go @@ -1,5 +1,5 @@ /* -Package swift provides a concrete Cataloger implementation for Podfile.lock files. +Package swift provides a concrete Cataloger implementation for Podfile.lock and Package.resolved files. */ package swift @@ -7,6 +7,11 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/generic" ) +func NewSwiftPackageManagerCataloger() *generic.Cataloger { + return generic.NewCataloger("spm-cataloger"). + WithParserByGlobs(parsePackageResolved, "**/Package.resolved", "**/.package.resolved") +} + // NewCocoapodsCataloger returns a new Swift Cocoapods lock file cataloger object. func NewCocoapodsCataloger() *generic.Cataloger { return generic.NewCataloger("cocoapods-cataloger"). diff --git a/syft/pkg/cataloger/swift/package.go b/syft/pkg/cataloger/swift/package.go index ad6416e64..c5f606cb2 100644 --- a/syft/pkg/cataloger/swift/package.go +++ b/syft/pkg/cataloger/swift/package.go @@ -1,16 +1,37 @@ package swift import ( + "strings" + "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) -func newPackage(name, version, hash string, locations ...file.Location) pkg.Package { +func newSwiftPackageManagerPackage(name, version, sourceURL, revision string, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, - PURL: packageURL(name, version), + PURL: swiftPackageManagerPackageURL(name, version, sourceURL), + Locations: file.NewLocationSet(locations...), + Type: pkg.SwiftPkg, + Language: pkg.Swift, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: revision, + }, + } + + p.SetID() + + return p +} + +func newCocoaPodsPackage(name, version, hash string, locations ...file.Location) pkg.Package { + p := pkg.Package{ + Name: name, + Version: version, + PURL: cocoaPodsPackageURL(name, version), Locations: file.NewLocationSet(locations...), Type: pkg.CocoapodsPkg, Language: pkg.Swift, @@ -25,7 +46,7 @@ func newPackage(name, version, hash string, locations ...file.Location) pkg.Pack return p } -func packageURL(name, version string) string { +func cocoaPodsPackageURL(name, version string) string { var qualifiers packageurl.Qualifiers return packageurl.NewPackageURL( @@ -37,3 +58,16 @@ func packageURL(name, version string) string { "", ).ToString() } + +func swiftPackageManagerPackageURL(name, version, sourceURL string) string { + var qualifiers packageurl.Qualifiers + + return packageurl.NewPackageURL( + packageurl.TypeSwift, + strings.Replace(sourceURL, "https://", "", 1), + name, + version, + qualifiers, + "", + ).ToString() +} diff --git a/syft/pkg/cataloger/swift/package_test.go b/syft/pkg/cataloger/swift/package_test.go index 388379226..daa3132cd 100644 --- a/syft/pkg/cataloger/swift/package_test.go +++ b/syft/pkg/cataloger/swift/package_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_packageURL(t *testing.T) { +func Test_cocoaPodsPackageURL(t *testing.T) { type args struct { name string version string @@ -27,7 +27,7 @@ func Test_packageURL(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, packageURL(tt.args.name, tt.args.version)) + assert.Equal(t, tt.want, cocoaPodsPackageURL(tt.args.name, tt.args.version)) }) } } diff --git a/syft/pkg/cataloger/swift/parse_package_resolved.go b/syft/pkg/cataloger/swift/parse_package_resolved.go new file mode 100644 index 000000000..abead252b --- /dev/null +++ b/syft/pkg/cataloger/swift/parse_package_resolved.go @@ -0,0 +1,134 @@ +package swift + +import ( + "encoding/json" + "errors" + "fmt" + "io" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +var _ generic.Parser = parsePackageResolved + +// swift package manager has two versions (1 and 2) of the resolved files, the types below describes the serialization strategies for each version +// with its suffix indicating which version its specific to. + +type packageResolvedV1 struct { + PackageObject packageObjectV1 `json:"object"` + Version int `json:"version"` +} + +type packageObjectV1 struct { + Pins []packagePinsV1 +} + +type packagePinsV1 struct { + Name string `json:"package"` + RepositoryURL string `json:"repositoryURL"` + State packageState `json:"state"` +} + +type packageResolvedV2 struct { + Pins []packagePinsV2 +} + +type packagePinsV2 struct { + Identity string `json:"identity"` + Kind string `json:"kind"` + Location string `json:"location"` + State packageState `json:"state"` +} + +type packagePin struct { + Identity string + Location string + Revision string + Version string +} + +type packageState struct { + Revision string `json:"revision"` + Version string `json:"version"` +} + +// parsePackageResolved is a parser for the contents of a Package.resolved file, which is generated by Xcode after it's resolved Swift Package Manger packages. +func parsePackageResolved(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + dec := json.NewDecoder(reader) + var packageResolvedData map[string]interface{} + for { + if err := dec.Decode(&packageResolvedData); errors.Is(err, io.EOF) { + break + } else if err != nil { + return nil, nil, fmt.Errorf("failed to parse Package.resolved file: %w", err) + } + } + + var pins, err = pinsForVersion(packageResolvedData, packageResolvedData["version"].(float64)) + if err != nil { + return nil, nil, err + } + + var pkgs []pkg.Package + for _, packagePin := range pins { + pkgs = append( + pkgs, + newSwiftPackageManagerPackage( + packagePin.Identity, + packagePin.Version, + packagePin.Location, + packagePin.Revision, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + ) + } + return pkgs, nil, nil +} + +func pinsForVersion(data map[string]interface{}, version float64) ([]packagePin, error) { + var genericPins []packagePin + switch version { + case 1: + t := packageResolvedV1{} + jsonString, err := json.Marshal(data) + if err != nil { + return nil, err + } + parseErr := json.Unmarshal(jsonString, &t) + if parseErr != nil { + return nil, parseErr + } + for _, pin := range t.PackageObject.Pins { + genericPins = append(genericPins, packagePin{ + pin.Name, + pin.RepositoryURL, + pin.State.Revision, + pin.State.Version, + }) + } + case 2: + t := packageResolvedV2{} + jsonString, err := json.Marshal(data) + if err != nil { + return nil, err + } + parseErr := json.Unmarshal(jsonString, &t) + if parseErr != nil { + return nil, parseErr + } + for _, pin := range t.Pins { + genericPins = append(genericPins, packagePin{ + pin.Identity, + pin.Location, + pin.State.Revision, + pin.State.Version, + }) + } + default: + return nil, fmt.Errorf("unknown swift package manager version, %f", version) + } + return genericPins, nil +} diff --git a/syft/pkg/cataloger/swift/parse_package_resolved_test.go b/syft/pkg/cataloger/swift/parse_package_resolved_test.go new file mode 100644 index 000000000..25d7c3a87 --- /dev/null +++ b/syft/pkg/cataloger/swift/parse_package_resolved_test.go @@ -0,0 +1,82 @@ +package swift + +import ( + "testing" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" +) + +func TestParsePackageResolved(t *testing.T) { + fixture := "test-fixtures/Package.resolved" + locations := file.NewLocationSet(file.NewLocation(fixture)) + expectedPkgs := []pkg.Package{ + { + Name: "swift-algorithms", + Version: "1.0.0", + PURL: "pkg:swift/github.com/apple/swift-algorithms.git/swift-algorithms@1.0.0", + Locations: locations, + Language: pkg.Swift, + Type: pkg.SwiftPkg, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: "b14b7f4c528c942f121c8b860b9410b2bf57825e", + }, + }, + { + Name: "swift-async-algorithms", + Version: "0.1.0", + PURL: "pkg:swift/github.com/apple/swift-async-algorithms.git/swift-async-algorithms@0.1.0", + Locations: locations, + Language: pkg.Swift, + Type: pkg.SwiftPkg, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", + }, + }, + { + Name: "swift-atomics", + Version: "1.1.0", + PURL: "pkg:swift/github.com/apple/swift-atomics.git/swift-atomics@1.1.0", + Locations: locations, + Language: pkg.Swift, + Type: pkg.SwiftPkg, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: "6c89474e62719ddcc1e9614989fff2f68208fe10", + }, + }, + { + Name: "swift-collections", + Version: "1.0.4", + PURL: "pkg:swift/github.com/apple/swift-collections.git/swift-collections@1.0.4", + Locations: locations, + Language: pkg.Swift, + Type: pkg.SwiftPkg, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: "937e904258d22af6e447a0b72c0bc67583ef64a2", + }, + }, + { + Name: "swift-numerics", + Version: "1.0.2", + PURL: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2", + Locations: locations, + Language: pkg.Swift, + Type: pkg.SwiftPkg, + MetadataType: pkg.SwiftPackageManagerMetadataType, + Metadata: pkg.SwiftPackageManagerMetadata{ + Revision: "0a5bc04095a675662cf24757cc0640aa2204253b", + }, + }, + } + + // TODO: no relationships are under test yet + var expectedRelationships []artifact.Relationship + + pkgtest.TestFileParser(t, fixture, parsePackageResolved, expectedPkgs, expectedRelationships) +} diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock.go b/syft/pkg/cataloger/swift/parse_podfile_lock.go index 58a58c464..3ba479566 100644 --- a/syft/pkg/cataloger/swift/parse_podfile_lock.go +++ b/syft/pkg/cataloger/swift/parse_podfile_lock.go @@ -61,7 +61,7 @@ func parsePodfileLock(_ file.Resolver, _ *generic.Environment, reader file.Locat pkgs = append( pkgs, - newPackage( + newCocoaPodsPackage( podName, podVersion, pkgHash, diff --git a/syft/pkg/cataloger/swift/test-fixtures/Package.resolved b/syft/pkg/cataloger/swift/test-fixtures/Package.resolved new file mode 100644 index 000000000..d8e20bb8b --- /dev/null +++ b/syft/pkg/cataloger/swift/test-fixtures/Package.resolved @@ -0,0 +1,50 @@ +{ + "pins" : [ + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", + "version" : "0.1.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + } + ], + "version" : 2 +} \ No newline at end of file diff --git a/syft/pkg/language.go b/syft/pkg/language.go index bb2902bbf..38760180e 100644 --- a/syft/pkg/language.go +++ b/syft/pkg/language.go @@ -82,7 +82,7 @@ func LanguageByName(name string) Language { return Dart case packageurl.TypeDotnet: return Dotnet - case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg): + case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg), string(SwiftPkg): return Swift case packageurl.TypeConan, string(CPP): return CPP diff --git a/syft/pkg/language_test.go b/syft/pkg/language_test.go index 36740d66c..57417e280 100644 --- a/syft/pkg/language_test.go +++ b/syft/pkg/language_test.go @@ -70,9 +70,13 @@ func TestLanguageFromPURL(t *testing.T) { purl: "pkg:cran/base@4.3.0", want: R, }, + { + purl: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2", + want: Swift, + }, } - var languages []string + var languages = strset.New() var expectedLanguages = strset.New() for _, ty := range AllLanguages { expectedLanguages.Add(string(ty)) @@ -87,14 +91,14 @@ func TestLanguageFromPURL(t *testing.T) { actual := LanguageFromPURL(tt.purl) if actual != "" { - languages = append(languages, string(actual)) + languages.Add(string(actual)) } assert.Equalf(t, tt.want, actual, "LanguageFromPURL(%v)", tt.purl) }) } - assert.ElementsMatch(t, expectedLanguages.List(), languages, "missing one or more languages to test against (maybe a package type was added?)") + assert.ElementsMatch(t, expectedLanguages.List(), languages.List(), "missing one or more languages to test against (maybe a package type was added?)") } diff --git a/syft/pkg/metadata.go b/syft/pkg/metadata.go index 689300b38..ce0594bda 100644 --- a/syft/pkg/metadata.go +++ b/syft/pkg/metadata.go @@ -42,6 +42,7 @@ const ( RDescriptionFileMetadataType MetadataType = "RDescriptionFileMetadataType" RpmMetadataType MetadataType = "RpmMetadata" RustCargoPackageMetadataType MetadataType = "RustCargoPackageMetadata" + SwiftPackageManagerMetadataType MetadataType = "SwiftPackageManagerMetadata" ) var AllMetadataTypes = []MetadataType{ @@ -76,6 +77,7 @@ var AllMetadataTypes = []MetadataType{ RebarLockMetadataType, RpmMetadataType, RustCargoPackageMetadataType, + SwiftPackageManagerMetadataType, } var MetadataTypeByName = map[MetadataType]reflect.Type{ @@ -110,6 +112,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{ RebarLockMetadataType: reflect.TypeOf(RebarLockMetadata{}), RpmMetadataType: reflect.TypeOf(RpmMetadata{}), RustCargoPackageMetadataType: reflect.TypeOf(CargoPackageMetadata{}), + SwiftPackageManagerMetadataType: reflect.TypeOf(SwiftPackageManagerMetadata{}), } func CleanMetadataType(typ MetadataType) MetadataType { diff --git a/syft/pkg/swiftpackagemanager_metadata.go b/syft/pkg/swiftpackagemanager_metadata.go new file mode 100644 index 000000000..fd33a8d6b --- /dev/null +++ b/syft/pkg/swiftpackagemanager_metadata.go @@ -0,0 +1,5 @@ +package pkg + +type SwiftPackageManagerMetadata struct { + Revision string `mapstructure:"revision" json:"revision"` +} diff --git a/syft/pkg/type.go b/syft/pkg/type.go index 760b32329..e3ed3f4c1 100644 --- a/syft/pkg/type.go +++ b/syft/pkg/type.go @@ -36,6 +36,7 @@ const ( Rpkg Type = "R-package" RpmPkg Type = "rpm" RustPkg Type = "rust-crate" + SwiftPkg Type = "swift" ) // AllPkgs represents all supported package types @@ -65,6 +66,7 @@ var AllPkgs = []Type{ Rpkg, RpmPkg, RustPkg, + SwiftPkg, } // PackageURLType returns the PURL package type for the current package. @@ -114,6 +116,8 @@ func (t Type) PackageURLType() string { return packageurl.TypeRPM case RustPkg: return "cargo" + case SwiftPkg: + return packageurl.TypeSwift default: // TODO: should this be a "generic" purl type instead? return "" @@ -179,6 +183,8 @@ func TypeByName(name string) Type { return NixPkg case packageurl.TypeCran: return Rpkg + case packageurl.TypeSwift: + return SwiftPkg default: return UnknownPkg } diff --git a/syft/pkg/type_test.go b/syft/pkg/type_test.go index e5c7a687f..3da73184d 100644 --- a/syft/pkg/type_test.go +++ b/syft/pkg/type_test.go @@ -95,6 +95,10 @@ func TestTypeFromPURL(t *testing.T) { purl: "pkg:cran/base@4.3.0", expected: Rpkg, }, + { + purl: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2", + expected: SwiftPkg, + }, } var pkgTypes []string diff --git a/test/integration/catalog_packages_cases_test.go b/test/integration/catalog_packages_cases_test.go index 815ccd3e4..48b280a56 100644 --- a/test/integration/catalog_packages_cases_test.go +++ b/test/integration/catalog_packages_cases_test.go @@ -356,6 +356,18 @@ var dirOnlyTestCases = []testCase{ "unicode_util_compat": "0.7.0", }, }, + { + name: "find swift package manager packages", + pkgType: pkg.SwiftPkg, + pkgLanguage: pkg.Swift, + pkgInfo: map[string]string{ + "swift-algorithms": "1.0.0", + "swift-async-algorithms": "0.1.0", + "swift-atomics": "1.1.0", + "swift-collections": "1.0.4", + "swift-numerics": "1.0.2", + }, + }, } var commonTestCases = []testCase{ diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index f2f153a52..0b6ca54f0 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -95,6 +95,7 @@ func TestPkgCoverageImage(t *testing.T) { definedPkgs.Remove(string(pkg.HexPkg)) definedPkgs.Remove(string(pkg.LinuxKernelPkg)) definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) + definedPkgs.Remove(string(pkg.SwiftPkg)) var cases []testCase cases = append(cases, commonTestCases...) diff --git a/test/integration/test-fixtures/image-pkg-coverage/swift/Package.resolved b/test/integration/test-fixtures/image-pkg-coverage/swift/Package.resolved new file mode 100644 index 000000000..d8e20bb8b --- /dev/null +++ b/test/integration/test-fixtures/image-pkg-coverage/swift/Package.resolved @@ -0,0 +1,50 @@ +{ + "pins" : [ + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", + "version" : "0.1.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + } + ], + "version" : 2 +} \ No newline at end of file