Skip to content

Conversation

@taylormadore
Copy link
Contributor

Implement RPM architecture filtering

Maintainers will complete the following section

  • Commit messages are descriptive enough
  • Code coverage from testing does not decrease and new code is covered
  • Docs updated (if applicable)
  • Docs links in the code are still valid (if docs were updated)

Note: if the contribution is external (not from an organization member), the CI
pipeline will not run automatically. After verifying that the CI is safe to run:

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

📝 Walkthrough

Walkthrough

Introduces RPM binary-architecture filtering via a new RPMArchitectureFilter and integrates it into the RPM resolve/download flow. Signatures are extended to accept an optional RpmBinaryFilters. The download step validates and filters lockfile architectures before processing. New unit and integration tests cover filtering behavior and the updated control flow.

Changes

Cohort / File(s) Summary of Changes
RPM binary filtering core
hermeto/core/package_managers/rpm/binary_filters.py
New module adding UnsatisfiableArchitectureFilter and RPMArchitectureFilter. Implements parsing of RpmBinaryFilters, membership checks, filter, ensure_satisfiable, and validate_and_filter over LockfileArch collections.
RPM main plumbing
hermeto/core/package_managers/rpm/main.py
Threads optional binary_filter: Optional[RpmBinaryFilters] through fetch/resolve/download paths. _resolve_rpm_project and _download signatures updated; _download uses RPMArchitectureFilter(...).validate_and_filter(lockfile.arches) to limit processed architectures.
Unit tests for filtering
tests/unit/package_managers/rpm/test_binary_filters.py, tests/unit/package_managers/rpm/test_main.py
Adds tests for RPMArchitectureFilter success/error scenarios and verifies _download respects architecture filters. Updates mocks to reflect new _resolve_rpm_project/_download parameter.
Integration test harness
tests/integration/test_rpm.py
Adds a new parameterized scenario using rpm/multiple-archs with {"arch": "x86_64"} filter to validate end-to-end behavior.
Integration test data
tests/integration/test_data/rpm_multiple_archs_with_filtering/...
Adds .build-config.yaml and bom.json test fixtures for multiple-arch RPM filtering scenario.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as Caller
  participant RPM as RPM (main)
  participant Resolve as _resolve_rpm_project
  participant Download as _download
  participant Filter as RPMArchitectureFilter
  participant Repo as Repos

  User->>RPM: fetch_rpm_source(pkg, binary_filter)
  RPM->>Resolve: _resolve_rpm_project(..., binary_filter)
  Resolve->>Download: _download(lockfile, output_dir, ..., binary_filter)
  Download->>Filter: validate_and_filter(lockfile.arches)
  alt Constraints satisfiable
    Filter-->>Download: arches_to_process
    loop per arch in arches_to_process
      Download->>Repo: fetch RPM for arch
      Repo-->>Download: RPM metadata/path
    end
    Download-->>Resolve: results
  else Unsatisfiable
    Filter-->>Download: raise UnsatisfiableArchitectureFilter
    Download-->>Resolve: propagate error
  end
  Resolve-->>RPM: components / error
  RPM-->>User: result / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The PR description contains only a one-line summary plus the maintainer checklist from the template but lacks implementation details, rationale, files changed, testing/coverage evidence, CI status, and any migration or user-facing impact, so it is too terse to judge as complete. Please expand the description to include a concise overview of what changed and why, list the key files or modules modified, summarize tests run and their results (unit/integration), note any backward-compatibility or user-facing impacts, and confirm docs or coverage updates and required maintainer CI steps.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "RPM Architecture Filtering" is concise and directly reflects the primary change in the changeset—adding RPM binary architecture filtering and related plumbing—so a reviewer scanning history can quickly understand the main intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (9)
tests/integration/test_rpm.py (1)

71-80: Add a negative-case integration for an unsatisfiable arch filter

Consider a companion case with binary={"arch": "s390x"} (or any absent arch) expecting a non‑zero exit and a clear error message. This guards the user‑error path end‑to‑end.

