Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

flasher-driver: add support for using fls as flasher#735

Merged
mangelajo merged 1 commit intomainfrom
fls-support
Nov 5, 2025
Merged

flasher-driver: add support for using fls as flasher#735
mangelajo merged 1 commit intomainfrom
fls-support

Conversation

@mangelajo
Copy link
Copy Markdown
Member

@mangelajo mangelajo commented Nov 5, 2025

for now we will download it by default from github in runtime, once we are happy with a version, we can bake it into the flasher image.

Summary by CodeRabbit

  • New Features

    • Added an alternative FLS-based flashing method with option to specify FLS version.
    • New CLI options to choose flashing method and provide FLS version.
    • Improved live progress reporting for FLS-based flashes.
  • Bug Fixes

    • Enhanced error handling and retry behavior for network/TLS and startup timeouts during flashing.
    • Unified handling of TLS and header arguments to improve reliability.

@netlify
Copy link
Copy Markdown

netlify Bot commented Nov 5, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit 4c0c286
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/690b75720e18500007f0eec5
😎 Deploy Preview https://deploy-preview-735--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 5, 2025

Walkthrough

Adds a selectable flash method (default "fls") with optional fls_version; implements an FLS-based flash flow and progress monitor, refactors TLS CLI arg helper, enhances udhcpc error handling, and exposes --method / --fls-version in the CLI.

Changes

Cohort / File(s) Summary
Flash API & Routing
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Added method (default "fls") and fls_version params to flash() and _perform_flash_operation(); routing now selects _flash_with_fls() or existing shell flow and validates method.
FLS Flow & Monitoring
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
New _flash_with_fls() to download/install specified FLS version and run fls from-url; new _monitor_fls_progress() streams stdout, prints progress, detects completion markers, and treats unexpected EOF/timeouts as retryable errors.
TLS Arg Refactor
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Renamed _curl_tls_args()_cmdline_tls_args() and updated callers; centralizes TLS/insecure/CA handling for command-line tools used in both flash flows.
Error Handling Enhancements
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Wraps udhcpc startup TIMEOUTs and other exceptions in FlashRetryableError to enable retry semantics; added retryable handling for FLS progress EOF/timeouts.
CLI Changes
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Added click options --method (choices fls,shell) and --fls-version; CLI forwards these into the driver's flash() call.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Driver as FlashDriver
    participant Target as TargetHost
    participant FLS as FLS Installer/Runner
    participant Monitor as ProgressMonitor

    User->>Driver: flash(path, method="fls", fls_version="X")
    activate Driver

    alt method == "fls"
        Driver->>Driver: _perform_flash_operation(method="fls", fls_version="X")
        Driver->>FLS: (optional) download & install fls version X
        FLS-->>Driver: fls installed / available
        Driver->>Driver: _cmdline_tls_args()
        Driver->>Target: run `fls from-url [image_url]` (with TLS/headers)
        activate Target
        loop stream progress
            Target->>Monitor: stdout lines
            Monitor->>User: print progress updates
            Monitor->>Monitor: detect completion marker / EOF
        end
        Target-->>Driver: process exit
        deactivate Target
    else method == "shell"
        Driver->>Driver: _flash_with_progress() (existing shell-based flow)
    end

    Driver-->>User: flash result
    deactivate Driver
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • _flash_with_fls() download/install logic and command construction.
    • _monitor_fls_progress() streaming, completion detection, and retryable error handling.
    • TLS helper rename (_cmdline_tls_args) and all updated callers.
    • CLI option wiring and parameter propagation through call chain.

Poem

🐰

I hopped to fetch a fls so bright,
Installed and watched the streaming light,
Two ways to flash, headers held tight,
Retries and TLS kept the night just right,
A happy rabbit cheers the byte! 🥕

Pre-merge checks and finishing touches

✅ 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 clearly and concisely summarizes the main change: adding support for using fls as an alternative flasher method, which aligns with the primary objective and all major code changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fls-support

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.

@mangelajo mangelajo requested a review from bennyz November 5, 2025 13:28
Copy link
Copy Markdown
Contributor

@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: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d88da4 and 743f113.

📒 Files selected for processing (1)
  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (12 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-14T16:05:14.577Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 339
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:349-349
Timestamp: 2025-03-14T16:05:14.577Z
Learning: The `_upload_artifact` method in `BaseFlasherClient` class should call `write_from_path` with the calculated filename parameter: `storage.write_from_path(filename, path, operator=operator)` instead of `storage.write_from_path(path, operator=operator)`.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
🧬 Code graph analysis (1)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (2)
packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py (1)
  • pexpect (29-37)
examples/soc-pytest/jumpstarter_example_soc_pytest/test_on_rpi4.py (1)
  • console (20-24)
⏰ 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). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: e2e
🔇 Additional comments (6)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (6)

15-15: LGTM - pexpect import is necessary.

The pexpect import is required for the new error handling (pexpect.TIMEOUT at line 325) and FLS progress monitoring (pexpect.TIMEOUT and pexpect.EOF at lines 503, 525).


322-330: Good enhancement - explicit retryable error handling for network startup.

The enhanced error handling for udhcpc properly wraps timeout and other exceptions as retryable errors, which aligns well with the retry logic at the higher level.


410-425: Good refactor - generalized TLS argument builder.

Renaming _curl_tls_args to _cmdline_tls_args better reflects its use for both curl-based and FLS-based flashing methods. The implementation remains correct.


487-487: Verify FLS command-line argument compatibility.

The code assumes FLS accepts the same TLS and header argument formats as curl (e.g., -k, --cacert, -H). Please confirm that FLS supports these exact argument formats, or adjust the argument construction accordingly.

Additionally, document what the -i 1.0 and -n flags mean for FLS, as they're not immediately clear.


497-532: Verify FLS outputs expected completion markers.

The progress monitor reuses _check_completion_markers (line 517), which looks for "FLASH_COMPLETE" and "FLASH_FAILED" strings. Please verify that the FLS tool actually outputs these markers. If FLS uses different completion indicators, the monitoring logic may not detect completion correctly or may wait indefinitely.

If FLS uses different markers, consider:

  1. Adding FLS-specific marker detection, or
  2. Documenting the expected FLS output format, or
  3. Making FLS output these markers for compatibility.

1131-1142: CLI options properly defined with validation.

The CLI options for --method and --fls-version are well-defined with appropriate types and choices. The click.Choice constraint ensures only valid methods are accepted via CLI.

Note: The default value inconsistency with the method signature (line 101) was flagged separately.

Comment thread packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py Outdated
@mangelajo mangelajo force-pushed the fls-support branch 2 times, most recently from 1fa098b to 827e367 Compare November 5, 2025 13:33
@mangelajo
Copy link
Copy Markdown
Member Author

this is how it looks with this patch:

jumpstarter ⚡ti-jacinto-j784s4xevm-01 ➤ uv run j storage flash  https://xxx/auto-osbuild-j784s4evm-rhivos-qa-regular-aarch64-13407404.95895702.raw.xz
[11/05/25 14:21:35] INFO     Setting up flasher bundle files in exporter                                                                                                                                                 client.py:161
[11/05/25 14:21:36] INFO     Power cycling target...                                                                                                                                                                      client.py:37
[11/05/25 14:21:36] INFO     Starting power cycle sequence                                                                                                                                                                client.py:25
[11/05/25 14:21:37] INFO     Waiting 2 seconds...                                                                                                                                                                         client.py:27
[11/05/25 14:21:39] INFO     Power cycle sequence complete                                                                                                                                                                client.py:30
[11/05/25 14:21:39] INFO     Waiting for U-Boot prompt...                                                                                                                                                                 client.py:40
[11/05/25 14:21:43] INFO     Running DHCP to obtain network configuration...                                                                                                                                             client.py:117
                    INFO     Running command checked: printenv autoload                                                                                                                                                   client.py:87
                    INFO     Running command checked: setenv autoload 'no'                                                                                                                                                client.py:87
                    INFO     Running command checked: dhcp                                                                                                                                                                client.py:87
[11/05/25 14:21:48] INFO     Running command checked: setenv autoload                                                                                                                                                     client.py:87
                    INFO     Running command checked: printenv ipaddr                                                                                                                                                     client.py:87
[11/05/25 14:21:49] INFO     Running command checked: printenv gatewayip                                                                                                                                                  client.py:87
                    INFO     Running command checked: printenv netmask                                                                                                                                                    client.py:87
[11/05/25 14:21:49] INFO     discovered dhcp details: ip_address='10.26.28.206' gateway='10.26.28.254' netmask='255.255.255.0'                                                                                           client.py:946
                    INFO     Running command checked: setenv serverip '10.26.28.75'                                                                                                                                       client.py:87
[11/05/25 14:21:50] INFO     Running command: tftpboot 0x90000000 flasher-fedora.itb                                                                                                                                      client.py:71
[11/05/25 14:22:02] INFO     Running boot command: unlz4 0x900000d4 0x80000000 0x8000000; imxtract 0x90000000 fdt-j784s4 0x88000000; imxtract 0x90000000 initrd 0x89000000; booti 0x80000000 0x89000000:0x7000000        client.py:982
                             0x88000000
[11/05/25 14:22:17] INFO     Using target block device: /dev/mmcblk1                                                                                                                                                     client.py:308
[11/05/25 14:22:19] INFO     Setting the remote DUT time to match the local system time                                                                                                                                  client.py:386
 fls from-url -i 1.0 -n   "https://xxxIauto
-osbuild-j784s4evm-rhivos-qa-regular-aarch64-13407404.95895702.raw.xz" /dev/mmcb
lk1
Block flash command:
  URL: https://xxx/auto-osbuild-j784s4evm-rhivos-qa-regular-aarch64-13407404.95895702.raw.xz
  Device: /dev/mmcblk1
  Ignore certificates: false
  Buffer size: 1024 MB
  Max retries: 10
  Retry delay: 2 seconds
  Debug: false
  O_DIRECT mode: false

Using decompressor: xzcat
Opening block device for writing: /dev/mmcblk1
Using download buffer: 1024 MB (capacity: 131072 chunks, ~8 KB per chunk)
Starting download from: https://xxx/auto-osbuild-j784s4evm-rhivos-qa-regular-aarch64-13407404.95895702.raw.xz
Content length: 547832416 bytes
Download: 27.92 MB / 522.45 MB (5.3%) | 27.92 MB/s | Decompressed: [░░░░░░░░░░] 0.4% | Written: 63.00 MB | 62.99 MB/s
Download: 68.82 MB / 522.45 MB (13.2%) | 34.38 MB/s | Decompressed: [░░░░░░░░░░] 3.8% | Written: 63.00 MB | 31.47 MB/s
Download: 101.40 MB / 522.45 MB (19.4%) | 33.78 MB/s | Decompressed: [░░░░░░░░░░] 5.3% | Written: 63.00 MB | 20.99 MB/s
Download: 131.56 MB / 522.45 MB (25.2%) | 32.85 MB/s | Decompressed: [░░░░░░░░░░] 5.5% | Written: 63.00 MB | 15.73 MB/s
Download: 157.90 MB / 522.45 MB (30.2%) | 31.55 MB/s | Decompressed: [░░░░░░░░░░] 6.3% | Written: 127.00 MB | 25.37 MB/s
Download: 185.42 MB / 522.45 MB (35.5%) | 30.88 MB/s | Decompressed: [░░░░░░░░░░] 6.3% | Written: 127.00 MB | 21.15 MB/s
Download: 214.07 MB / 522.45 MB (41.0%) | 30.55 MB/s | Decompressed: [░░░░░░░░░░] 6.3% | Written: 127.00 MB | 18.12 MB/s
Download: 243.30 MB / 522.45 MB (46.6%) | 30.38 MB/s | Decompressed: [░░░░░░░░░░] 6.4% | Written: 191.00 MB | 23.85 MB/s
Download: 273.07 MB / 522.45 MB (52.3%) | 30.31 MB/s | Decompressed: [░░░░░░░░░░] 6.4% | Written: 191.00 MB | 21.20 MB/s
Download: 303.46 MB / 522.45 MB (58.1%) | 30.32 MB/s | Decompressed: [░░░░░░░░░░] 6.4% | Written: 191.00 MB | 19.08 MB/s
Download: 334.43 MB / 522.45 MB (64.0%) | 30.37 MB/s | Decompressed: [░░░░░░░░░░] 6.7% | Written: 191.00 MB | 17.35 MB/s
Download: 366.87 MB / 522.45 MB (70.2%) | 30.54 MB/s | Decompressed: [░░░░░░░░░░] 7.8% | Written: 255.00 MB | 21.23 MB/s
Download: 400.35 MB / 522.45 MB (76.6%) | 30.76 MB/s | Decompressed: [░░░░░░░░░░] 8.9% | Written: 255.00 MB | 19.59 MB/s
Download: 434.50 MB / 522.45 MB (83.2%) | 31.00 MB/s | Decompressed: [█░░░░░░░░░] 10.2% | Written: 255.00 MB | 18.20 MB/s
Download: 470.02 MB / 522.45 MB (90.0%) | 31.30 MB/s | Decompressed: [█░░░░░░░░░] 11.5% | Written: 255.00 MB | 16.98 MB/s
Download: 506.08 MB / 522.45 MB (96.9%) | 31.60 MB/s | Decompressed: [█░░░░░░░░░] 12.8% | Written: 319.00 MB | 19.92 MB/s
Download: Done | Decompressed: [██░░░░░░░░░░░░░░░░░░] 14.1% | Written: 319.00 MB | 18.70 MB/s
Download: Done | Decompressed: [███░░░░░░░░░░░░░░░░░] 15.3% | Written: 319.00 MB | 17.65 MB/s
Download: Done | Decompressed: [███░░░░░░░░░░░░░░░░░] 16.2% | Written: 383.00 MB | 20.06 MB/s
Download: Done | Decompressed: [███░░░░░░░░░░░░░░░░░] 17.3% | Written: 383.00 MB | 19.04 MB/s
Download: Done | Decompressed: [███░░░░░░░░░░░░░░░░░] 18.7% | Written: 383.00 MB | 18.13 MB/s
Download: Done | Decompressed: [███░░░░░░░░░░░░░░░░░] 20.0% | Written: 416.00 MB | 18.80 MB/s
Download: Done | Decompressed: [████░░░░░░░░░░░░░░░░] 21.3% | Written: 447.00 MB | 19.32 MB/s
Download: Done | Decompressed: [████░░░░░░░░░░░░░░░░] 22.7% | Written: 447.00 MB | 18.51 MB/s
Download: Done | Decompressed: [████░░░░░░░░░░░░░░░░] 24.1% | Written: 447.00 MB | 17.77 MB/s
Download: Done | Decompressed: [█████░░░░░░░░░░░░░░░] 25.4% | Written: 511.00 MB | 19.52 MB/s
Download: Done | Decompressed: [█████░░░░░░░░░░░░░░░] 26.9% | Written: 511.00 MB | 18.80 MB/s
Download: Done | Decompressed: [█████░░░░░░░░░░░░░░░] 28.2% | Written: 511.00 MB | 18.13 MB/s
Download: Done | Decompressed: [█████░░░░░░░░░░░░░░░] 29.6% | Written: 545.00 MB | 18.66 MB/s
Download: Done | Decompressed: [██████░░░░░░░░░░░░░░] 31.0% | Written: 575.00 MB | 19.03 MB/s
Download: Done | Decompressed: [██████░░░░░░░░░░░░░░] 32.3% | Written: 575.00 MB | 18.42 MB/s
Download: Done | Decompressed: [██████░░░░░░░░░░░░░░] 33.5% | Written: 575.00 MB | 17.84 MB/s
Download: Done | Decompressed: [██████░░░░░░░░░░░░░░] 34.8% | Written: 639.00 MB | 19.22 MB/s
Download: Done | Decompressed: [███████░░░░░░░░░░░░░] 36.5% | Written: 639.00 MB | 18.65 MB/s
Download: Done | Decompressed: [███████░░░░░░░░░░░░░] 39.3% | Written: 639.00 MB | 18.12 MB/s
Download: Done | Decompressed: [████████░░░░░░░░░░░░] 42.0% | Written: 703.00 MB | 19.38 MB/s
Download: Done | Decompressed: [████████░░░░░░░░░░░░] 43.5% | Written: 703.00 MB | 18.85 MB/s
Download: Done | Decompressed: [████████░░░░░░░░░░░░] 44.8% | Written: 703.00 MB | 18.36 MB/s
Download: Done | Decompressed: [█████████░░░░░░░░░░░] 46.1% | Written: 703.00 MB | 17.88 MB/s
Download: Done | Decompressed: [█████████░░░░░░░░░░░] 47.5% | Written: 767.00 MB | 19.02 MB/s
Download: Done | Decompressed: [█████████░░░░░░░░░░░] 49.0% | Written: 767.00 MB | 18.56 MB/s
Download: Done | Decompressed: [██████████░░░░░░░░░░] 50.3% | Written: 767.00 MB | 18.12 MB/s
Download: Done | Decompressed: [██████████░░░░░░░░░░] 51.6% | Written: 831.50 MB | 19.18 MB/s
Download: Done | Decompressed: [██████████░░░░░░░░░░] 52.9% | Written: 831.50 MB | 18.74 MB/s
Download: Done | Decompressed: [██████████░░░░░░░░░░] 54.3% | Written: 831.50 MB | 18.33 MB/s
Download: Done | Decompressed: [███████████░░░░░░░░░] 55.6% | Written: 831.50 MB | 17.93 MB/s
Download: Done | Decompressed: [███████████░░░░░░░░░] 57.0% | Written: 895.75 MB | 18.90 MB/s
Download: Done | Decompressed: [███████████░░░░░░░░░] 58.3% | Written: 895.75 MB | 18.51 MB/s
Download: Done | Decompressed: [███████████░░░░░░░░░] 59.6% | Written: 895.75 MB | 18.13 MB/s
Download: Done | Decompressed: [████████████░░░░░░░░] 61.0% | Written: 959.75 MB | 19.03 MB/s
Download: Done | Decompressed: [████████████░░░░░░░░] 62.3% | Written: 959.75 MB | 18.66 MB/s
Download: Done | Decompressed: [████████████░░░░░░░░] 63.7% | Written: 959.75 MB | 18.30 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 65.1% | Written: 1023.75 MB | 19.15 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 65.8% | Written: 1023.75 MB | 18.79 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 65.9% | Written: 1023.75 MB | 18.44 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 67.0% | Written: 1023.75 MB | 18.11 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 68.3% | Written: 1087.00 MB | 18.89 MB/s
Download: Done | Decompressed: [█████████████░░░░░░░] 69.6% | Written: 1087.00 MB | 18.57 MB/s
Download: Done | Decompressed: [██████████████░░░░░░] 71.0% | Written: 1087.00 MB | 18.25 MB/s
Download: Done | Decompressed: [██████████████░░░░░░] 72.3% | Written: 1151.00 MB | 19.00 MB/s
Download: Done | Decompressed: [██████████████░░░░░░] 73.7% | Written: 1151.00 MB | 18.69 MB/s
Download: Done | Decompressed: [███████████████░░░░░] 75.0% | Written: 1151.00 MB | 18.39 MB/s
Download: Done | Decompressed: [███████████████░░░░░] 75.3% | Written: 1198.00 MB | 18.83 MB/s
Download: Done | Decompressed: [███████████████░░░░░] 76.7% | Written: 1215.00 MB | 18.80 MB/s
Download: Done | Decompressed: [███████████████░░░░░] 78.0% | Written: 1215.00 MB | 18.51 MB/s
Download: Done | Decompressed: [███████████████░░░░░] 79.4% | Written: 1215.00 MB | 18.23 MB/s
Download: Done | Decompressed: [████████████████░░░░] 80.7% | Written: 1279.00 MB | 18.90 MB/s
Download: Done | Decompressed: [████████████████░░░░] 82.1% | Written: 1279.00 MB | 18.62 MB/s
Download: Done | Decompressed: [████████████████░░░░] 83.4% | Written: 1279.00 MB | 18.35 MB/s
Download: Done | Decompressed: [████████████████░░░░] 84.8% | Written: 1343.00 MB | 19.00 MB/s
Download: Done | Decompressed: [█████████████████░░░] 86.2% | Written: 1343.00 MB | 18.73 MB/s
Download: Done | Decompressed: [█████████████████░░░] 87.6% | Written: 1343.00 MB | 18.47 MB/s
Download: Done | Decompressed: [█████████████████░░░] 89.0% | Written: 1343.00 MB | 18.22 MB/s
Download: Done | Decompressed: [██████████████████░░] 90.2% | Written: 1407.00 MB | 18.83 MB/s
Download: Done | Decompressed: [██████████████████░░] 91.5% | Written: 1407.00 MB | 18.57 MB/s
Download: Done | Decompressed: [██████████████████░░] 92.9% | Written: 1407.00 MB | 18.33 MB/s
Download: Done | Decompressed: [██████████████████░░] 94.3% | Written: 1471.00 MB | 18.92 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1471.00 MB | 18.67 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1471.00 MB | 18.43 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1471.00 MB | 18.19 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1535.00 MB | 18.74 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1535.00 MB | 18.51 MB/s
Download: Done | Decompressed: [███████████████████░] 95.2% | Written: 1535.00 MB | 18.28 MB/s
Download: Done | Decompressed: [███████████████████░] 96.4% | Written: 1599.00 MB | 18.81 MB/s
Download: Done | Decompressed: [███████████████████░] 97.7% | Written: 1599.00 MB | 18.59 MB/s
Download: Done | Decompressed: [███████████████████░] 99.0% | Written: 1599.00 MB | 18.37 MB/s
Download: Done | Decompressed: Done | Written: 1656.00 MB | 18.75 MB/s
Download: Done | Decompressed: Done | Written: 1720.00 MB | 18.72 MB/s
Download: Done | Decompressed: Done | Written: 1791.00 MB | 18.75 MB/s
Download: Done | Decompressed: Done | Written: 1827.00 MB | 18.46 MB/s
Download: Done | Decompressed: Done | Written: 1919.00 MB | 18.72 MB/s
Download: Done | Decompressed: Done | Written: 1955.00 MB | 18.47 MB/s
Download: Done | Decompressed: Done | Written: 2034.00 MB | 18.60 MB/s
Download: Done | Decompressed: Done | Written: 2051.00 MB | 18.18 MB/s
Download: Done | Decompressed: Done | Written: 2131.00 MB | 18.33 MB/s
Download: Done | Decompressed: Done | Written: 2232.00 MB | 18.63 MB/s
Download: Done | Decompressed: Done | Written: 2261.00 MB | 18.36 MB/s
Download: Done | Decompressed: Done | Written: 2332.00 MB | 18.41 MB/s
Download: Done | Decompressed: Done | Written: 2374.00 MB | 18.23 MB/s
Download: Done | Decompressed: Done | Written: 2464.00 MB | 18.42 MB/s
Download: Done | Decompressed: Done | Written: 2518.00 MB | 18.34 MB/s
Download: Done | Decompressed: Done | Written: 2619.00 MB | 18.59 MB/s
Download: Done | Decompressed: Done | Written: 2687.00 MB | 18.60 MB/s
Download: Done | Decompressed: Done | Written: 2709.00 MB | 18.31 MB/s
Download: Done | Decompressed: Done | Written: 2796.00 MB | 18.45 MB/s
Download: Done | Decompressed: Done | Written: 2867.00 MB | 18.48 MB/s
Download: Done | Decompressed: Done | Written: 2918.00 MB | 18.39 MB/s
Download: Done | Decompressed: Done | Written: 2959.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 3009.00 MB | 18.17 MB/s
Download: Done | Decompressed: Done | Written: 3082.00 MB | 18.24 MB/s
Download: Done | Decompressed: Done | Written: 3199.00 MB | 18.54 MB/s
Download: Done | Decompressed: Done | Written: 3263.00 MB | 18.53 MB/s
Download: Done | Decompressed: Done | Written: 3327.00 MB | 18.53 MB/s
Download: Done | Decompressed: Done | Written: 3364.00 MB | 18.39 MB/s
Download: Done | Decompressed: Done | Written: 3407.00 MB | 18.28 MB/s
Download: Done | Decompressed: Done | Written: 3474.00 MB | 18.30 MB/s
Download: Done | Decompressed: Done | Written: 3536.00 MB | 18.29 MB/s
Download: Done | Decompressed: Done | Written: 3594.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 3666.00 MB | 18.29 MB/s
Download: Done | Decompressed: Done | Written: 3758.00 MB | 18.40 MB/s
Download: Done | Decompressed: Done | Written: 3791.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 3860.00 MB | 18.27 MB/s
Download: Done | Decompressed: Done | Written: 3967.00 MB | 18.46 MB/s
Download: Done | Decompressed: Done | Written: 4031.00 MB | 18.45 MB/s
Download: Done | Decompressed: Done | Written: 4049.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 4159.00 MB | 18.45 MB/s
Download: Done | Decompressed: Done | Written: 4223.00 MB | 18.45 MB/s
Download: Done | Decompressed: Done | Written: 4225.00 MB | 18.20 MB/s
Download: Done | Decompressed: Done | Written: 4351.00 MB | 18.46 MB/s
Download: Done | Decompressed: Done | Written: 4415.00 MB | 18.46 MB/s
Download: Done | Decompressed: Done | Written: 4479.00 MB | 18.46 MB/s
Download: Done | Decompressed: Done | Written: 4530.00 MB | 18.41 MB/s
Download: Done | Decompressed: Done | Written: 4584.00 MB | 18.37 MB/s
Download: Done | Decompressed: Done | Written: 4665.00 MB | 18.44 MB/s
Download: Done | Decompressed: Done | Written: 4696.00 MB | 18.31 MB/s
Download: Done | Decompressed: Done | Written: 4760.00 MB | 18.31 MB/s
Download: Done | Decompressed: Done | Written: 4843.00 MB | 18.39 MB/s
Download: Done | Decompressed: Done | Written: 4869.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 4930.00 MB | 18.25 MB/s
Download: Done | Decompressed: Done | Written: 4998.00 MB | 18.26 MB/s
Download: Done | Decompressed: Done | Written: 5102.00 MB | 18.41 MB/s
Download: Done | Decompressed: Done | Written: 5120.00 MB | 18.24 MB/s
[  298.172131]  mmcblk1: p1 p2 p3 p4
Download: Done | Decompressed: Done | Written: Done

Download complete: 522.45 MB in 16.46s (31.75 MB/s)
Decompression complete: 5120.00 MB in 87.83s (58.29 MB/s)
Write complete: 5120.00 MB in 281.23s (18.21 MB/s)
Compression ratio: 9.80x
Result: FLASH_COMPLETED
[11/05/25 14:27:04] INFO     Flash operation completed successfully                                                                                                                                                      client.py:646
                    INFO     Flushing buffers                                                                                                                                                                            client.py:492
[11/05/25 14:27:06] INFO     Powering off target                                                                                                                                                                         client.py:367
[11/05/25 14:27:07] INFO     Flash operation succeeded on attempt 1                                                                                                                                                      client.py:177
                    INFO     Flashing completed in 5m 31s

Copy link
Copy Markdown
Contributor

@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: 2

Caution

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

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

412-427: Fix insecure TLS flag: FLS expects --insecure-tls, not -k.

FLS supports --insecure-tls for cert verification (equivalent to curl -k) and --cacert for CA bundles. The code at line 420 generates -k but should generate --insecure-tls to be compatible with FLS. The --cacert flag is correct.

♻️ Duplicate comments (1)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (1)

478-491: Handle missing FLS when no version specified.

When fls_version == "" (the API default), the code skips downloading FLS and directly executes the fls command at line 491. If FLS isn't pre-installed in the flasher image, this will fail with an unclear error.

Consider checking if FLS exists before attempting to use it:

     if fls_version != "":
         self.logger.info(f"Downloading FLS version {fls_version} from GitHub releases")
         # Download fls binary to the target device (until it is available on the target device)
         fls_url = (
             f"https://github.com/jumpstarter-dev/fls/releases/download/{fls_version}/"
             f"fls-aarch64-linux"
         )
         console.sendline(f"curl -L {fls_url} -o /sbin/fls")
         console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
         console.sendline("chmod +x /sbin/fls")
         console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
+    else:
+        # Verify FLS is available when no version specified
+        console.sendline("which fls")
+        console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
+        console.sendline("echo $?")
+        console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
+        exit_code = int(console.before.decode(errors="ignore").strip().splitlines()[-1])
+        if exit_code != 0:
+            raise FlashNonRetryableError(
+                "FLS not found. Either specify fls_version to download, or ensure FLS is pre-installed in the flasher image."
+            )

     # Flash the image

Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa098b and 827e367.

📒 Files selected for processing (1)
  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (12 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:100-101
Timestamp: 2025-11-05T13:31:39.263Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the `flash()` method and its CLI command intentionally have different defaults for `fls_version`: the method defaults to `""` (empty string) while the CLI defaults to `"0.1.5"`. This is by design to provide CLI users with a convenient default while keeping the programmatic API ready for when FLS is included in the flasher images.
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:476-486
Timestamp: 2025-11-05T13:33:24.716Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the FLS flasher download in _flash_with_fls() hardcodes "aarch64-linux" architecture because the flasher always targets ARM devices currently.
📚 Learning: 2025-11-05T13:31:39.263Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:100-101
Timestamp: 2025-11-05T13:31:39.263Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the `flash()` method and its CLI command intentionally have different defaults for `fls_version`: the method defaults to `""` (empty string) while the CLI defaults to `"0.1.5"`. This is by design to provide CLI users with a convenient default while keeping the programmatic API ready for when FLS is included in the flasher images.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-11-05T13:33:24.716Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:476-486
Timestamp: 2025-11-05T13:33:24.716Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the FLS flasher download in _flash_with_fls() hardcodes "aarch64-linux" architecture because the flasher always targets ARM devices currently.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-09-24T16:13:14.244Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 644
File: packages/jumpstarter-driver-flashers/oci_bundles/aarch64-itb/build_fits.sh:16-18
Timestamp: 2025-09-24T16:13:14.244Z
Learning: The build script packages/jumpstarter-driver-flashers/oci_bundles/aarch64-itb/build_fits.sh is designed to run specifically on ARM containers to pick up the Fedora ARM kernel, so hardcoded aarch64 package references are intentional and correct.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-03-14T16:05:14.577Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 339
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:349-349
Timestamp: 2025-03-14T16:05:14.577Z
Learning: The `_upload_artifact` method in `BaseFlasherClient` class should call `write_from_path` with the calculated filename parameter: `storage.write_from_path(filename, path, operator=operator)` instead of `storage.write_from_path(path, operator=operator)`.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
🧬 Code graph analysis (1)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (2)
packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py (1)
  • pexpect (29-37)
examples/soc-pytest/jumpstarter_example_soc_pytest/test_on_rpi4.py (1)
  • console (20-24)
⏰ 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). (4)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
🔇 Additional comments (8)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (8)

100-101: LGTM!

The different defaults for fls_version between the method (empty string) and CLI (line 1144: "0.1.5") are intentional per the design—providing CLI convenience while keeping the API ready for when FLS is baked into images.

Based on learnings.


322-330: LGTM! Good error handling improvement.

Wrapping udhcpc failures in FlashRetryableError enables the retry mechanism to handle network setup issues gracefully. The specific handling of pexpect.TIMEOUT and the fallback Exception catch are both appropriate.


341-365: LGTM! Method validation properly implemented.

The branching logic correctly validates the method parameter, raising ArgumentError for invalid values. This addresses the concern about programmatic calls bypassing CLI validation.


537-584: LGTM! Refactoring to use renamed TLS helper.

The update correctly uses the renamed _cmdline_tls_args method. The refactoring maintains consistency across both flash methods.


1135-1146: LGTM! CLI options properly configured.

The --method and --fls-version options are correctly defined with appropriate defaults and validation. The TODO comment at line 1144 clearly documents the plan to change the default once FLS is baked into images.

Based on learnings.


1148-1163: LGTM! CLI function signature updated.

The function signature correctly accepts the new method and fls_version parameters from the CLI options.


1174-1186: LGTM! Parameters properly forwarded.

The new method and fls_version parameters are correctly passed through from the CLI to the flash() method, completing the integration.


