Skip to content

Conversation

@mulkieran
Copy link
Member

@mulkieran mulkieran commented Aug 25, 2025

Related stratis-storage/stratisd#3597

Supercedes #1186

Summary by CodeRabbit

  • New Features

    • CLI: add pool encryption commands — on, off, reencrypt — with Clevis (Tang/NBDE), TPM2, and kernel keyring support; requires explicit --in-place for long-running in-place operations and prints confirmation when operations are initiated.
    • Pool listing: shows “Encryption Enabled: Last Time Reencrypted” when applicable.
  • Documentation

    • Updated docs covering new commands, --in-place implications, and output fields.
  • Tests

    • Expanded unit and integration tests for encryption flows, parsing, and long-running behavior.
  • Chores

    • PR-triggered Copr builds now auto-start; build instrumentation coverage expanded.

@mulkieran mulkieran self-assigned this Aug 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 25, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds whole-pool encryption commands (on/off/reencrypt) with an in-place requirement, implements CryptActions and new D-Bus pool methods, updates CLI parser and docs, records last reencrypt timestamp in listings, introduces long-running-operation handling for DBus NoReply, lowers DBus timeout, adjusts tests and build instrumentation, and enables PR Copr builds.

Changes

Cohort / File(s) Summary
Build & CI
.packit.yaml, Makefile
Removed manual_trigger: true from the Copr PR job; added stratis_cli._actions._crypt to MONKEYTYPE_MODULES.
New encryption actions & API surface
src/stratis_cli/_actions/_crypt.py, src/stratis_cli/_actions/__init__.py, src/stratis_cli/_actions/_introspect.py
New CryptActions with encrypt, unencrypt, reencrypt; added DBus introspection methods EncryptPool, DecryptPool, ReencryptPool and property LastReencryptedTimestamp; removed EmitChangedSignal annotation on Encrypted; re-exported CryptActions.
CLI parser and shared args
src/stratis_cli/_parser/_encryption.py, src/stratis_cli/_parser/_shared.py, src/stratis_cli/_parser/_pool.py
Added encryption on/off/reencrypt subcommands wired to CryptActions; introduced CLEVIS_AND_KERNEL and IN_PLACE shared arg groups; pool create now uses CLEVIS_AND_KERNEL.
CLI listing & errors
src/stratis_cli/_actions/_list_pool.py, src/stratis_cli/_errors.py, src/stratis_cli/_actions/_utils.py, src/stratis_cli/_actions/_data.py
List view prints Last Time Reencrypted when applicable; added StratisCliInPlaceNotSpecified error; added get_errors and long_running_operation decorator to swallow DBus NoReply; reduced default DBus timeout from 120s to 60s.
Error reporting wiring
src/stratis_cli/_error_reporting.py
get_errors moved into actions/utils and is re-exported from error_reporting.
Docs
docs/stratis.txt
New pool encryption command group and docs for on/off/reencrypt, in-place behavior, and new listing field.
Tests
tests/whitebox/integration/pool/test_encryption.py, tests/whitebox/integration/test_parser.py, tests/whitebox/unit/test_running.py
Added/updated integration tests covering encryption commands, in-place enforcement, name-based targeting, reencrypt state, and parser clevis checks; added unit tests for long_running_operation decorator behavior.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CLI as stratis-cli
  participant Parser as ArgParser
  participant Act as CryptActions
  participant D as stratisd (DBus)

  U->>CLI: pool encryption on --in-place --name <pool> [--key-desc/--clevis...]
  CLI->>Parser: parse args
  Parser-->>CLI: Namespace
  CLI->>Act: CryptActions.encrypt(namespace)
  Act->>Act: validate --in-place, resolve pool state
  Act->>D: EncryptPool(key_descs, clevis_infos)
  alt synchronous reply
    D-->>Act: results, return_code, return_string
    Act-->>CLI: success or raises error
    CLI-->>U: prints result
  else asynchronous (DBus NoReply)
    D--x Act: NoReply
    Act->>Act: long_running_operation swallows NoReply
    Act-->>CLI: writes "Operation initiated" to stderr
    CLI-->>U: exits without error
  end
Loading
sequenceDiagram
  autonumber
  participant CLI as stratis-cli
  participant Act as CryptActions
  participant D as stratisd (DBus)

  CLI->>Act: CryptActions.unencrypt(namespace with --in-place)
  Act->>D: DecryptPool()
  alt pool not encrypted / no-change
    D-->>Act: return_code indicates no change
    Act-->>CLI: raise StratisCliNoChangeError
  else success
    D-->>Act: results true
    Act-->>CLI: success
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • drckeefe
  • jbaublitz

Poem

I twitch my ears at newly spun keys,
I hop through args and DBus breeze.
On, off, rotate — we do it in place,
If NoReply comes, I save you face.
A timestamp gleams where carrots hide — secure and cozy inside. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Support for encryption enhancements" concisely and accurately reflects the primary changes in this PR—adding pool encryption features across the CLI, actions, introspection, docs, and tests—while remaining specific and free of noisy details or file lists.

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.

@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo yum install -y dnf-plugins-core on RHEL 8
  • sudo dnf install -y dnf-plugins-core on Fedora
  • dnf copr enable packit/stratis-storage-stratis-cli-1207-copr_pull
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

@mulkieran
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (20)
src/stratis_cli/_actions/_list_pool.py (1)

364-371: Make timestamp parsing robust; optionally remove external dependency

Current code assumes a valid ISO timestamp when valid is True. A malformed value would raise and break the detail view. Consider guarding the parse and, if desired, switch to stdlib parsing to avoid the extra dependency.

Option A (keep dateutil, handle parse errors):

