diff --git a/sbom/test_update_component_sbom.py b/sbom/test_update_component_sbom.py index cabdca9..8b95c50 100644 --- a/sbom/test_update_component_sbom.py +++ b/sbom/test_update_component_sbom.py @@ -11,7 +11,6 @@ class TestUpdateComponentSBOM(unittest.TestCase): - def test_get_component_to_purls_map(self) -> None: release_note_images = [ {"component": "comp1", "purl": "purl1"}, @@ -65,7 +64,7 @@ def test_update_spdx_sbom(self) -> None: { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "purl1", + "referenceLocator": "pkg:oci/package@sha256:123", } ], }, @@ -75,19 +74,26 @@ def test_update_spdx_sbom(self) -> None: { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "purl2", + "referenceLocator": "pkg:oci/package@sha256:456", } ], }, ] } mapping = { - "comp1": ["updated_purl1", "updated_purl2"], - "comp2": ["updated_purl3", "updated_purl4"], + "comp1": [ + "pkg:oci/package@sha256:123?repository_url=quay.io/foo/bar", + "pkg:oci/package@sha256:234?repository_url=quay.io/foo/bar", + ], + "comp2": [ + "pkg:oci/package@sha256:456?repository_url=quay.io/foo/bar", + "pkg:oci/package@sha256:567?repository_url=quay.io/foo/bar", + ], } update_spdx_sbom(sbom, mapping) assert sbom == { + "name": "quay.io/foo/bar@sha256:456", "packages": [ { "name": "comp1", @@ -95,17 +101,19 @@ def test_update_spdx_sbom(self) -> None: { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "purl1", + "referenceLocator": "pkg:oci/package@sha256:123", }, { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "updated_purl1", + "referenceLocator": "pkg:oci/package@sha256:123" + "?repository_url=quay.io/foo/bar", }, { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "updated_purl2", + "referenceLocator": "pkg:oci/package@sha256:234" + "?repository_url=quay.io/foo/bar", }, ], }, @@ -115,21 +123,23 @@ def test_update_spdx_sbom(self) -> None: { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "purl2", + "referenceLocator": "pkg:oci/package@sha256:456", }, { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "updated_purl3", + "referenceLocator": "pkg:oci/package@sha256:456" + "?repository_url=quay.io/foo/bar", }, { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "updated_purl4", + "referenceLocator": "pkg:oci/package@sha256:567" + "?repository_url=quay.io/foo/bar", }, ], }, - ] + ], } @patch("update_component_sbom.glob.glob") @@ -143,7 +153,6 @@ def test_update_sboms_with_cyclonedex_format( mock_mapping: MagicMock, mock_glob: MagicMock, ) -> None: - # combining the content of data.json and sbom, since there can only be one read_data # defined in the mock_open test_cyclonedx_sbom = {"bomFormat": "CycloneDX", "releaseNotes": {"images": "foo"}} diff --git a/sbom/update_component_sbom.py b/sbom/update_component_sbom.py index 399df34..4b920f8 100755 --- a/sbom/update_component_sbom.py +++ b/sbom/update_component_sbom.py @@ -5,10 +5,12 @@ import argparse import glob import json +import logging import os from collections import defaultdict from typing import DefaultDict, Dict, List -import logging + +from packageurl import PackageURL LOG = logging.getLogger("update_component_sbom") @@ -54,9 +56,27 @@ def update_cyclonedx_sbom(sbom: Dict, component_to_purls_map: Dict[str, List[str component["purl"] = component_to_purls_map[component["name"]][0] +def get_image_pullspec_from_purl(purl: str) -> str: + """ + Parse the image purl to get the image pullspec. + + The pullspec is made of the repository URL and digest that is available as + the version in the purl. + + Args: + purl (str): A package URL for an image. + + Returns: + str: Image pullspec with repository URL and digest. + """ + parsed_purl = PackageURL.from_string(purl) + repository = parsed_purl.qualifiers["repository_url"] + return f"{repository}@{parsed_purl.version}" + + def update_spdx_sbom(sbom: Dict, component_to_purls_map: Dict[str, List[str]]) -> None: """ - Update the purl in an SBOM with SPDX format + Update the purl in an SBOM with SPDX format and set the SBOM document name Args: sbom: SPDX SBOM file to update. component_to_purls_map: dictionary mapping of component names to list of purls. @@ -64,16 +84,20 @@ def update_spdx_sbom(sbom: Dict, component_to_purls_map: Dict[str, List[str]]) - LOG.info("Updating SPDX sbom") for package in sbom["packages"]: if package["name"] in component_to_purls_map: + purls = component_to_purls_map[package["name"]] purl_external_refs = [ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": purl, } - for purl in component_to_purls_map[package["name"]] + for purl in purls ] package["externalRefs"].extend(purl_external_refs) + # Set the SBOM document name to the first image pullspec + sbom["name"] = get_image_pullspec_from_purl(purls[0]) + def update_sboms(data_path: str, input_path: str, output_path: str) -> None: """