501-536: No changes needed—FLS completion detection is properly implemented.

The code correctly handles FLS completion via two mechanisms:

  1. Primary (prompt-based): Lines 524-526 detect completion when console.match_index == 0, meaning the prompt matched. Since FLS is a standard CLI tool, it returns to the prompt upon completion, triggering this detection path.

  2. Fallback (marker-based): Line 521 checks for shell-specific markers ("FLASH_COMPLETE"/"FLASH_FAILED"), which are generated by the shell-based method (line 574: echo "F""LASH_COMPLETE") but not emitted by FLS. This check is harmless and won't interfere.

The timeout loop (line 507) ensures output is continuously accumulated with 1-second intervals until the prompt is matched or an exception occurs. The _flash_with_fls and _flash_with_progress methods appropriately use different completion mechanisms suited to each approach.

Comment thread packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py Outdated
for now we will download it by default from github in runtime,
once we are happy with a version, we can bake it into the
flasher image.
Copy link
Copy Markdown
Contributor

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

478-496: Consider adding a check for pre-installed FLS when version is empty.

When fls_version == "" (the programmatic default), the code assumes FLS is pre-installed at /sbin/fls. If it's not present, the command at line 498 will fail with a less clear error message.

Consider adding a pre-check when fls_version == "":

if fls_version == "":
    # Verify FLS is installed
    console.sendline("command -v fls")
    console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
    fls_check = console.before.decode(errors="ignore").strip()
    if "/fls" not in fls_check:
        raise FlashNonRetryableError(
            "FLS binary not found. Either include FLS in the flasher image or specify --fls-version."
        )