tests/unit/package_managers/rpm/test_rpm.py (1)

596-618: Fix unused mock and strengthen the assertion

Use the patched asyncio.run to avoid Ruff ARG001 and assert it’s called exactly once (only the filtered arch is processed).

Apply this diff:

 @mock.patch("hermeto.core.package_managers.rpm.main.asyncio.run")
 def test_download_filters_architectures(
-    mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
+    mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
 ) -> None:
@@
     assert all("x86_64" in p for p in paths)
     assert not any("aarch64" in p for p in paths)
 
+    # Ensure exactly one download loop ran (only the filtered arch).
+    mock_asyncio.assert_called_once()
hermeto/core/package_managers/rpm/main.py (1)

319-325: Document the new binary_filter parameter in _download

Add a brief note so future readers don’t have to trace call sites.

Apply this diff:

 def _download(
     lockfile: RedhatRpmsLock,
     output_dir: Path,
     ssl_options: Optional[SSLOptions] = None,
     binary_filter: Optional[RpmBinaryFilters] = None,
 ) -> dict[Path, Any]:
     """
     Download packages and module metadata mentioned in the lockfile.
 
     Go through the parsed lockfile structure and find all RPM, SRPM and module metadata files.
     Create a metadata structure indexed by destination path used
     for later verification (size, checksum) after download.
     Prepare a list of files to be downloaded, and then download files.
+    If binary_filter is provided, only architectures matching the filter are processed.
     """
hermeto/core/package_managers/rpm/binary_filters.py (3)

50-53: Tidy exception construction and handle empty-available case

Move long f-strings to variables (appeases Ruff TRY003) and provide a clearer message when no arches are available.

Apply this diff:

-                raise UnsatisfiableArchitectureFilter(
-                    f"Specified RPM architecture(s) not found in lockfile: {', '.join(sorted(unsatisfiable))}",
-                    solution=f"Use one of the available architectures: {', '.join(sorted(available))}",
-                )
+                reason = "Specified RPM architecture(s) not found in lockfile: " + ", ".join(sorted(unsatisfiable))
+                available_msg = ", ".join(sorted(available)) if available else "none available in lockfile"
+                solution = f"Use one of the available architectures: {available_msg}"
+                raise UnsatisfiableArchitectureFilter(reason, solution=solution)

3-3: Remove unused logger

logging import and log are unused.

Apply this diff:

-import logging
@@
-log = logging.getLogger(__name__)

Also applies to: 11-11


40-43: Signature polish: accept Sequence to be more general

If you want to accept tuples/lists equally, consider Sequence[LockfileArch] for the parameter type.

Apply this diff:

-from typing import Any, Optional
+from typing import Any, Optional, Sequence
@@
-    def filter(self, arches: list[LockfileArch]) -> list[LockfileArch]:
+    def filter(self, arches: Sequence[LockfileArch]) -> list[LockfileArch]:
         """Filter a list of architectures based on constraints."""
         return [arch for arch in arches if arch in self]
tests/unit/package_managers/rpm/test_binary_filters.py (3)

35-39: Prefer typing.cast over type: ignore

Avoid suppressing type checking; cast the mocked list to the expected type.

Apply this diff:

-from typing import Optional
+from typing import Optional, cast
@@
-    all_arches = [mock.Mock(spec=LockfileArch, arch=arch) for arch in lockfile_arches]
-
-    filtered = arch_filter.validate_and_filter(all_arches)  # type: ignore[arg-type]
+    all_arches = [mock.Mock(spec=LockfileArch, arch=arch) for arch in lockfile_arches]
+    all_arches_typed = cast(list[LockfileArch], all_arches)
+
+    filtered = arch_filter.validate_and_filter(all_arches_typed)
     filtered_arch_strings = [arch.arch for arch in filtered]

14-28: Add edge-case coverage: ':all:' combined and whitespace handling

Strengthen guarantees around parsing by covering mixed ':all:' specs and whitespace trimming.

Apply this diff (add two params):

         pytest.param(
             RpmBinaryFilters(arch=":all:"),
             ["x86_64", "aarch64", "s390x", "ppc64le"],
             id="all_keyword",
         ),
+        pytest.param(
+            RpmBinaryFilters(arch="x86_64,:all:"),
+            ["x86_64", "aarch64", "s390x", "ppc64le"],
+            id="all_mixed_with_other",
+        ),
+        pytest.param(
+            RpmBinaryFilters(arch="x86_64, aarch64"),
+            ["x86_64", "aarch64"],
+            id="whitespace_trim",
+        ),

58-59: Assert error message content for better contract checking

Lock in user‑facing text fragment to prevent regressions.

Apply this diff:

-    with pytest.raises(UnsatisfiableArchitectureFilter):
-        _ = arch_filter.validate_and_filter(all_arches)  # type: ignore[arg-type]
+    with pytest.raises(UnsatisfiableArchitectureFilter, match="not found in lockfile"):
+        arch_filter.validate_and_filter(all_arches)  # type: ignore[arg-type]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d487264 and fddebb9.

📒 Files selected for processing (7)
  • hermeto/core/package_managers/rpm/binary_filters.py (1 hunks)
  • hermeto/core/package_managers/rpm/main.py (6 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml (1 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json (1 hunks)
  • tests/integration/test_rpm.py (1 hunks)
  • tests/unit/package_managers/rpm/test_binary_filters.py (1 hunks)
  • tests/unit/package_managers/rpm/test_rpm.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tests/integration/test_rpm.py (1)
tests/integration/utils.py (1)
  • TestParameters (66-74)
tests/unit/package_managers/rpm/test_binary_filters.py (3)
hermeto/core/models/input.py (1)
  • RpmBinaryFilters (235-238)
hermeto/core/package_managers/rpm/binary_filters.py (3)
  • RPMArchitectureFilter (18-60)
  • UnsatisfiableArchitectureFilter (14-15)
  • validate_and_filter (55-60)
hermeto/core/package_managers/rpm/redhat.py (1)
  • LockfileArch (28-41)
tests/unit/package_managers/rpm/test_rpm.py (4)
hermeto/core/models/input.py (4)
  • ExtraOptions (310-359)
  • RpmBinaryFilters (235-238)
  • RpmPackageInput (362-368)
  • SSLOptions (141-176)
tests/unit/conftest.py (1)
  • rooted_tmp_path (59-61)
hermeto/core/package_managers/rpm/redhat.py (1)
  • RedhatRpmsLock (44-83)
hermeto/core/package_managers/rpm/main.py (1)
  • _download (319-370)
hermeto/core/package_managers/rpm/binary_filters.py (4)
hermeto/core/binary_filters.py (2)
  • BinaryPackageFilter (9-30)
  • _parse_filter_spec (12-26)
hermeto/core/errors.py (1)
  • PackageRejected (84-100)
hermeto/core/models/input.py (1)
  • RpmBinaryFilters (235-238)
hermeto/core/package_managers/rpm/redhat.py (1)
  • LockfileArch (28-41)
hermeto/core/package_managers/rpm/main.py (3)
hermeto/core/models/input.py (1)
  • RpmBinaryFilters (235-238)
hermeto/core/package_managers/general.py (1)
  • async_download_files (102-148)
hermeto/core/package_managers/rpm/binary_filters.py (2)
  • RPMArchitectureFilter (18-60)
  • validate_and_filter (55-60)
🪛 Ruff (0.12.2)
tests/unit/package_managers/rpm/test_rpm.py

598-598: Unused function argument: mock_asyncio

(ARG001)

hermeto/core/package_managers/rpm/binary_filters.py

50-53: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build container image and run integration tests on it
  • GitHub Check: Konflux kflux-prd-rh03 / on-pull-request
🔇 Additional comments (11)
tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml (1)

1-2: LGTM: minimal test config is fine

Empty env and project files are appropriate for this scenario.

tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json (1)

1-27: LGTM: BOM fixture matches the filtering case

The PURL includes arch=x86_64 and checksum, which is consistent with the test intent.

tests/unit/package_managers/rpm/test_rpm.py (2)

12-12: LGTM: imports updated for binary filters

RpmBinaryFilters is correctly imported for the new tests.


361-362: LGTM: _resolve_rpm_project threads binary_filter to _download

Assertion reflects the new fourth argument (binary_filter) and preserves call order.

hermeto/core/package_managers/rpm/main.py (5)

20-25: LGTM: imports for RpmBinaryFilters and RPMArchitectureFilter

Dependency injection is clean and localized.


229-230: LGTM: binary filter is passed from package input

Forwarding package.binary keeps input flow explicit.


262-269: LGTM: _resolve_rpm_project accepts binary_filter

Signature change is clear and preserves default behavior when None.


311-313: LGTM: _resolve_rpm_project applies the filter in _download

The filter plumbs through without affecting SSL/options handling.


333-338: LGTM: validated arch filtering before download

validate_and_filter avoids silent no‑ops and provides clear errors on unsatisfiable constraints.

hermeto/core/package_managers/rpm/binary_filters.py (1)

21-25: LGTM on core filter flow

Constructor, satisfiability check, and filter pipeline read clean and match the intended semantics for ':all:' and exact arch matching.

Also applies to: 55-60

tests/unit/package_managers/rpm/test_binary_filters.py (1)

41-41: LGTM: order-preserving filter assertion

Asserting exact order verifies stable iteration over lockfile arches.

Comment on lines 26 to 33
def __contains__(self, item: Any) -> bool:
"""Return True if an architecture is allowed by the filter constraints."""
if isinstance(item, str):
arch = item
elif isinstance(item, LockfileArch):
arch = item.arch
else:
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix: contains is too strict, breaks tests with LockfileArch mocks

Duck‑type on "arch" instead of requiring isinstance(LockfileArch). Current code returns False for mocks with spec=LockfileArch, causing filtering to drop all arches in tests.

Apply this diff:

-    def __contains__(self, item: Any) -> bool:
+    def __contains__(self, item: Any) -> bool:
         """Return True if an architecture is allowed by the filter constraints."""
-        if isinstance(item, str):
-            arch = item
-        elif isinstance(item, LockfileArch):
-            arch = item.arch
-        else:
-            return False
+        if isinstance(item, str):
+            arch = item
+        elif hasattr(item, "arch"):
+            # Accept LockfileArch instances and test doubles/mocks with an `arch` attribute
+            arch = getattr(item, "arch")
+        else:
+            return False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __contains__(self, item: Any) -> bool:
"""Return True if an architecture is allowed by the filter constraints."""
if isinstance(item, str):
arch = item
elif isinstance(item, LockfileArch):
arch = item.arch
else:
return False
def __contains__(self, item: Any) -> bool:
"""Return True if an architecture is allowed by the filter constraints."""
if isinstance(item, str):
arch = item
elif hasattr(item, "arch"):
# Accept LockfileArch instances and test doubles/mocks with an `arch` attribute
arch = getattr(item, "arch")
else:
return False
🤖 Prompt for AI Agents
In hermeto/core/package_managers/rpm/binary_filters.py around lines 26 to 33,
__contains__ is too strict by requiring isinstance(item, LockfileArch) which
causes mocks with spec=LockfileArch to be treated as non-matching; change the
branch to duck-type: if item is a str use it directly, elif it has an "arch"
attribute (hasattr(item, "arch")) use item.arch, otherwise return False; ensure
no direct isinstance check against LockfileArch so mocks pass.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
hermeto/core/package_managers/rpm/binary_filters.py (1)

26-39: contains is too strict; breaks duck-typed tests and mocks

Don't require isinstance(LockfileArch); accept any object with an arch attribute.

Apply this diff:

-from hermeto.core.package_managers.rpm.redhat import LockfileArch
@@
-        if isinstance(item, str):
-            arch = item
-        elif isinstance(item, LockfileArch):
-            arch = item.arch
-        else:
-            return False
+        if isinstance(item, str):
+            arch = item
+        elif hasattr(item, "arch"):
+            # Accept LockfileArch instances and test doubles/mocks with an `arch` attribute
+            arch = getattr(item, "arch")
+        else:
+            return False

And remove the now-unused import:

-from hermeto.core.package_managers.rpm.redhat import LockfileArch
🧹 Nitpick comments (4)
tests/integration/test_rpm.py (1)

71-80: New multi-arch filtering scenario looks good

Covers the positive path for specifying a binary arch filter in integration. No issues spotted.

Consider adding a negative case (e.g., arch not present) to assert an UnsatisfiableArchitectureFilter is surfaced end-to-end.

tests/unit/package_managers/rpm/test_rpm.py (1)

596-618: Fix unused mocked argument; assert asyncio.run was invoked

Ruff flags mock_asyncio as unused (ARG001). Use it to assert the download path was exercised.

Apply this diff:

 @mock.patch("hermeto.core.package_managers.rpm.main.asyncio.run")
 def test_download_filters_architectures(
-    mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
+    mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
 ) -> None:
@@
     assert all("x86_64" in p for p in paths)
     assert not any("aarch64" in p for p in paths)
+
+    # Ensure the async download path was triggered
+    mock_asyncio.assert_called_once()
hermeto/core/package_managers/rpm/binary_filters.py (2)

40-43: Fast-path when no constraints

Return arches unmodified when accepting all to avoid needless membership checks.

Apply this diff:

 def filter(self, arches: list[LockfileArch]) -> list[LockfileArch]:
     """Filter a list of architectures based on constraints."""
-    return [arch for arch in arches if arch in self]
+    if self.arch_constraints is None:
+        return arches
+    return [arch for arch in arches if arch in self]

14-16: Tidy raise site per TRY003: move message formatting into the exception

Encapsulates long messages in the exception class; improves reuse and satisfies style checks.

Apply this diff:

 class UnsatisfiableArchitectureFilter(PackageRejected):
-    """RPM architecture filter constraints cannot be satisfied by lockfile architectures."""
+    """RPM architecture filter constraints cannot be satisfied by lockfile architectures."""
+
+    def __init__(self, unsatisfiable: set[str], available: set[str]) -> None:
+        reason = (
+            "Specified RPM architecture(s) not found in lockfile: "
+            f"{', '.join(sorted(unsatisfiable))}"
+        )
+        solution = f"Use one of the available architectures: {', '.join(sorted(available))}"
+        super().__init__(reason, solution=solution)
@@
-            if unsatisfiable:
-                raise UnsatisfiableArchitectureFilter(
-                    f"Specified RPM architecture(s) not found in lockfile: {', '.join(sorted(unsatisfiable))}",
-                    solution=f"Use one of the available architectures: {', '.join(sorted(available))}",
-                )
+            if unsatisfiable:
+                raise UnsatisfiableArchitectureFilter(unsatisfiable, available)

Also applies to: 50-53

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fddebb9 and da0e1dd.

📒 Files selected for processing (7)
  • hermeto/core/package_managers/rpm/binary_filters.py (1 hunks)
  • hermeto/core/package_managers/rpm/main.py (6 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml (1 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json (1 hunks)
  • tests/integration/test_rpm.py (1 hunks)
  • tests/unit/package_managers/rpm/test_binary_filters.py (1 hunks)
  • tests/unit/package_managers/rpm/test_rpm.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json
  • hermeto/core/package_managers/rpm/main.py
  • tests/unit/package_managers/rpm/test_binary_filters.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/integration/test_rpm.py (1)
tests/integration/utils.py (1)
  • TestParameters (66-74)
hermeto/core/package_managers/rpm/binary_filters.py (4)
hermeto/core/binary_filters.py (2)
  • BinaryPackageFilter (9-30)
  • _parse_filter_spec (12-26)
hermeto/core/errors.py (1)
  • PackageRejected (84-100)
hermeto/core/models/input.py (1)
  • RpmBinaryFilters (235-238)
hermeto/core/package_managers/rpm/redhat.py (1)
  • LockfileArch (28-41)
tests/unit/package_managers/rpm/test_rpm.py (5)
hermeto/core/models/input.py (4)
  • ExtraOptions (310-359)
  • RpmBinaryFilters (235-238)
  • RpmPackageInput (362-368)
  • SSLOptions (141-176)
tests/unit/conftest.py (1)
  • rooted_tmp_path (59-61)
hermeto/core/rooted_path.py (1)
  • RootedPath (13-122)
hermeto/core/package_managers/rpm/redhat.py (1)
  • RedhatRpmsLock (44-83)
hermeto/core/package_managers/rpm/main.py (1)
  • _download (319-370)
🪛 Ruff (0.12.2)
hermeto/core/package_managers/rpm/binary_filters.py

50-53: Avoid specifying long messages outside the exception class

(TRY003)

tests/unit/package_managers/rpm/test_rpm.py

598-598: Unused function argument: mock_asyncio

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build container image and run integration tests on it
  • GitHub Check: Konflux kflux-prd-rh03 / on-pull-request
🔇 Additional comments (2)
tests/unit/package_managers/rpm/test_rpm.py (2)

12-12: Importing RpmBinaryFilters is correct

Matches the new public API and keeps tests self-contained.


360-362: Updated _download call signature verified

Passing ssl_options=None and binary_filter=None aligns with the new function signature.

Signed-off-by: Taylor Madore <tmadore@redhat.com>
Add RPMArchitectureFilter class to assist with filtering RPM requests
based on user-specified architecture constraints. This builds on the
binary filtering framework established in earlier commits.

Signed-off-by: Taylor Madore <tmadore@redhat.com>
Assisted-by: Claude Sonnet 4
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
hermeto/core/package_managers/rpm/binary_filters.py (1)

23-35: contains is too strict; duck‑type on arch to support mocks and test doubles.
Using isinstance(item, LockfileArch) breaks tests that pass mocks with an arch attribute.

Apply this diff:

-        if isinstance(item, str):
-            arch = item
-        elif isinstance(item, LockfileArch):
-            arch = item.arch
-        else:
-            return False
+        if isinstance(item, str):
+            arch = item
+        elif hasattr(item, "arch"):
+            # Accept LockfileArch instances and test doubles/mocks with an `arch` attribute
+            arch = getattr(item, "arch")
+        else:
+            return False
🧹 Nitpick comments (3)
tests/unit/package_managers/rpm/test_main.py (1)

596-599: Silence unused patched argument to satisfy Ruff ARG001.
Rename the unused mock_asyncio parameter to _mock_asyncio (or use it) to avoid ARG001.

Apply this diff:

 @mock.patch("hermeto.core.package_managers.rpm.main.asyncio.run")
 def test_download_filters_architectures(
-    mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
+    _mock_asyncio: mock.Mock, rooted_tmp_path: RootedPath
 ) -> None:
hermeto/core/package_managers/rpm/main.py (1)

333-335: Architecture filtering applied before download.
Consider adding a debug log to trace which arches were selected.

Apply this diff:

 arch_filter = RPMArchitectureFilter(binary_filter)
 arches_to_process = arch_filter.validate_and_filter(lockfile.arches)
+log.debug("Filtered architectures to process: %s", [a.arch for a in arches_to_process])
hermeto/core/package_managers/rpm/binary_filters.py (1)

47-50: Minor: long error message assembly inside raise.
Style-only: consider moving formatting into the exception class or a helper to satisfy TRY003.

Example:

-class UnsatisfiableArchitectureFilter(PackageRejected):
-    """RPM architecture filter constraints cannot be satisfied by lockfile architectures."""
+class UnsatisfiableArchitectureFilter(PackageRejected):
+    """RPM architecture filter constraints cannot be satisfied by lockfile architectures."""
+    def __init__(self, missing: set[str], available: set[str]) -> None:
+        super().__init__(
+            f"Specified RPM architecture(s) not found in lockfile: {', '.join(sorted(missing))}",
+            solution=f"Use one of the available architectures: {', '.join(sorted(available))}",
+        )

and then:

-                raise UnsatisfiableArchitectureFilter(
-                    f"Specified RPM architecture(s) not found in lockfile: {', '.join(sorted(unsatisfiable))}",
-                    solution=f"Use one of the available architectures: {', '.join(sorted(available))}",
-                )
+                raise UnsatisfiableArchitectureFilter(unsatisfiable, available)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da0e1dd and 94b818c.

📒 Files selected for processing (7)
  • hermeto/core/package_managers/rpm/binary_filters.py (1 hunks)
  • hermeto/core/package_managers/rpm/main.py (6 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml (1 hunks)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json (1 hunks)
  • tests/integration/test_rpm.py (1 hunks)
  • tests/unit/package_managers/rpm/test_binary_filters.py (1 hunks)
  • tests/unit/package_managers/rpm/test_main.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/bom.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/integration/test_data/rpm_multiple_archs_with_filtering/.build-config.yaml
  • tests/integration/test_rpm.py
  • tests/unit/package_managers/rpm/test_binary_filters.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/unit/package_managers/rpm/test_main.py (2)
hermeto/core/models/input.py (4)
  • ExtraOptions (310-359)
  • RpmBinaryFilters (235-238)
  • RpmPackageInput (362-368)
  • SSLOptions (141-176)
hermeto/core/package_managers/rpm/main.py (1)
  • _download (319-370)
hermeto/core/package_managers/rpm/main.py (3)
hermeto/core/models/input.py (2)
  • RpmBinaryFilters (235-238)
  • SSLOptions (141-176)
hermeto/core/package_managers/general.py (1)
  • async_download_files (102-148)
hermeto/core/package_managers/rpm/binary_filters.py (2)
  • RPMArchitectureFilter (15-57)
  • validate_and_filter (52-57)
hermeto/core/package_managers/rpm/binary_filters.py (4)
hermeto/core/binary_filters.py (2)
  • BinaryPackageFilter (9-30)
  • _parse_filter_spec (12-26)
hermeto/core/errors.py (1)
  • PackageRejected (84-100)
hermeto/core/models/input.py (1)
  • RpmBinaryFilters (235-238)
hermeto/core/package_managers/rpm/redhat.py (1)
  • LockfileArch (28-41)
🪛 Ruff (0.12.2)
tests/unit/package_managers/rpm/test_main.py

598-598: Unused function argument: mock_asyncio

(ARG001)

hermeto/core/package_managers/rpm/binary_filters.py

47-50: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build container image and run integration tests on it
  • GitHub Check: Konflux kflux-prd-rh03 / on-pull-request
🔇 Additional comments (8)
tests/unit/package_managers/rpm/test_main.py (3)

12-12: Import of RpmBinaryFilters is appropriate for new filtering tests.
Looks good.


359-362: Updated _download call signature assertion is correct.
The additional binary_filter argument is properly accounted for in the expectation.


612-617: Good validation of architecture filtering behavior.
The assertions tightly verify that only the selected arch makes it into metadata.

hermeto/core/package_managers/rpm/main.py (5)

20-25: Plumbing: import additions align with new filtering flow.
No issues spotted.


229-230: Thread binary_filter through fetch path.
Forwarding package.binary ensures per‑package filters are honored.


312-313: Passing binary_filter to _download is correct.
Keeps filter semantics centralized in the download step.


337-338: Looping only over filtered arches is correct.
This confines downloads and repo creation to the intended set.


267-268: Signature extended with binary_filter — no call-site changes required. binary_filter is Optional with default None; grep shows remaining two-arg callers are tests (compatible) and callers that need the filter already pass it.

@taylormadore taylormadore added this pull request to the merge queue Sep 17, 2025
Merged via the queue into hermetoproject:main with commit 9657a87 Sep 17, 2025
14 checks passed
@taylormadore taylormadore deleted the rpm-platform-filtering branch September 17, 2025 13:33
@taylormadore taylormadore linked an issue Sep 22, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Implement RPM platform filtering

3 participants