-            reencrypted = (
-                date_parser.isoparse(timestamp).astimezone().strftime("%b %d %Y %H:%M")
-                if valid
-                else "Never"
-            )
+            if valid:
+                try:
+                    reencrypted = date_parser.isoparse(timestamp).astimezone().strftime("%b %d %Y %H:%M")
+                except Exception:  # pragma: no cover
+                    reencrypted = "<INVALID TIMESTAMP>"
+            else:
+                reencrypted = "Never"

Option B (drop dependency; use stdlib):

- from dateutil import parser as date_parser
+ from datetime import datetime

@@
-            reencrypted = (
-                date_parser.isoparse(timestamp).astimezone().strftime("%b %d %Y %H:%M")
-                if valid
-                else "Never"
-            )
+            if valid:
+                try:
+                    ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
+                    reencrypted = ts.astimezone().strftime("%b %d %Y %H:%M")
+                except Exception:  # pragma: no cover
+                    reencrypted = "<INVALID TIMESTAMP>"
+            else:
+                reencrypted = "Never"
src/stratis_cli/_actions/_utils.py (2)

277-287: Address Ruff B009 and simplify exception chaining

getattr with constant attribute names is not safer and triggers B009. Direct attribute access is clearer and marginally faster.

Apply:

-    while True:
-        yield exc
-        exc = getattr(exc, "__cause__") or getattr(exc, "__context__")
-        if exc is None:
-            return
+    while True:
+        yield exc
+        exc = exc.__cause__ or exc.__context__
+        if exc is None:
+            return

289-308: Preserve return value and generalize wrapper signature

Two small improvements:

  • Return the wrapped function’s result to preserve semantics.
  • Accept *args, **kwargs to match the wrapped callable’s signature (still works for Namespace).

Proposed diff:

-    @wraps(func)
-    def wrapper(namespace: Namespace):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
         try:
-            func(namespace)
+            return func(*args, **kwargs)
         except DPClientInvocationError as err:
             if not any(
                 isinstance(e, DBusException)
                 and e.get_dbus_name() == "org.freedesktop.DBus.Error.NoReply"
                 for e in get_errors(err)
             ):
                 raise err
             print("Operation initiated", file=sys.stderr)
+            return None
tests/whitebox/unit/test_running.py (2)

37-51: Optional: assert the user-facing stderr message as part of the contract

The wrapper’s contract includes printing “Operation initiated” on NoReply. Asserting stderr strengthens the test against regressions.

Example patch:

+        import io
+        import contextlib
@@
-        self.assertIsNone(long_running_operation(raises_error)(None))
+        buf = io.StringIO()
+        with contextlib.redirect_stderr(buf):
+            self.assertIsNone(long_running_operation(raises_error)(None))
+        self.assertIn("Operation initiated", buf.getvalue())

52-61: Good negative-path coverage

This test correctly ensures non-NoReply DPClientInvocationError propagates. Consider a third case with a DBusException of a different name to pin the predicate more tightly, but not strictly necessary.

Makefile (1)

7-10: Optional: keep MONKEYTYPE_MODULES list sorted to reduce merge conflicts

Not required, but keeping the module list sorted alphabetically tends to minimize diffs and future merge noise.

 MONKEYTYPE_MODULES = stratis_cli._actions._bind \
                      stratis_cli._actions._constants \
-                     stratis_cli._actions._crypt \
+                     stratis_cli._actions._crypt \
                      stratis_cli._actions._data \
src/stratis_cli/_actions/__init__.py (2)

20-20: Resolve Ruff F401 for CryptActions re-export

This import exists to re-export, but Ruff flags it as unused. Either add a local # noqa: F401 or, preferably, define __all__ to explicitly export it.

-from ._crypt import CryptActions
+from ._crypt import CryptActions  # noqa: F401

Alternatively (preferred): define __all__ to document the public API. Example snippet to add near the end of this module:

# Public re-exports for consumers of stratis_cli._actions
__all__ = (
    "BindActions",
    "RebindActions",
    "BLOCKDEV_INTERFACE",
    "FILESYSTEM_INTERFACE",
    "POOL_INTERFACE",
    "CryptActions",
    "LogicalActions",
    "PhysicalActions",
    "PoolActions",
    "StratisActions",
    "check_stratisd_version",
    "TopActions",
    "get_errors",
)

33-34: Resolve Ruff F401 for get_errors re-export

Same as above; mark as intentional re-export or add to __all__.

-from ._utils import get_errors
+from ._utils import get_errors  # noqa: F401
tests/whitebox/integration/test_parser.py (1)

251-256: Apply Clevis negative cases to encryption-on — LGTM; consider also covering forbidden Clevis flags for other subcommands

Good to extend these cases to pool encryption on. If the parser forbids Clevis options on encryption off and encryption reencrypt (as expected), consider adding a couple of negative tests to lock that behavior in.

         for subcommand in [
             ["pool", "create", "pn", "/dev/n"],
             ["pool", "encryption", "on", "--name=pn"],
+            # Clevis options should be rejected on these subcommands as well:
+            ["pool", "encryption", "off", "--name=pn"],
+            ["pool", "encryption", "reencrypt", "--name=pn"],
         ]:
             self._do_test(subcommand + clevis_args)
src/stratis_cli/_parser/_shared.py (1)

278-299: CLEVIS_AND_KERNEL constant looks correct and consistent with parser expectations

  • --key-desc, --clevis (type Clevis, choices list(Clevis)), and --tang-url are captured here; verification flags remain in TRUST_URL_OR_THUMBPRINT, which is appropriate.
  • Help strings are clear and align with docs.

Optional nits:

  • Consider adding metavar hints for readability, e.g., "--tang-url ", "--key-desc <key_desc>" to match the docs synopsis.
docs/stratis.txt (1)

277-289: Use “cannot” instead of “can not” for consistency

Minor wording improvement matching the project’s existing style elsewhere.

