Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/pytest/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ runs:
shell: bash
run: |
python -m pip install --upgrade pip
pip install .
pip install .[cdx,spdx]
- name: test generate minimal deps
shell: bash
Expand Down
21 changes: 12 additions & 9 deletions docs/source/starting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ Virtual Environment

``source <env_name>/bin/activate``

3. Install the dependencies in the virtual environment with one of the following commands:
3. Install debsbom and its dependencies in the virtual environment with one of the following commands:

- ``pip3 install -e .``
Installs the dependencies for all but the download command.
- Installs the debsbom pip package with its core dependencies:

- ``pip3 install -e .[download]``
Installs the dependencies for all commands.
``pip3 install debsbom[cdx,spdx,download]``

- ``pip3 install -e .[dev]``
Installs the dependencies for all commands, as well as dependencies for testing and documentation building
If you do not need CycloneDX, SPDX, or download support, you can omit the respective extras.

4. test installation with:
- Installs the debsbom package in editable mode with all dependencies, including development dependencies for testing and documentation building:

``pip3 install -e .[dev]``

Replace the dot (.) with the path to the debsbom source code if not executing from within the source directory.

4. Test the installation with:

``debsbom -h``

**Optional**: To significantly speedup the parsing of deb822 data, it is recommended to install the non-pip package python3-apt (e.g., ``apt install python3-apt`` on Debian-based systems)
**Optional**: To significantly speed up the parsing of deb822 data, it is recommended to install the system package python3-apt (e.g., ``apt install python3-apt`` on Debian-based systems)

Container Image
---------------
Expand Down
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ build-backend = "setuptools.build_meta"
name = "debsbom"
version = "0.4.0"
dependencies = [
"cyclonedx-python-lib>=11.0.0",
"packageurl-python>=0.16.0",
"spdx-tools>=0.8.3",
"python-debian>=0.1.49",
]
requires-python = ">=3.11"
Expand Down Expand Up @@ -43,6 +41,14 @@ dev = [
"lz4",
"debsbom[download]",
"debsbom[doc]",
"debsbom[cdx]",
"debsbom[spdx]",
]
cdx = [
"cyclonedx-python-lib>=11.0.0",
]
spdx = [
"spdx-tools>=0.8.3",
]
download = [
"requests>=2.25.1",
Expand Down
2 changes: 0 additions & 2 deletions src/debsbom/bomreader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@
# SPDX-License-Identifier: MIT

from .bomreader import BomReader
from .spdxbomreader import SpdxBomReader
from .cdxbomreader import CdxBomReader
13 changes: 10 additions & 3 deletions src/debsbom/bomwriter/bomwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
#
# SPDX-License-Identifier: MIT

import cyclonedx.output as cdx_output
import cyclonedx.schema as cdx_schema
from io import TextIOWrapper
from pathlib import Path
import spdx_tools.spdx.writer.json.json_writer as spdx_json_writer

from ..sbom import SBOMType

Expand All @@ -15,20 +12,30 @@ class BomWriter:
@staticmethod
def write_to_file(bom, bomtype: SBOMType, outfile: Path, validate: bool):
if bomtype == SBOMType.CycloneDX:
import cyclonedx.output as cdx_output
import cyclonedx.schema as cdx_schema

cdx_output.make_outputter(
bom, cdx_schema.OutputFormat.JSON, cdx_schema.SchemaVersion.V1_6
).output_to_file(str(outfile), allow_overwrite=True, indent=4)
elif bomtype == SBOMType.SPDX:
import spdx_tools.spdx.writer.json.json_writer as spdx_json_writer

spdx_json_writer.write_document_to_file(bom, str(outfile), validate)

@staticmethod
def write_to_stream(bom, bomtype: SBOMType, f: TextIOWrapper, validate: bool):
if bomtype == SBOMType.CycloneDX:
import cyclonedx.output as cdx_output
import cyclonedx.schema as cdx_schema

f.write(
cdx_output.make_outputter(
bom, cdx_schema.OutputFormat.JSON, cdx_schema.SchemaVersion.V1_6
).output_as_string(indent=4)
)
elif bomtype == SBOMType.SPDX:
import spdx_tools.spdx.writer.json.json_writer as spdx_json_writer

spdx_json_writer.write_document_to_stream(bom, f, validate)
f.write("\n")
2 changes: 1 addition & 1 deletion src/debsbom/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ExportCmd(SbomInput):

@classmethod
def run(cls, args):
from ..export.spdx import GraphExporter
from ..export.exporter import GraphExporter
from ..export.exporter import GraphOutputFormat

exporter = cls.create_sbom_processor(
Expand Down
15 changes: 13 additions & 2 deletions src/debsbom/commands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,20 @@ class GenerateCmd(GenerateInput):
@staticmethod
def run(args):
if args.sbom_type is None:
sbom_types = [SBOMType.SPDX, SBOMType.CycloneDX]
sbom_types: list[SBOMType] = []
missing_deps: list[str] = []
for stype in [SBOMType.SPDX, SBOMType.CycloneDX]:
try:
stype.validate_dependency_availability()
sbom_types.append(stype)
except RuntimeError as e:
missing_deps.append(str(e))
if sbom_types == []:
raise RuntimeError(f"Cannot generate any SBOM due to {', '.join(missing_deps)}")
else:
sbom_types = [SBOMType.from_str(stype) for stype in args.sbom_type]
sbom_types: list[SBOMType] = [SBOMType.from_str(stype) for stype in args.sbom_type]
for stype in sbom_types:
stype.validate_dependency_availability()

cdx_standard = BOM_Standard.DEFAULT
if args.cdx_standard == "standard-bom":
Expand Down
14 changes: 10 additions & 4 deletions src/debsbom/commands/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@
from pathlib import Path
import sys

from ..bomreader.cdxbomreader import CdxBomReader
from ..bomreader.spdxbomreader import SpdxBomReader
from ..bomwriter import BomWriter
from .input import GenerateInput, warn_if_tty
from ..merge.spdx import SpdxSbomMerger
from ..merge.cdx import CdxSbomMerger
from ..sbom import SBOMType
from ..util.progress import progress_cb

Expand Down Expand Up @@ -44,14 +40,20 @@ def run(args):
else:
sbom_path = Path(sbom)
if ".spdx" in sbom_path.suffixes:
SBOMType.SPDX.validate_dependency_availability()
spdx_paths.append(sbom_path)
elif ".cdx" in sbom_path.suffixes:
SBOMType.CycloneDX.validate_dependency_availability()
cdx_paths.append(sbom_path)

docs = []
if len(spdx_paths) > 0 and len(cdx_paths) > 0:
raise ValueError("can not merge mixed SPDX and CycloneDX documents")
elif len(spdx_paths) > 0 or args.sbom_type == "spdx":
SBOMType.SPDX.validate_dependency_availability()
from ..bomreader.spdxbomreader import SpdxBomReader
from ..merge.spdx import SpdxSbomMerger

if json_sboms:
for obj in json_sboms:
docs.append(SpdxBomReader.from_json(obj))
Expand All @@ -75,6 +77,10 @@ def run(args):
out += ".spdx.json"
BomWriter.write_to_file(bom, SBOMType.SPDX, Path(out), args.validate)
elif len(cdx_paths) > 0 or args.sbom_type == "cdx":
SBOMType.CycloneDX.validate_dependency_availability()
from ..bomreader.cdxbomreader import CdxBomReader
from ..merge.cdx import CdxSbomMerger

if json_sboms:
for obj in json_sboms:
docs.append(CdxBomReader.from_json(obj))
Expand Down
11 changes: 7 additions & 4 deletions src/debsbom/export/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ def create(filename: Path, format: GraphOutputFormat) -> "GraphExporter":
Factory to create a GraphExporter for the given SBOM type (based on the filename extension).
"""
if filename.name.endswith("spdx.json"):
from ..bomreader import SpdxBomReader
SBOMType.SPDX.validate_dependency_availability()
from ..bomreader.spdxbomreader import SpdxBomReader
from .spdx import SpdxGraphMLExporter

bom = SpdxBomReader.read_file(filename)
if format == GraphOutputFormat.GRAPHML:
return SpdxGraphMLExporter(bom)
elif filename.name.endswith("cdx.json"):
from ..bomreader import CdxBomReader
SBOMType.CycloneDX.validate_dependency_availability()
from ..bomreader.cdxbomreader import CdxBomReader
from .cdx import CdxGraphMLExporter

bom = CdxBomReader.read_file(filename)
Expand All @@ -54,15 +56,16 @@ def from_stream(stream: IO, bomtype: SBOMType, format: GraphOutputFormat) -> "Gr
"""
Factory to create a GraphExporter for the given SBOM type that takes the SBOM as stream.
"""
bomtype.validate_dependency_availability()
if bomtype == SBOMType.SPDX:
from ..bomreader import SpdxBomReader
from ..bomreader.spdxbomreader import SpdxBomReader
from .spdx import SpdxGraphMLExporter

bom = SpdxBomReader.read_stream(stream)
if format == GraphOutputFormat.GRAPHML:
return SpdxGraphMLExporter(bom)
else:
from ..bomreader import CdxBomReader
from ..bomreader.cdxbomreader import CdxBomReader
from .cdx import CdxGraphMLExporter

bom = CdxBomReader.read_stream(stream)
Expand Down
6 changes: 4 additions & 2 deletions src/debsbom/generate/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
)
from ..bomwriter import BomWriter
from ..sbom import SBOMType, BOM_Standard
from .cdx import cyclonedx_bom
from .spdx import spdx_bom


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -254,6 +252,8 @@ def generate(

write_to_stdout = out == "-"
if SBOMType.CycloneDX in self.sbom_types:
from .cdx import cyclonedx_bom

cdx_out = out
if cdx_out != "-" and not cdx_out.endswith(".cdx.json"):
cdx_out += ".cdx.json"
Expand All @@ -278,6 +278,8 @@ def generate(
else:
BomWriter.write_to_file(bom, SBOMType.CycloneDX, Path(cdx_out), validate)
if SBOMType.SPDX in self.sbom_types:
from .spdx import spdx_bom

spdx_out = out
if spdx_out != "-" and not spdx_out.endswith(".spdx.json"):
spdx_out += ".spdx.json"
Expand Down
11 changes: 7 additions & 4 deletions src/debsbom/resolver/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ def create(filename: Path) -> "PackageResolver":
Factory to create a PackageResolver for the given SBOM type (based on the filename extension).
"""
if filename.name.endswith("spdx.json"):
SBOMType.SPDX.validate_dependency_availability()
from .spdx import SpdxPackageResolver
from ..bomreader import SpdxBomReader
from ..bomreader.spdxbomreader import SpdxBomReader

return SpdxPackageResolver(SpdxBomReader.read_file(filename))
elif filename.name.endswith("cdx.json"):
SBOMType.CycloneDX.validate_dependency_availability()
from .cdx import CdxPackageResolver
from ..bomreader import CdxBomReader
from ..bomreader.cdxbomreader import CdxBomReader

return CdxPackageResolver(CdxBomReader.read_file(filename))
else:
Expand All @@ -48,14 +50,15 @@ def from_stream(stream: IO, bomtype=SBOMType) -> "PackageResolver":
"""
Factory to create a PackageResolver for the given SBOM type that parses a stream.
"""
bomtype.validate_dependency_availability()
if bomtype == SBOMType.SPDX:
from .spdx import SpdxPackageResolver
from ..bomreader import SpdxBomReader
from ..bomreader.spdxbomreader import SpdxBomReader

return SpdxPackageResolver(SpdxBomReader.read_stream(stream))
else:
from .cdx import CdxPackageResolver
from ..bomreader import CdxBomReader
from ..bomreader.cdxbomreader import CdxBomReader

return CdxPackageResolver(CdxBomReader.read_stream(stream))

Expand Down
16 changes: 16 additions & 0 deletions src/debsbom/sbom.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ def from_str(bomtype: str):
return SBOMType.SPDX
raise RuntimeError(f"Unknown SBOM type '{bomtype}'")

def validate_dependency_availability(self) -> None:
"""
Check if the required imports for the SBOM type are available.
Raises RuntimeError if not available.
"""
if self == SBOMType.CycloneDX:
try:
import cyclonedx.model.component
except ModuleNotFoundError as e:
raise RuntimeError(f"Missing dependency: {e}")
elif self == SBOMType.SPDX:
try:
import spdx_tools.spdx.model.package
except ModuleNotFoundError as e:
raise RuntimeError(f"Missing dependency: {e}")


class BOM_Standard(Enum):
"""Controls the data representation and added values in the SBOM"""
Expand Down