diff --git a/README.md b/README.md index 52b7873..23d6b46 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ from siemens_standard_bom.model import StandardBom, Component, ComponentType from cyclonedx.model.contact import OrganizationalContact bom = StandardBom() +bom.component=Component(name='Sample Application', version='1.0.0', type=ComponentType.APPLICATION) bom.add_author(OrganizationalContact(name='John Doe')) bom.add_tool(Component(name='Sample Tool', version='1.0.0', type=ComponentType.APPLICATION)) bom.add_component(Component(name='Sample Component', version='1.2.3', type=ComponentType.LIBRARY)) @@ -91,6 +92,7 @@ from siemens_standard_bom.model import StandardBom, Component, ComponentType, Sb from cyclonedx.model.contact import OrganizationalContact bom = StandardBom() +bom.component=SbomComponent(Component(name='Sample Application', version='1.0.0', type=ComponentType.APPLICATION)) bom.add_author(OrganizationalContact(name='John Doe')) bom.add_tool(SbomComponent(Component(name='Sample Tool', version='1.0.0', type=ComponentType.APPLICATION))) bom.add_component(SbomComponent(Component(name='Sample Component', version='1.2.3', type=ComponentType.LIBRARY))) diff --git a/pyproject.toml b/pyproject.toml index 1ece143..b1aec7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "siemens-standard-bom" -version = "4.1.0" +version = "4.2.0" description = "Standard BOM Format Library" keywords = ["sbom", "software-bill-of-materials", "cyclonedx", "cdx"] authors = [ diff --git a/siemens_standard_bom/model.py b/siemens_standard_bom/model.py index 0c18a96..50be38a 100644 --- a/siemens_standard_bom/model.py +++ b/siemens_standard_bom/model.py @@ -555,6 +555,7 @@ def __init__(self, bom: Optional[Bom] = None) -> None: self.bom = bom self._insert_standard_bom_tools_entry_if_missing() self._insert_standard_bom_definitions_entry_if_missing() + self._insert_standard_bom_metadata_component_entry_if_missing() self._set_supplier_if_missing() def _insert_standard_bom_tools_entry_if_missing(self) -> None: @@ -604,6 +605,19 @@ def _set_supplier_if_missing(self) -> None: if not self.bom.metadata.supplier: self.bom.metadata.supplier = OrganizationalEntity(name='Siemens or its Affiliates') + def _insert_standard_bom_metadata_component_entry_if_missing(self) -> None: + """ + Ensures that self.bom.metadata.component exists. + If it doesn't exist, creates a minimal Component object with required information. + Users should update the name and version with actual project values. + """ + if self.bom.metadata.component is None: + self.bom.metadata.component = Component( + name='Unknown Name', + version='0.0.0', + type=ComponentType.APPLICATION + ) + def _set_metadata_property(self, property_name: str, value: Optional[str | None]) -> None: existing = next(filter(lambda p: p.name == property_name, self.bom.metadata.properties), None) diff --git a/tests/test_model_standard_bom.py b/tests/test_model_standard_bom.py index 60fe3fc..8fb65cd 100644 --- a/tests/test_model_standard_bom.py +++ b/tests/test_model_standard_bom.py @@ -5,6 +5,7 @@ from importlib.metadata import version from cyclonedx.model import ExternalReference, ExternalReferenceType, XsUri +from cyclonedx.model.bom import Bom from cyclonedx.model.component import ComponentType, Component from cyclonedx.model.contact import OrganizationalContact from sortedcontainers import SortedSet @@ -235,3 +236,68 @@ def test_sbom_external_components_is_iterable(self) -> None: sbom.add_external_component(external) for comp in sbom.external_components: self.assertEqual(comp.reference, external) + + def test_metadata_component_is_created_when_missing(self) -> None: + bom = Bom() + bom.metadata.component = None + + sbom = StandardBom(bom) + sbom._insert_standard_bom_metadata_component_entry_if_missing() # noqa: SLF001 + self.assertIsNotNone(sbom.bom.metadata.component) + self.assertEqual('Unknown Name', sbom.bom.metadata.component.name) + self.assertEqual('0.0.0', sbom.bom.metadata.component.version) + self.assertEqual(ComponentType.APPLICATION, sbom.bom.metadata.component.type) + + def test_metadata_component_is_not_overwritten_when_exists(self) -> None: + bom = Bom() + existing_component = Component( + name='MyApplication', + version='1.2.3', + type=ComponentType.APPLICATION + ) + bom.metadata.component = existing_component + + sbom = StandardBom(bom) + sbom._insert_standard_bom_metadata_component_entry_if_missing() # noqa: SLF001 + self.assertIsNotNone(sbom.bom.metadata.component) + self.assertEqual('MyApplication', sbom.bom.metadata.component.name) + self.assertEqual('1.2.3', sbom.bom.metadata.component.version) + self.assertEqual(ComponentType.APPLICATION, sbom.bom.metadata.component.type) + + def test_metadata_component_property_get_when_exists(self) -> None: + bom = Bom() + test_component = Component( + name='TestApp', + version='2.0.0', + type=ComponentType.LIBRARY + ) + bom.metadata.component = test_component + + sbom = StandardBom(bom) + self.assertIsNotNone(sbom.component) + self.assertEqual('TestApp', sbom.component.name) + self.assertEqual('2.0.0', sbom.component.version) + self.assertEqual(ComponentType.LIBRARY, sbom.component.type) + + def test_metadata_component_property_get_when_missing(self) -> None: + bom = Bom() + bom.metadata.component = None + + sbom = StandardBom(bom) + self.assertIsNotNone(sbom.component) + self.assertEqual('Unknown Name', sbom.component.name) + self.assertEqual('0.0.0', sbom.component.version) + + def test_metadata_component_property_set(self) -> None: + sbom = StandardBom() + new_component = SbomComponent(Component( + name='SetApp', + version='3.0.0', + type=ComponentType.APPLICATION + )) + + sbom.component = new_component + self.assertIsNotNone(sbom.component) + self.assertEqual('SetApp', sbom.component.name) + self.assertEqual('3.0.0', sbom.component.version) + self.assertEqual(ComponentType.APPLICATION, sbom.component.type)