-        for example, extending filesystems, can not be taken on the pool.
+        for example, extending filesystems, cannot be taken on the pool.
src/stratis_cli/_actions/_crypt.py (3)

66-89: Optional: pre-validate presence of at least one encryption method for a clearer CLI error

Right now, the daemon enforces “no encryption params” for encrypt and returns an engine error (tests expect this). If you want a more user-friendly CLI-side message, you could check for both key_desc and clevis being absent and raise a StratisCliUserError with an actionable hint. This would require adjusting tests that currently expect the engine error.

Example (non-diff, illustrative):

if namespace.key_desc is None and namespace.clevis is None:
    # raise a CLI-layer user error with a helpful hint
    raise StratisCliUserError(
        "No encryption parameters specified; use --key-desc or --clevis."
    )

149-171: Reencrypt relies on daemon-side validation; consider early state check (optional)

Current behavior delegates “unencrypted pools” rejection to stratisd (tests validate the engine error path). For a more consistent UX, you could mirror the encrypt/unencrypt pattern and check MOPool(...).Encrypted() before calling ReencryptPool, raising StratisCliNoChangeError on unencrypted pools. If you decide to do this, tests expecting engine errors will need to be updated.


53-65: Minor: reduce duplication in pool resolution logic

The pool lookup pattern repeats in all three methods. Extracting a tiny helper (e.g., _get_pool_object_path(namespace) -> object_path, mopool) would trim duplication and centralize any future changes to selection semantics.

Illustrative helper (non-diff):

@staticmethod
def _get_pool_object_path(namespace):
    from ._data import ObjectManager, pools
    pool_id = PoolId.from_parser_namespace(namespace)
    assert pool_id is not None
    proxy = get_object(TOP_OBJECT)
    managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
    return next(
        pools(props=pool_id.managed_objects_key())
        .require_unique_match(True)
        .search(managed_objects)
    )
tests/whitebox/integration/pool/test_encryption.py (2)

433-473: OffTestCase: validates decrypt-on-encrypted path; consider asserting post-state

The test exercises the success path. Optionally, add a quick verification (e.g., pool list detail) to assert encryption is now disabled to strengthen the signal.


498-542: ReencryptTestCase: exercising list after reencryption is good — optionally assert presence of the new field

Since docs introduce “Last Time Reencrypted,” consider asserting the detail view includes it (and is not “Never”) to validate the end-to-end exposure. If the harness doesn’t capture stdout easily, we can extend RUNNER/TEST_RUNNER to return output for assertions.

src/stratis_cli/_parser/_encryption.py (4)

402-419: Polish help text for “encryption off”

Functionality and wiring look good. Consider clearer phrasing.

-            "help": "Make unencrypted a previously encrypted pool",
+            "help": "Decrypt a previously encrypted pool",

421-438: Polish help text for “reencrypt”

Looks good. Minor wording improvement for user-facing help.

-            "help": "Reencrypt an encrypted pool with a new master key",
+            "help": "Rotate the pool’s encryption master key (re-encrypt with a new master key)",

25-26: Constants Verified; Consider Enforcing Mutual Exclusivity for Encryption Arguments

I’ve confirmed that both CLEVIS_AND_KERNEL (line 278) and IN_PLACE (line 301) in src/stratis_cli/_parser/_shared.py are defined as list literals of argument specs, so their reuse here is correct.

To ensure users select exactly one encryption method at parse time (rather than catching it post‐parse), you may wish to leverage an argparse mutually‐exclusive group. For example, in src/stratis_cli/_parser/_encryption.py, you could replace:

-    {
-        "description": "Arguments controlling encryption",
-        "args": CLEVIS_AND_KERNEL,
-    },
+    {
+        "description": "Arguments controlling encryption",
+        "mut_ex_args": [(True, CLEVIS_AND_KERNEL)],
+    },

This change will enforce exclusivity when parsing encryption on.

  • src/stratis_cli/_parser/_shared.py: CLEVIS_AND_KERNEL (lines 278–299) and IN_PLACE (lines 301–303) confirmed as lists
  • src/stratis_cli/_parser/_encryption.py: consider swapping "args": CLEVIS_AND_KERNEL for a "mut_ex_args" entry

357-400: Tighten help text for “encryption on” subcommand

• Confirmed the “Tang Server Verification” group label correctly refers to --tang-url as defined in src/stratis_cli/_parser/_shared.py (line 294), so no change needed there.
• Nit: simplify the help text for the encryption on subcommand for clarity.

Suggested update in src/stratis_cli/_parser/_encryption.py (around line 360):

-            "help": "Make encrypted a previously unencrypted pool",
+            "help": "Encrypt a previously unencrypted pool",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0417815 and c87558d.

📒 Files selected for processing (17)
  • .packit.yaml (0 hunks)
  • Makefile (1 hunks)
  • docs/stratis.txt (3 hunks)
  • src/stratis_cli/_actions/__init__.py (2 hunks)
  • src/stratis_cli/_actions/_crypt.py (1 hunks)
  • src/stratis_cli/_actions/_data.py (1 hunks)
  • src/stratis_cli/_actions/_introspect.py (3 hunks)
  • src/stratis_cli/_actions/_list_pool.py (2 hunks)
  • src/stratis_cli/_actions/_utils.py (2 hunks)
  • src/stratis_cli/_error_reporting.py (1 hunks)
  • src/stratis_cli/_errors.py (1 hunks)
  • src/stratis_cli/_parser/_encryption.py (2 hunks)
  • src/stratis_cli/_parser/_pool.py (2 hunks)
  • src/stratis_cli/_parser/_shared.py (1 hunks)
  • tests/whitebox/integration/pool/test_encryption.py (2 hunks)
  • tests/whitebox/integration/test_parser.py (1 hunks)
  • tests/whitebox/unit/test_running.py (1 hunks)
💤 Files with no reviewable changes (1)
  • .packit.yaml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T15:25:05.145Z
Learnt from: mulkieran
PR: stratis-storage/stratis-cli#0
File: :0-0
Timestamp: 2025-08-18T15:25:05.145Z
Learning: For the stratis-cli project, use Python's standard unittest framework instead of pytest. The codebase follows unittest.TestCase patterns with standard unittest assertions and test discovery.

Applied to files:

  • tests/whitebox/integration/pool/test_encryption.py
🪛 Ruff (0.12.2)
src/stratis_cli/_actions/__init__.py

20-20: ._crypt.CryptActions imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


33-33: ._utils.get_errors imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

src/stratis_cli/_actions/_utils.py

284-284: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)


284-284: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

🪛 LanguageTool
docs/stratis.txt

[grammar] ~123-~123: There might be a mistake here.
Context: ...oportional to the size of the pool. pool encryption off --in-place <(--uuid ...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ...oportional to the size of the pool. pool encryption reencrypt --in-place <(-...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ... proportional to the size of the pool. pool encryption bind <(nbde|tang)> <(--u...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ...on). tang is an alias for nbde. pool encryption bind tpm2 <(--uuid <uuid...

(QB_NEW_EN)


[style] ~285-~285: Unless you want to emphasize “not”, use “cannot” which is more common.
Context: ... for example, extending filesystems, can not be taken on the pool. Furthermo...

(CAN_NOT_PREMIUM)

⏰ 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). (52)
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
🔇 Additional comments (26)
src/stratis_cli/_actions/_data.py (1)

46-46: The above will show us how _data.py uses get_timeout (and whether any long-running methods use explicit overrides or decorators). Once we see the methods and their timeout handling, we can confirm no uncovered operations remain.

src/stratis_cli/_errors.py (1)

459-478: Clear, user-focused error for in-place requirement

The new StratisCliInPlaceNotSpecified is well-scoped and the message is explicit about implications and next steps. Looks good.

src/stratis_cli/_actions/_list_pool.py (1)

25-25: New dependency on python-dateutil — confirm packaging/runtime availability

Importing dateutil.parser introduces/assumes a runtime dependency. If this project targets environments where python-dateutil might not be present by default, please add it to the packaging manifest (setup/config) or the distro spec, and update installation docs.

Optional: you can avoid the dependency by using the stdlib datetime.fromisoformat with a small adapter for trailing 'Z' timestamps. See the companion comment below for a concrete diff.

src/stratis_cli/_actions/_utils.py (1)

289-308: Behavioral acknowledgment

Catching DPClientInvocationError chained to NoReply and emitting a one-line status to stderr is an appropriate UX for initiating long-running actions over D-Bus where replies may not arrive before timeout.

Makefile (1)

7-10: Include new encryption actions module in MonkeyType tracing — LGTM

Adding stratis_cli._actions._crypt to MONKEYTYPE_MODULES ensures type traces are collected for the new actions. This will help keep annotations current as the encryption workflows evolve.

src/stratis_cli/_actions/_introspect.py (5)

180-184: New DecryptPool method signature — LGTM

The out-parameters (results: b, return_code: q, return_string: s) match the existing conventions for similar single-result methods on the pool interface.


191-197: New EncryptPool method signature aligns with Manager.CreatePool types — LGTM

In-params key_descs: a((bu)s) and clevis_infos: a((bu)ss) mirror the types used by Manager.r9.CreatePool, ensuring consistency in how keys and Clevis metadata are conveyed.


236-240: New ReencryptPool method signature — LGTM

Shape and naming consistent with related pool actions returning a single boolean results plus return code/string.


269-269: No Encrypted property-change listeners detected; safe to remove EmitsChangedSignal annotation

Search for subscription patterns (.connect('PropertiesChanged', .subscribe, add_signal_receiver) and occurrences of Encrypted in signal contexts returned no matches. All references to Encrypted in the codebase are direct, synchronous property reads (e.g., bool(MOPool(...).Encrypted()) in _crypt.py and _list_pool.py). Therefore, there are no listeners that depend on property-changed signals for Encrypted.


274-275: LastReencryptedTimestamp usage verified

The pool-listing code in src/stratis_cli/_actions/_list_pool.py (around lines 363–370) correctly unpacks (valid, timestamp) and uses a conditional expression to format the timestamp only when valid is true, falling back to "Never" otherwise. This confirms downstream formatting gracefully handles the boolean flag and absent/empty timestamp.

tests/whitebox/integration/test_parser.py (1)

244-245: Docstring scope widened appropriately — LGTM

The updated description now covers encryption options beyond creation; matches the broadened test coverage.

src/stratis_cli/_error_reporting.py (1)

36-41: No import cycle detected between _actions and _error_reporting

I’ve verified that:

  • src/stratis_cli/_actions/__init__.py re-exports get_errors (from _utils.py) and several other helpers, but it does not import src/stratis_cli/_error_reporting.py (script output lines 18–33).
  • src/stratis_cli/_actions/_utils.py contains no references or imports of _error_reporting (script output “No references found”).
  • src/stratis_cli/_error_reporting.py imports from _actions, but there is no reverse import path.

The re-export pattern for get_errors is safe and introduces no import cycle.

src/stratis_cli/_parser/_pool.py (2)

38-38: Good move: centralizing encryption args via CLEVIS_AND_KERNEL

Importing CLEVIS_AND_KERNEL from _shared reduces duplication and keeps pool create aligned with the new encryption subcommands. No issues spotted with the import set.


114-119: Pool create “Encryption” group now reuses CLEVIS_AND_KERNEL — verify option coverage

Switching "args" to CLEVIS_AND_KERNEL keeps --key-desc / --clevis / --tang-url consistent with the encryption subcommands. The follow-on "Tang Server Verification" group still supplies --thumbprint/--trust-url, so ClevisEncryptionOptions will find everything it needs.

Two checks to consider:

  • Ensure there are no legacy callers/tests relying on a different help/ordering for these options for pool create.
  • Confirm CreateOptions’ ClevisEncryptionOptions(namespace) is only constructed when these members exist; here they do, so this is safe.

If desired, we can add an explicit doc string here that both --key-desc and --clevis may be specified together (to allow multiple bindings) to avoid ambiguity for users.

src/stratis_cli/_parser/_shared.py (1)

301-313: IN_PLACE switch is clear; message communicates operational constraints well

The mandatory nature and runtime implications are well articulated. This matches enforcement in CryptActions.

docs/stratis.txt (2)

120-151: New pool encryption subcommands documented comprehensively — build the man page to catch formatting edge cases

The synopsis for on/off/reencrypt mirrors the parser. AsciiDoc with nested brackets can be sensitive; please run the doc build (manpage) to ensure the new lines render as intended and don’t trip the formatter on nested option groups.

Would you like me to provide a script to build and sanity-check the generated man page locally?


385-389: “Last Time Reencrypted” field description is precise and scoped correctly

Clearly states conditions (metadata v2 and encryption enabled) and the “Never” behavior. Looks good.

src/stratis_cli/_actions/_crypt.py (2)

47-49: Hard requirement of --in-place is enforced early — good

Fast-failing on missing in-place matches the new CLI contract and simplifies subsequent logic.


101-142: Decrypt flow mirrors encrypt with coherent no-change and error handling

Checks encrypted state, calls DecryptPool, and handles Stratisd return codes consistently. Looks solid.

tests/whitebox/integration/pool/test_encryption.py (6)

20-24: Imports for new error types are correct and match behavior under test

Bringing in StratisCliInPlaceNotSpecified and friends aligns with the new enforcement and engine errors.


475-496: OffTestCase2: correct “no change” assertion for decrypting unencrypted pools

Matches CryptActions.unencrypt pre-check behavior.


544-565: ReencryptTestCase2: expecting engine error on unencrypted reencryption is consistent with current implementation

Good negative coverage.


567-608: EncryptTestCase: asserting no-change when already encrypted is correct

This aligns with the early MOPool().Encrypted() guard in CryptActions.encrypt.


610-647: EncryptTestCase2: covers both success and “missing params” error

  • Success path with --clevis=tpm2 is validated.
  • Error path without any encryption params matches current engine enforcement. If you ever add CLI-side validation, this assertion will need to change accordingly.

649-705: NoInPlaceTestCase: great coverage of the mandatory --in-place requirement across on/off/reencrypt

These tests directly backstop the new policy enforced in CryptActions.

src/stratis_cli/_parser/_encryption.py (1)

22-22: CryptActions import verified
The CryptActions class is defined in src/stratis_cli/_actions/_crypt.py (line 35) with the required encrypt, unencrypt, and reencrypt methods (lines 42, 103, 145), and it’s re-exported in src/stratis_cli/_actions/__init__.py (line 20). The import in src/stratis_cli/_parser/_encryption.py is correct—no changes needed.

@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from c87558d to 35a90f3 Compare August 28, 2025 21:19
@mulkieran mulkieran removed this from 2025August Sep 2, 2025
@mulkieran mulkieran moved this to In Review in 2025September Sep 2, 2025
@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch 3 times, most recently from 10d9e7a to abfcfce Compare September 4, 2025 21:04
@mulkieran mulkieran mentioned this pull request Sep 5, 2025
@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from abfcfce to e04f847 Compare September 5, 2025 16:09
@mulkieran
Copy link
Member Author

rebased, conflicts resolved

@mulkieran
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Sep 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (11)
src/stratis_cli/_actions/_utils.py (1)

277-286: Broaden exception-chain walk and add return typing

Consider including context in addition to cause so wrapped DBus errors aren’t missed, and add an explicit iterator return type.

-from typing import Callable, Tuple
+from typing import Callable, Tuple, Iterator
@@
-def get_errors(exc: BaseException):
+def get_errors(exc: BaseException) -> Iterator[BaseException]:
@@
-    while exc.__cause__ is not None:
-        yield exc.__cause__
-        exc = exc.__cause__
+    while True:
+        nxt = exc.__cause__ or exc.__context__
+        if nxt is None:
+            break
+        yield nxt
+        exc = nxt
docs/stratis.txt (3)

120-132: Clarify requirement for specifying an encryption method on “encryption on”

Users otherwise hit an engine error (covered by tests). Add one sentence.

 pool encryption on --in-place <(--uuid <uuid> |--name <name>)> [--key-desc <key_desc>] [--clevis <(nbde|tang|tpm2)> [--tang-url <tang_url>] [<(--thumbprint <thp> | --trust-url)>]::
      Turn encryption on for the specified pool. This operation must encrypt
      every block in the pool and takes time proportional to the size of the
      pool.
+     Either --key-desc or --clevis must be supplied to specify the encryption method.

277-291: Fix typos and minor grammar in --in-place option text

“can not” -> “cannot”; “you data” -> “your data”.

-        for example, extending filesystems, can not be taken on the pool.
+        for example, extending filesystems, cannot be taken on the pool.
@@
-        necessary while the encryption operation is running. Consider
-        backing up you data before initiating this operation.
+        necessary while the encryption operation is running. Consider
+        backing up your data before initiating this operation.

386-390: Consistency: “re-encrypted” vs “reencrypted”

Elsewhere the label uses “Reencrypted”. Consider using “Reencrypted” here for consistency.

-        The last time the pool was re-encrypted. If the pool has never been
+        The last time the pool was reencrypted. If the pool has never been
src/stratis_cli/_actions/_crypt.py (3)

22-29: Import StratisdErrors from its home module, not via _errors

Avoids layering confusion; _errors doesn’t own StratisdErrors.

-from .._errors import (
-    StratisCliEngineError,
-    StratisCliIncoherenceError,
-    StratisCliInPlaceNotSpecified,
-    StratisCliNoChangeError,
-    StratisdErrors,
-)
+from .._errors import (
+    StratisCliEngineError,
+    StratisCliIncoherenceError,
+    StratisCliInPlaceNotSpecified,
+    StratisCliNoChangeError,
+)
+from .._stratisd_constants import StratisdErrors

53-56: Replace assert with an explicit check

Asserts may be optimized out; raise a CLI error instead for robustness.

If PoolId.from_parser_namespace(namespace) can ever return None, apply:

-        pool_id = PoolId.from_parser_namespace(namespace)
-        assert pool_id is not None
+        pool_id = PoolId.from_parser_namespace(namespace)
+        if pool_id is None:  # Parser should prevent this; fail defensively.
+            raise StratisCliEngineError(StratisdErrors.ERROR, "Missing pool identifier")

If None is truly unreachable, add a brief comment:

-        pool_id = PoolId.from_parser_namespace(namespace)
-        assert pool_id is not None
+        pool_id = PoolId.from_parser_namespace(namespace)
+        assert pool_id is not None  # guaranteed by parser

69-89: Validate mixed method inputs or document intent

encrypt() accepts both key_desc and clevis simultaneously and forwards both. If this is intended (multiple bindings on enable), great; if not, fail fast at the CLI with a clear message.

Would you like a small guard to enforce “exactly one of --key-desc or --clevis”?

tests/whitebox/integration/pool/test_encryption.py (2)

438-463: Minor test nits: annotate class constants; prefer star-unpacking

  • Annotate class-level constants as ClassVar to satisfy RUF012 (optional in tests).
  • Use iterable unpacking for command lines to satisfy RUF005.

Example pattern (apply similarly across classes):

-from .._misc import RUNNER, TEST_RUNNER, SimTestCase, device_name_list
+from .._misc import RUNNER, TEST_RUNNER, SimTestCase, device_name_list
+from typing import ClassVar, List
@@
-class OffTestCase(SimTestCase):
+class OffTestCase(SimTestCase):
@@
-    _MENU = ["--propagate", "pool", "encryption", "off", "--in-place"]
-    _POOLNAME = "poolname"
-    _KEY_DESC = "keydesc"
+    _MENU: ClassVar[List[str]] = ["--propagate", "pool", "encryption", "off", "--in-place"]
+    _POOLNAME: ClassVar[str] = "poolname"
+    _KEY_DESC: ClassVar[str] = "keydesc"
@@
-        command_line = ["pool", "create", self._POOLNAME] + _DEVICE_STRATEGY()
+        command_line = ["pool", "create", self._POOLNAME, *_DEVICE_STRATEGY()]
@@
-        command_line = self._MENU + [f"--name={self._POOLNAME}"]
+        command_line = [*self._MENU, f"--name={self._POOLNAME}"]

Also applies to: 480-487, 503-529, 549-556, 572-598, 615-647


621-636: Optional: factor repeated setup logic

Pool creation and key setup sequences are repeated across classes. Consider small helpers to DRY the tests.

src/stratis_cli/_parser/_encryption.py (2)

361-372: Use iterable unpacking instead of list concatenation (RUF005)

Cleaner and satisfies Ruff by avoiding + IN_PLACE.

-            "args": [
-                (
-                    "--post-parser",
-                    {
-                        "action": RejectAction,
-                        "default": ClevisEncryptionOptions,
-                        "help": SUPPRESS,
-                        "nargs": "?",
-                    },
-                )
-            ]
-            + IN_PLACE,
+            "args": [
+                (
+                    "--post-parser",
+                    {
+                        "action": RejectAction,
+                        "default": ClevisEncryptionOptions,
+                        "help": SUPPRESS,
+                        "nargs": "?",
+                    },
+                ),
+                *IN_PLACE,
+            ],

360-361: Polish user-facing help strings

Slight rewording for clarity and consistency.

-            "help": "Make encrypted a previously unencrypted pool",
+            "help": "Encrypt a previously unencrypted pool",

-            "help": "Make unencrypted a previously encrypted pool",
+            "help": "Unencrypt a previously encrypted pool",

-            "help": "Reencrypt an encrypted pool with a new master key",
+            "help": "Re-encrypt an encrypted pool with a new master key",

Also applies to: 404-405, 423-424

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c87558d and 0dcc89f.

📒 Files selected for processing (17)
  • .packit.yaml (0 hunks)
  • Makefile (1 hunks)
  • docs/stratis.txt (3 hunks)
  • src/stratis_cli/_actions/__init__.py (2 hunks)
  • src/stratis_cli/_actions/_crypt.py (1 hunks)
  • src/stratis_cli/_actions/_data.py (1 hunks)
  • src/stratis_cli/_actions/_introspect.py (3 hunks)
  • src/stratis_cli/_actions/_list_pool.py (2 hunks)
  • src/stratis_cli/_actions/_utils.py (2 hunks)
  • src/stratis_cli/_error_reporting.py (1 hunks)
  • src/stratis_cli/_errors.py (1 hunks)
  • src/stratis_cli/_parser/_encryption.py (2 hunks)
  • src/stratis_cli/_parser/_pool.py (2 hunks)
  • src/stratis_cli/_parser/_shared.py (1 hunks)
  • tests/whitebox/integration/pool/test_encryption.py (2 hunks)
  • tests/whitebox/integration/test_parser.py (1 hunks)
  • tests/whitebox/unit/test_running.py (1 hunks)
💤 Files with no reviewable changes (1)
  • .packit.yaml
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/stratis_cli/_actions/init.py
  • src/stratis_cli/_parser/_shared.py
  • src/stratis_cli/_actions/_data.py
  • tests/whitebox/unit/test_running.py
  • src/stratis_cli/_parser/_pool.py
  • src/stratis_cli/_error_reporting.py
  • tests/whitebox/integration/test_parser.py
  • Makefile
  • src/stratis_cli/_actions/_introspect.py
  • src/stratis_cli/_actions/_list_pool.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T15:25:05.145Z
Learnt from: mulkieran
PR: stratis-storage/stratis-cli#0
File: :0-0
Timestamp: 2025-08-18T15:25:05.145Z
Learning: For the stratis-cli project, use Python's standard unittest framework instead of pytest. The codebase follows unittest.TestCase patterns with standard unittest assertions and test discovery.

Applied to files:

  • tests/whitebox/integration/pool/test_encryption.py
🪛 LanguageTool
docs/stratis.txt

[grammar] ~123-~123: There might be a mistake here.
Context: ...oportional to the size of the pool. pool encryption off --in-place <(--uuid ...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ...oportional to the size of the pool. pool encryption reencrypt --in-place <(-...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ... proportional to the size of the pool. pool encryption bind <(nbde|tang)> <(--u...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ...on). tang is an alias for nbde. pool encryption bind tpm2 <(--uuid <uuid...

(QB_NEW_EN)


[style] ~285-~285: Unless you want to emphasize “not”, use “cannot” which is more common.
Context: ... for example, extending filesystems, can not be taken on the pool. Furthermo...

(CAN_NOT_PREMIUM)


[grammar] ~290-~290: Ensure spelling is correct
Context: ...is running. Consider backing up you data before initiating this operation. ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.12.2)
src/stratis_cli/_actions/_crypt.py

54-54: Use of assert detected

(S101)


67-67: Avoid specifying long messages outside the exception class

(TRY003)


95-99: Avoid specifying long messages outside the exception class

(TRY003)


114-114: Use of assert detected

(S101)


127-127: Avoid specifying long messages outside the exception class

(TRY003)


137-141: Avoid specifying long messages outside the exception class

(TRY003)


156-156: Use of assert detected

(S101)


176-179: Avoid specifying long messages outside the exception class

(TRY003)

src/stratis_cli/_actions/_utils.py

304-304: Use raise without specifying exception name

Remove exception name

(TRY201)

src/stratis_cli/_parser/_encryption.py

361-372: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

tests/whitebox/integration/pool/test_encryption.py

438-438: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


455-462: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


469-471: Consider [*self._MENU, f"--name={self._POOLNAME}"] instead of concatenation

Replace with [*self._MENU, f"--name={self._POOLNAME}"]

(RUF005)


480-480: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


485-485: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


492-494: Consider [*self._MENU, f"--name={self._POOLNAME}"] instead of concatenation

Replace with [*self._MENU, f"--name={self._POOLNAME}"]

(RUF005)


503-503: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


520-527: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


534-536: Consider [*self._MENU, f"--name={self._POOLNAME}"] instead of concatenation

Replace with [*self._MENU, f"--name={self._POOLNAME}"]

(RUF005)


549-549: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


554-554: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


561-563: Consider [*self._MENU, f"--name={self._POOLNAME}"] instead of concatenation

Replace with [*self._MENU, f"--name={self._POOLNAME}"]

(RUF005)


572-572: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


589-596: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


603-606: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


615-615: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


621-626: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


633-636: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


643-645: Consider [*self._MENU, f"--name={self._POOLNAME}"] instead of concatenation

Replace with [*self._MENU, f"--name={self._POOLNAME}"]

(RUF005)


659-664: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

⏰ 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). (16)
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: test-runs (3.13)
  • GitHub Check: test-runs (3.12)
🔇 Additional comments (11)
src/stratis_cli/_errors.py (1)

459-472: New user-facing error reads well; API addition LGTM

Clear message, consistent with the new mandatory --in-place option. No functional concerns.

src/stratis_cli/_actions/_crypt.py (1)

101-109: In-place enforcement is correctly applied

Early fail on missing --in-place matches the docs and tests.

tests/whitebox/integration/pool/test_encryption.py (7)

433-473: Good coverage for “off” happy path and setup

Setup and name-based targeting look correct; test asserts on errors are precise.


475-496: No-change path validation reads well

Asserting StratisCliNoChangeError on decrypt when unencrypted matches action semantics.


498-542: Reencrypt flow and detail view exercise are solid

Covers operation and the detail listing that includes last reencrypt time.


544-565: Engine error expectation on reencrypt when unencrypted is appropriate

Matches daemon-side validation.


567-608: Encrypt-on when already encrypted; negative case looks correct

Asserting StratisCliNoChangeError is consistent with CryptActions.encrypt().


610-647: Positive encrypt-on and missing-params error both covered

Nice pairing of success and failure tests.


649-706: Mandatory --in-place tests are on point

Explicitly verifies enforcement across on/off/reencrypt.

src/stratis_cli/_parser/_encryption.py (2)

22-27: Imports for CryptActions and shared argument sets look correct

No issues wiring up CryptActions and pulling in CLEVIS_AND_KERNEL/IN_PLACE.


384-389: CLEVIS_AND_KERNEL is a list of (name, opts) tuples and correctly used under “args”

Comment on lines +288 to +337
def long_running_operation(func: Callable) -> Callable:
"""
Mark a function as a long running operation and catch and ignore NoReply
D-Bus exception.
"""

@wraps(func)
def wrapper(namespace: Namespace):
try:
func(namespace)
except DPClientInvocationError as err:
if not any(
isinstance(e, DBusException)
and e.get_dbus_name() == "org.freedesktop.DBus.Error.NoReply"
for e in get_errors(err)
):
raise err
print("Operation initiated", file=sys.stderr)

return wrapper
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve traceback; pass through return; accept any signature

  • Use bare raise to keep the original traceback (TRY201).
  • Return the wrapped function’s result.
  • Support arbitrary signatures so the decorator is reusable beyond Namespace-only call sites.
-from typing import Callable, Tuple
+from typing import Callable, Tuple, Any
@@
-def long_running_operation(func: Callable) -> Callable:
+def long_running_operation(func: Callable[..., Any]) -> Callable[..., Any]:
@@
-    def wrapper(namespace: Namespace):
+    def wrapper(*args: Any, **kwargs: Any) -> Any:
         try:
-            func(namespace)
+            return func(*args, **kwargs)
         except DPClientInvocationError as err:
             if not any(
                 isinstance(e, DBusException)
                 and e.get_dbus_name() == "org.freedesktop.DBus.Error.NoReply"
                 for e in get_errors(err)
             ):
-                raise err
+                raise
             print("Operation initiated", file=sys.stderr)
+            return None
📝 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 long_running_operation(func: Callable) -> Callable:
"""
Mark a function as a long running operation and catch and ignore NoReply
D-Bus exception.
"""
@wraps(func)
def wrapper(namespace: Namespace):
try:
func(namespace)
except DPClientInvocationError as err:
if not any(
isinstance(e, DBusException)
and e.get_dbus_name() == "org.freedesktop.DBus.Error.NoReply"
for e in get_errors(err)
):
raise err
print("Operation initiated", file=sys.stderr)
return wrapper
from typing import Callable, Tuple, Any
def long_running_operation(func: Callable[..., Any]) -> Callable[..., Any]:
"""
Mark a function as a long running operation and catch and ignore NoReply
D-Bus exception.
"""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return func(*args, **kwargs)
except DPClientInvocationError as err:
if not any(
isinstance(e, DBusException)
and e.get_dbus_name() == "org.freedesktop.DBus.Error.NoReply"
for e in get_errors(err)
):
raise
print("Operation initiated", file=sys.stderr)
return None
return wrapper
🧰 Tools
🪛 Ruff (0.12.2)

304-304: Use raise without specifying exception name

Remove exception name

(TRY201)

🤖 Prompt for AI Agents
In src/stratis_cli/_actions/_utils.py around lines 288 to 307, the decorator
currently forces a Namespace-only signature, drops the wrapped function’s return
value, and re-raises exceptions with "raise err" which loses the original
traceback; change wrapper to accept arbitrary signatures (def wrapper(*args,
**kwargs)), call the wrapped function as result = func(*args, **kwargs) inside
the try, return that result, and in the except block re-raise the original
exception with a bare "raise" when it is not the NoReply DBus error so the
original traceback is preserved; keep the existing NoReply-check and stderr
print behavior unchanged.

@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from 1606146 to c7d4f82 Compare September 6, 2025 01:59
@mulkieran
Copy link
Member Author

rebased, conflicts resolved

@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch 2 times, most recently from 1c9b1b8 to dbe1553 Compare September 11, 2025 20:20
@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from dbe1553 to fee0c44 Compare September 16, 2025 19:20
@mulkieran
Copy link
Member Author

rebased, conflicts resolved

@mulkieran mulkieran mentioned this pull request Sep 29, 2025
@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from 8583b69 to c1b9bce Compare October 2, 2025 13:54
@mulkieran
Copy link
Member Author

rebased, conflicts resolved.

@mulkieran
Copy link
Member Author

mulkieran commented Oct 2, 2025

Test errors are from the change on stratisd side. If the D-Bus lock isn't FIFO, then the tests will have to read the D-Bus until the newly created D-Bus object is inserted in the tree before operating on it. Until now, many of the tests have relied on the lock being FIFO. To change that...can't wait for the signal in this code, 'cause the signal might appear too early, so need to scan the D-Bus until a new dbus object path appears. For that, should really design a context manager for any command that should produce a new object path on success. The problem is, if reads can get in before writes, incessant reads will block the write that is being waited for. So need to give that write a chance by an intelligent wait using one of our favorite retry libraries, e.g., tenacity.

@mulkieran mulkieran moved this to In Review in 2025October Oct 11, 2025
@mulkieran mulkieran force-pushed the issue-stratisd-3597-2 branch from c1b9bce to 722b0bb Compare November 12, 2025 01:44
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
We had it as high as two minutes to give a chance of returning on fairly
long-running task, like creating encrypted pools, since each device had
to be separately encrypted. We do not do that anymore, as the whole pool
is encrypted these days, so the create method returns faster.

We are about to introduce really long running commands, where 120
minutes will not be enough in almost all cases.

So shortening the D-Bus timeout seems like a reasonable thing to do.

60 seconds is a plenty long time to wait for any error messsages that
stratisd might return.

Signed-off-by: mulhern <amulhern@redhat.com>
This way we can relatively cheaply avoid printing the timeout error
message and return a zero exit code on the timeout.

Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
These are bind, unbind, and rebind. The new commands use the
mandatory --name, --uuid option mechanism for specifying the pool to
operate on, while the old commands just used name.

Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
Signed-off-by: mulhern <amulhern@redhat.com>
@mulkieran mulkieran removed this from 2025October Nov 17, 2025
@mulkieran mulkieran moved this to Pending in 2025November Nov 17, 2025
@mulkieran mulkieran added this to the v3.9.0 milestone Nov 19, 2025
@mulkieran mulkieran removed this from 2025November Dec 1, 2025
@mulkieran mulkieran moved this to Pending in 2025December Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Pending

Development

Successfully merging this pull request may close these issues.

2 participants