From bdc850d36f6f0a976128d6bc65e715d999e177b4 Mon Sep 17 00:00:00 2001 From: Alexey Ovchinnikov Date: Tue, 3 Dec 2024 21:41:43 -0600 Subject: [PATCH] Adding optional RPM summary to SBOMs This change adds an option to include RPM summary in a SBOM. Signed-off-by: Alexey Ovchinnikov --- cachi2/core/models/input.py | 1 + cachi2/core/models/property_semantics.py | 8 ++++++++ cachi2/core/models/sbom.py | 1 + cachi2/core/package_managers/rpm/main.py | 24 ++++++++++++++++++++---- tests/unit/models/test_input.py | 5 +++++ tests/unit/package_managers/test_rpm.py | 2 +- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/cachi2/core/models/input.py b/cachi2/core/models/input.py index 5a4a39269..19a921ccc 100644 --- a/cachi2/core/models/input.py +++ b/cachi2/core/models/input.py @@ -216,6 +216,7 @@ class RpmPackageInput(_PackageInputBase): """Accepted input for a rpm package.""" type: Literal["rpm"] + add_rpm_summary: bool = False options: Optional[ExtraOptions] = None diff --git a/cachi2/core/models/property_semantics.py b/cachi2/core/models/property_semantics.py index 6261b9a77..d2f0b4aca 100644 --- a/cachi2/core/models/property_semantics.py +++ b/cachi2/core/models/property_semantics.py @@ -34,6 +34,7 @@ class PropertySet: npm_development: bool = False pip_package_binary: bool = False bundler_package_binary: bool = False + rpm_summary: str = "" @classmethod def from_properties(cls, props: Iterable[Property]) -> "Self": @@ -44,6 +45,7 @@ def from_properties(cls, props: Iterable[Property]) -> "Self": npm_development = False pip_package_binary = False bundler_package_binary = False + rpm_summary = "" for prop in props: if prop.name == "cachi2:found_by": @@ -58,6 +60,8 @@ def from_properties(cls, props: Iterable[Property]) -> "Self": pip_package_binary = True elif prop.name == "cachi2:bundler:package:binary": bundler_package_binary = True + elif prop.name == "cachi2:rpm_summary": + rpm_summary = "" else: assert_never(prop.name) @@ -68,6 +72,7 @@ def from_properties(cls, props: Iterable[Property]) -> "Self": npm_development, pip_package_binary, bundler_package_binary, + rpm_summary, ) def to_properties(self) -> list[Property]: @@ -87,6 +92,8 @@ def to_properties(self) -> list[Property]: props.append(Property(name="cachi2:pip:package:binary", value="true")) if self.bundler_package_binary: props.append(Property(name="cachi2:bundler:package:binary", value="true")) + if self.rpm_summary: + props.append(Property(name="cachi2:rpm_summary", value=self.rpm_summary)) return sorted(props, key=lambda p: (p.name, p.value)) @@ -100,4 +107,5 @@ def merge(self, other: "Self") -> "Self": npm_development=self.npm_development and other.npm_development, pip_package_binary=self.pip_package_binary or other.pip_package_binary, bundler_package_binary=self.bundler_package_binary or other.bundler_package_binary, + rpm_summary=self.rpm_summary or other.rpm_summary, ) diff --git a/cachi2/core/models/sbom.py b/cachi2/core/models/sbom.py index 0152513e3..de9781f7e 100644 --- a/cachi2/core/models/sbom.py +++ b/cachi2/core/models/sbom.py @@ -7,6 +7,7 @@ PropertyName = Literal[ "cachi2:bundler:package:binary", "cachi2:found_by", + "cachi2:rpm_summary", "cachi2:missing_hash:in_file", "cachi2:pip:package:binary", "cdx:npm:package:bundled", diff --git a/cachi2/core/package_managers/rpm/main.py b/cachi2/core/package_managers/rpm/main.py index 0fd230aa1..a69ba2482 100644 --- a/cachi2/core/package_managers/rpm/main.py +++ b/cachi2/core/package_managers/rpm/main.py @@ -47,6 +47,7 @@ class Package: vendor: Optional[str] = None checksum: Optional[str] = None repository_id: Optional[str] = None + summary: Optional[str] = None @classmethod def from_filepath(cls, rpm_filepath: Path, rpm_download_metadata: dict[str, Any]) -> "Package": @@ -85,6 +86,7 @@ def _query_rpm_fields(file_path: Path) -> dict[str, str]: "version=%{VERSION}\n" "release=%{RELEASE}\n" "arch=%{ARCH}\n" + "summary=%|SUMMARY?{%{SUMMARY}}:{}|\n" # vendor and epoch are optional RPM tags; return "" if not set instead of "(None)" "vendor=%|VENDOR?{%{VENDOR}}:{}|\n" "epoch=%|EPOCH?{%{EPOCH}}:{}|\n" @@ -205,7 +207,14 @@ def fetch_rpm_source(request: Request) -> RequestOutput: for package in request.rpm_packages: path = request.source_dir.join_within_root(package.path) - components.extend(_resolve_rpm_project(path, request.output_dir, options=package.options)) + components.extend( + _resolve_rpm_project( + path, + request.output_dir, + options=package.options, + add_rpm_summary=package.add_rpm_summary, + ) + ) # FIXME: this is only ever good enough for a PoC, but needs to be handled properly in the # future. @@ -240,6 +249,7 @@ def _resolve_rpm_project( source_dir: RootedPath, output_dir: RootedPath, options: Optional[ExtraOptions] = None, + add_rpm_summary: bool = False, ) -> list[Component]: """ Process a request for a single RPM source directory. @@ -288,7 +298,7 @@ def _resolve_rpm_project( _verify_downloaded(metadata) lockfile_relative_path = source_dir.subpath_from_root / DEFAULT_LOCKFILE_NAME - return _generate_sbom_components(metadata, lockfile_relative_path) + return _generate_sbom_components(metadata, lockfile_relative_path, add_rpm_summary) def _download( @@ -383,14 +393,20 @@ def _is_rpm_file(file_path: Path) -> bool: def _generate_sbom_components( - files_metadata: dict[Path, Any], lockfile_path: Path + files_metadata: dict[Path, Any], + lockfile_path: Path, + add_rpm_summary: bool = False, ) -> list[Component]: components = [] for file_path, file_metadata in files_metadata.items(): if not _is_rpm_file(file_path): continue package = Package.from_filepath(file_path, file_metadata) - components.append(package.to_component(lockfile_path)) + component = package.to_component(lockfile_path) + if add_rpm_summary: + summary = Property(name="cachi2:rpm_summary", value=str(package.summary)) + component.properties.append(summary) + components.append(component) return components diff --git a/tests/unit/models/test_input.py b/tests/unit/models/test_input.py index ebfe54f10..76a685e73 100644 --- a/tests/unit/models/test_input.py +++ b/tests/unit/models/test_input.py @@ -69,6 +69,7 @@ class TestPackageInput: "type": "rpm", "path": Path("."), "options": None, + "add_rpm_summary": False, }, ), ( @@ -80,6 +81,7 @@ class TestPackageInput: "foorepo": {"arch": "x86_64", "enabled": True}, } }, + "add_rpm_summary": False, }, { "type": "rpm", @@ -91,6 +93,7 @@ class TestPackageInput: }, "ssl": None, }, + "add_rpm_summary": False, }, ), ( @@ -110,6 +113,7 @@ class TestPackageInput: "ssl_verify": False, }, }, + "add_rpm_summary": False, }, ), ( @@ -138,6 +142,7 @@ class TestPackageInput: "ssl_verify": False, }, }, + "add_rpm_summary": False, }, ), ], diff --git a/tests/unit/package_managers/test_rpm.py b/tests/unit/package_managers/test_rpm.py index a2b325d17..1255c95a8 100644 --- a/tests/unit/package_managers/test_rpm.py +++ b/tests/unit/package_managers/test_rpm.py @@ -361,7 +361,7 @@ def test_resolve_rpm_project( mock_model_validate.return_value, mock_package_dir_path, None ) mock_verify_downloaded.assert_called_once_with({}) - mock_generate_sbom_components.assert_called_once_with({}, Path("rpms.lock.yaml")) + mock_generate_sbom_components.assert_called_once_with({}, Path("rpms.lock.yaml"), False) @mock.patch("cachi2.core.package_managers.rpm.main.run_cmd")