Summary
Takes a project source path, invokes Syft to produce a CycloneDX JSON SBOM, parses the output, and persists a SBOMSnapshot + one Component row per discovered package.
How it works
- Verifies the
Project row exists (raises SBOMGenerationError otherwise).
- Calls
run_tool([syft, source_path, "-o", "cyclonedx-json={output_path}"]).
- Reads the JSON file Syft wrote to disk, extracts the
components[] array.
- Inserts
SBOMSnapshot (with raw CycloneDX JSON blob) and one Component per entry — extracting name, version, purl, cpe, and ecosystem from the PURL prefix.
- Sets
SBOMSnapshot.status = "SBOM_DONE".
Key details
- PURL fallback: If Syft doesn't emit a PURL, a
pkg:generic/name@version fallback is constructed so the column stays non-null.
- Syft version handling: Handles both CycloneDX 1.4 (tools as a list) and 1.5 (tools as
{"components": [...]}) formats.
Tests (18 passing)
Snapshot row creation, component rows, ecosystem extraction from PURLs, CPE storage, Syft version parsing (1.4 and 1.5), and all error paths (project not found, non-zero exit, no output file, invalid JSON, zero components).
Summary
Takes a project source path, invokes Syft to produce a CycloneDX JSON SBOM, parses the output, and persists a
SBOMSnapshot+ oneComponentrow per discovered package.How it works
Projectrow exists (raisesSBOMGenerationErrorotherwise).run_tool([syft, source_path, "-o", "cyclonedx-json={output_path}"]).components[]array.SBOMSnapshot(with raw CycloneDX JSON blob) and oneComponentper entry — extractingname,version,purl,cpe, and ecosystem from the PURL prefix.SBOMSnapshot.status = "SBOM_DONE".Key details
pkg:generic/name@versionfallback is constructed so the column stays non-null.{"components": [...]}) formats.Tests (18 passing)
Snapshot row creation, component rows, ecosystem extraction from PURLs, CPE storage, Syft version parsing (1.4 and 1.5), and all error paths (project not found, non-zero exit, no output file, invalid JSON, zero components).