This would provide clearer feedback when FLS is missing rather than failing at command execution.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 827e367 and 4c0c286.

📒 Files selected for processing (1)
  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (12 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:100-101
Timestamp: 2025-11-05T13:31:39.263Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the `flash()` method and its CLI command intentionally have different defaults for `fls_version`: the method defaults to `""` (empty string) while the CLI defaults to `"0.1.5"`. This is by design to provide CLI users with a convenient default while keeping the programmatic API ready for when FLS is included in the flasher images.
📚 Learning: 2025-11-05T13:31:39.263Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:100-101
Timestamp: 2025-11-05T13:31:39.263Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the `flash()` method and its CLI command intentionally have different defaults for `fls_version`: the method defaults to `""` (empty string) while the CLI defaults to `"0.1.5"`. This is by design to provide CLI users with a convenient default while keeping the programmatic API ready for when FLS is included in the flasher images.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-11-05T13:33:24.716Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:476-486
Timestamp: 2025-11-05T13:33:24.716Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, the FLS flasher download in _flash_with_fls() hardcodes "aarch64-linux" architecture because the flasher always targets ARM devices currently.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-11-05T13:45:58.087Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:15-15
Timestamp: 2025-11-05T13:45:58.087Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, pexpect is intentionally used as a transitive dependency through the jumpstarter-driver-pyserial package. The flashers package does not declare pexpect as a direct dependency because the pyserial driver package is intended to control the pexpect version.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-09-24T16:13:14.244Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 644
File: packages/jumpstarter-driver-flashers/oci_bundles/aarch64-itb/build_fits.sh:16-18
Timestamp: 2025-09-24T16:13:14.244Z
Learning: The build script packages/jumpstarter-driver-flashers/oci_bundles/aarch64-itb/build_fits.sh is designed to run specifically on ARM containers to pick up the Fedora ARM kernel, so hardcoded aarch64 package references are intentional and correct.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-01-29T11:52:43.554Z
Learnt from: bennyz
Repo: jumpstarter-dev/jumpstarter PR: 241
File: packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py:52-60
Timestamp: 2025-01-29T11:52:43.554Z
Learning: The TFTP driver (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py) handles all low-level concerns like path validation, error handling, and checksum computation. The client (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py) should remain simple as it delegates these responsibilities to the driver.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
📚 Learning: 2025-03-14T16:05:14.577Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 339
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:349-349
Timestamp: 2025-03-14T16:05:14.577Z
Learning: The `_upload_artifact` method in `BaseFlasherClient` class should call `write_from_path` with the calculated filename parameter: `storage.write_from_path(filename, path, operator=operator)` instead of `storage.write_from_path(path, operator=operator)`.

Applied to files:

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
🧬 Code graph analysis (1)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (2)
packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py (1)
  • pexpect (29-37)
packages/jumpstarter/jumpstarter/common/exceptions.py (1)
  • ArgumentError (54-57)
⏰ 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). (11)
  • GitHub Check: e2e
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
🔇 Additional comments (2)
packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py (2)

322-330: LGTM! Enhanced error handling for network setup.

Good defensive improvement wrapping the udhcpc call with specific timeout and general exception handling, properly classified as retryable errors.


508-550: LGTM! Well-structured progress monitoring with comprehensive error detection.

The incremental output tracking, panic detection, and exit code verification provide robust monitoring of the FLS operation. The logic correctly handles both normal completion (prompt match) and timeout cases for continuous reading.

@mangelajo mangelajo merged commit bb1a7db into main Nov 5, 2025
18 checks passed
@mangelajo mangelajo deleted the fls-support branch November 5, 2025 16:33
@jumpstarter-backport-bot
Copy link
Copy Markdown

Successfully created backport PR for release-0.7:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants