Skip to content

add --no-power-off to flashers#296

Merged
mangelajo merged 1 commit into
jumpstarter-dev:mainfrom
bennyz:optional-power-off
Mar 10, 2026
Merged

add --no-power-off to flashers#296
mangelajo merged 1 commit into
jumpstarter-dev:mainfrom
bennyz:optional-power-off

Conversation

@bennyz

@bennyz bennyz commented Mar 9, 2026

Copy link
Copy Markdown
Member

Sometimes we would not want to power off the device after flashing

Summary by CodeRabbit

  • New Features

    • Added a --no-power-off flag to flash commands so devices can be left powered on after flashing (default remains to power off).
    • Flag is available across all flash entry points (single, local, OCI, batch) and propagated through the flash flow.
    • In batch flashes, the flag applies only to the final device; earlier devices still power off.
  • UX

    • When --no-power-off is used, the process logs that the device remains powered on.

@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a power_off boolean (default true) to flashing workflows and CLI in two driver packages, threads it through public and internal flash APIs and batch flows, and introduces a --no-power-off CLI flag to skip post-flash shutdown.

Changes

Cohort / File(s) Summary
Flashers Client
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Added power_off: bool = True to flash() and _perform_flash_operation(); conditional post-flash power-off behavior; new --no-power-off CLI option wired through; batch flow uses power_off for final operation only.
RideSX Client
python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py
Added power_off parameter across public flash entrypoints and internal helpers (_power_off_if_available, _execute_flash_operation, _execute_flash_command, flash_with_targets, flash_local, flash_oci_auto); propagated through call sites and CLI --no-power-off wiring; updated docs/strings.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Client
    participant Flasher as FlashOperation
    participant Power as PowerController

    User->>CLI: run flash command (--no-power-off?)
    CLI->>Client: flash(..., power_off=bool)
    Client->>Flasher: perform flash operation
    Flasher-->>Client: flash result (success/failure)
    alt power_off == true
        Client->>Power: power_off device
        Power-->>Client: powered off
    else power_off == false
        Client-->>User: log "left powered on (--no-power-off)"
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • mangelajo

Poem

🐇
I hop through wires and flags so bright,
I tuck devices in at night or leave them light,
A --no-power-off wink, a gentle shove,
Power left on or off by rabbit love,
Hooray for choice — and flashing done just right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'add --no-power-off to flashers' directly and clearly summarizes the main change—introducing a new CLI flag for conditional power-off behavior in flasher operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (1)

1586-1608: ⚠️ Potential issue | 🟠 Major

Apply --no-power-off only to the last flash in a batch.

Line 1608 forwards the user flag into every per-image self.flash() call. That is unsafe for multi--t flows, because _perform_flash_operation() always issues a reboot before returning; with power_off=False, the next iteration can start while the DUT is still rebooting. Keep intermediate operations on the existing power-off path and use the user’s choice only for the final image.

🛠️ Suggested adjustment
             # Execute each flash operation
             for idx, (image_file, target_partition, block_device) in enumerate(flash_operations):
                 op_num = f"{idx + 1}/{len(flash_operations)}" if len(flash_operations) > 1 else ""
                 op_desc = f"partition '{target_partition}'" if target_partition else "default target"

                 self.logger.info(f"Flashing {op_num} {op_desc} with '{image_file}'".strip())

+                should_power_off = True if idx < len(flash_operations) - 1 else power_off
+
                 # Perform the flash operation
                 self.flash(
                     image_file,
                     partition=target_partition,
                     block_device=block_device,
                     os_image_checksum=checksum,
                     force_exporter_http=force_exporter_http,
                     force_flash_bundle=force_flash_bundle,
                     cacert_file=cacert,
                     insecure_tls=insecure_tls,
                     headers=headers_dict,
                     bearer_token=bearer,
                     retries=retries,
                     method=method,
                     fls_version=fls_version,
                     fls_binary_url=fls_binary_url,
-                    power_off=power_off,
+                    power_off=should_power_off,
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`
around lines 1586 - 1608, The loop that calls self.flash(...) in the flash batch
forwards the user-specified power_off flag to every image, which can cause the
next iteration to start while the device is rebooting; change the call in the
for-loop (inside _perform_flash_operation / the batch-flashing logic) to compute
an is_last value (idx == len(flash_operations) - 1) and pass power_off only for
the final flash (power_off if is_last else True) so intermediate flashes use the
existing power-off behavior while the user's choice applies to the last image.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`:
- Around line 1586-1608: The loop that calls self.flash(...) in the flash batch
forwards the user-specified power_off flag to every image, which can cause the
next iteration to start while the device is rebooting; change the call in the
for-loop (inside _perform_flash_operation / the batch-flashing logic) to compute
an is_last value (idx == len(flash_operations) - 1) and pass power_off only for
the final flash (power_off if is_last else True) so intermediate flashes use the
existing power-off behavior while the user's choice applies to the last image.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04966f53-bc58-407f-b95f-6e69843c4068

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfa067 and a35102c.

📒 Files selected for processing (2)
  • python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
  • python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py

@bennyz bennyz force-pushed the optional-power-off branch from a35102c to b7b6a7d Compare March 9, 2026 09:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py (1)

115-119: Use neutral log wording in the shared helper.

_power_off_if_available() is now used by the programmatic API as well, so mentioning --no-power-off in the log is CLI-specific and a bit misleading outside that path.

Suggested tweak
-            self.logger.info("leaving device powered on (--no-power-off)")
+            self.logger.info("leaving device powered on")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py`
around lines 115 - 119, The helper _power_off_if_available uses a CLI-specific
message; update the logger call inside that function (the self.logger.info(...)
in _power_off_if_available) to use neutral wording that doesn't reference
--no-power-off (e.g., "leaving device powered on (power_off disabled)" or
similar) so the message is accurate for both CLI and programmatic callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py`:
- Around line 115-119: The helper _power_off_if_available uses a CLI-specific
message; update the logger call inside that function (the self.logger.info(...)
in _power_off_if_available) to use neutral wording that doesn't reference
--no-power-off (e.g., "leaving device powered on (power_off disabled)" or
similar) so the message is accurate for both CLI and programmatic callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cbab643c-6b6b-485c-8c73-ac316c4f8d2b

📥 Commits

Reviewing files that changed from the base of the PR and between a35102c and b7b6a7d.

📒 Files selected for processing (2)
  • python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
  • python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py

Sometimes we would not want to power off the device after flashing

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
@bennyz bennyz force-pushed the optional-power-off branch from b7b6a7d to 9344a3b Compare March 9, 2026 09:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (3)
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (1)

516-517: Keep shared logs CLI-agnostic.

BaseFlasherClient.flash() is public API as well as CLI plumbing, so mentioning --no-power-off in the log leaks CLI details into programmatic use.

🧹 Small log cleanup
-                self.logger.info("Leaving target powered on (--no-power-off)")
+                self.logger.info("Leaving target powered on")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`
around lines 516 - 517, The log in BaseFlasherClient.flash() currently mentions
a CLI flag and leaks CLI details; change the logger.info call that now says
"Leaving target powered on (--no-power-off)" to a CLI-agnostic message such as
"Leaving target powered on" or "Leaving target powered on per request" so the
public API doesn't expose CLI-specific text; update the log in the flash method
where this message is emitted.
python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py (2)

126-160: Add regression tests for the opt-out cleanup path.

The new behavior is centralized here, but the existing python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client_test.py:45-73 only checks the call() sequence for a success case. It would not catch power_off=False still calling _power_off_if_available, and it does not cover the failure path at all. Please add at least one success test and one error-path test that assert the cleanup is skipped when the flag is false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py`
around lines 126 - 160, Add regression tests for _execute_flash_operation to
cover the opt-out cleanup path: create one success test and one error-path test
that call client._execute_flash_operation(operation_func, power_off=False) and
assert client._power_off_if_available is NOT invoked. In each test,
patch/mocking should stub out client.boot_to_fastboot and provide a fake
operation_func (a callable that returns normally for the success test and raises
an exception for the error test), and verify that on success the operation's
result/flow is correct and on error the original exception is propagated (or
wrapped behavior preserved) while _power_off_if_available remains uncalled; name
the new tests to reflect success and error scenarios and place them alongside
the existing tests in client_test.py.

167-175: Add power_off to the FlasherClientInterface abstract signature.

RideSXClient.flash(..., power_off=...) is one of multiple implementations that include this parameter—BaseFlasherClient (in jumpstarter-driver-flashers) already has it too. The abstract interface in python/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py:527-550 should include power_off to reflect this shared behavior across implementations and ensure type consistency.

Minimal follow-up
# python/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py
     def flash(
         self,
         path: PathBuf | dict[str, PathBuf],
         *,
         target: str | None = None,
         operator: Operator | dict[str, Operator] | None = None,
         compression: Compression | None = None,
+        power_off: bool = True,
     ):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py`
around lines 167 - 175, The FlasherClientInterface abstract method signature is
missing the power_off parameter, causing mismatches with implementations like
RideSXClient.flash and BaseFlasherClient; update the abstract method
(FlasherClientInterface.flash) to include the power_off: bool = True parameter
(or the same typing as implementations) and propagate that parameter into the
abstract method's docstring/type hints so all implementers match the interface
and type check correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`:
- Around line 1521-1528: The --no-power-off click.option currently maps to the
variable power_off but the flashing loop applies power_off=False only to the
final item in a multi-target run; either change the flashing loop so each
iteration uses the passed power_off value (i.e., don't override power_off
per-iteration) or update the option help text to explicitly state it only
affects the final flash in a batch. Locate the click.option("--no-power-off") /
power_off variable and the loop that iterates over targets in the flashing
routine (where power_off is currently applied only for the last item) and
implement one of the two fixes: propagate power_off to every iteration, or
change the help string to "Leave device powered on after flashing (applies only
to the final target in a batch)".

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`:
- Around line 516-517: The log in BaseFlasherClient.flash() currently mentions a
CLI flag and leaks CLI details; change the logger.info call that now says
"Leaving target powered on (--no-power-off)" to a CLI-agnostic message such as
"Leaving target powered on" or "Leaving target powered on per request" so the
public API doesn't expose CLI-specific text; update the log in the flash method
where this message is emitted.

In
`@python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py`:
- Around line 126-160: Add regression tests for _execute_flash_operation to
cover the opt-out cleanup path: create one success test and one error-path test
that call client._execute_flash_operation(operation_func, power_off=False) and
assert client._power_off_if_available is NOT invoked. In each test,
patch/mocking should stub out client.boot_to_fastboot and provide a fake
operation_func (a callable that returns normally for the success test and raises
an exception for the error test), and verify that on success the operation's
result/flow is correct and on error the original exception is propagated (or
wrapped behavior preserved) while _power_off_if_available remains uncalled; name
the new tests to reflect success and error scenarios and place them alongside
the existing tests in client_test.py.
- Around line 167-175: The FlasherClientInterface abstract method signature is
missing the power_off parameter, causing mismatches with implementations like
RideSXClient.flash and BaseFlasherClient; update the abstract method
(FlasherClientInterface.flash) to include the power_off: bool = True parameter
(or the same typing as implementations) and propagate that parameter into the
abstract method's docstring/type hints so all implementers match the interface
and type check correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 804809f6-3718-463d-9c3c-b588a8f88a7d

📥 Commits

Reviewing files that changed from the base of the PR and between b7b6a7d and 9344a3b.

📒 Files selected for processing (2)
  • python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
  • python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py

Comment on lines +1521 to +1528
@click.option(
"--no-power-off",
"power_off",
is_flag=True,
flag_value=False,
default=True,
help="Leave device powered on after flashing",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify batch semantics of --no-power-off.

Line 1527 says the device is left on “after flashing”, but Line 1610 applies power_off=False only to the final item in a multi--t run. That help text is misleading for batch flashes. Either honor the flag for every operation or document that it only affects the last flash in the batch.

✏️ Suggested help-text tweak
-            help="Leave device powered on after flashing",
+            help="Leave device powered on after flashing; in batch mode this only applies to the final operation",

Also applies to: 1592-1610

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`
around lines 1521 - 1528, The --no-power-off click.option currently maps to the
variable power_off but the flashing loop applies power_off=False only to the
final item in a multi-target run; either change the flashing loop so each
iteration uses the passed power_off value (i.e., don't override power_off
per-iteration) or update the option help text to explicitly state it only
affects the final flash in a batch. Locate the click.option("--no-power-off") /
power_off variable and the loop that iterates over targets in the flashing
routine (where power_off is currently applied only for the last item) and
implement one of the two fixes: propagate power_off to every iteration, or
change the help string to "Leave device powered on after flashing (applies only
to the final target in a batch)".

@bennyz bennyz requested a review from mangelajo March 9, 2026 14:03
@mangelajo mangelajo merged commit 24aa76c into jumpstarter-dev:main Mar 10, 2026
25 checks passed
@bennyz bennyz deleted the optional-power-off branch March 12, 2026 08:02
@coderabbitai coderabbitai Bot mentioned this pull request Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants