Skip to content

flashers: add support for passing OCI credentials to fls#211

Merged
mangelajo merged 1 commit into
jumpstarter-dev:mainfrom
bennyz:oci-creds-flashers
Feb 10, 2026
Merged

flashers: add support for passing OCI credentials to fls#211
mangelajo merged 1 commit into
jumpstarter-dev:mainfrom
bennyz:oci-creds-flashers

Conversation

@bennyz
Copy link
Copy Markdown
Member

@bennyz bennyz commented Feb 10, 2026

Currently OCI credentials weren't passed, so it was not possible to work with private registries.

Summary by CodeRabbit

  • New Features

    • Added private OCI registry authentication (username/password), CLI options and environment-variable support; credentials are applied when flashing OCI images.
    • Improved flashing progress reporting and safer console output by redacting sensitive values.
  • Documentation

    • README updated with OCI auth examples, env-var usage, and a private-OCI flashing example.
  • Tests

    • Added tests for OCI credential validation, env construction, redaction, credential file handling, and flash behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds OCI credential support to the flasher client: new CLI options/env vars for OCI username/password, credential validation, propagation of credentials to FLS via environment or temporary credential file, redaction of sensitive outputs, and tests and README updates.

Changes

Cohort / File(s) Summary
Documentation
python/packages/jumpstarter-driver-flashers/README.md
Documented new CLI options (--header, --bearer, --oci-username, --oci-password), environment variable usage (OCI_USERNAME, OCI_PASSWORD), and example for flashing from a private OCI registry.
OCI credential implementation
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py
Added OCI support: _validate_oci_credentials, _fls_oci_auth_env, _monitor_fls_progress, credential file setup/cleanup, redaction helpers and context managers; extended flash(), _perform_flash_operation(), _flash_with_fls() signatures to accept oci_username/oci_password; propagate credentials to FLS via env prefix or file; added warnings when credentials ignored.
Tests
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client_test.py
Added tests for OCI credential validation, env construction (OCI vs non-OCI), redaction, credential file setup/cleanup, and flash behavior when OCI credentials are provided for non-OCI paths.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant Client as FlasherClient.flash
    participant Validator as _validate_oci_credentials
    participant Performer as _perform_flash_operation
    participant EnvBuilder as _fls_oci_auth_env / credential file setup
    participant FLS as FLS (on DUT)
    participant Console as Console/Redactor

    User->>Client: flash(path, oci_username?, oci_password?)
    Client->>Validator: validate credentials
    Validator-->>Client: normalized creds or error
    Client->>Performer: start flash operation (with creds)
    Performer->>EnvBuilder: build env prefix or write cred file (if OCI)
    EnvBuilder-->>Performer: env string / creds file path
    Performer->>FLS: invoke FLS with env prefix / cred file
    FLS-->>Console: progress output
    Console->>Performer: redacted progress/status
    Performer-->>Client: result
    Client-->>User: operation complete / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

backport release-0.7

Suggested reviewers

  • mangelajo
  • bkhizgiy
  • raballew

Poem

🐰 Hopping through registries, keys held tight,

Env vars tucked in the moonlit night,
Credentials wrapped, kept out of sight,
FLS hums on, glowing bright,
Flash complete — a rabbit's delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.03% 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 'flashers: add support for passing OCI credentials to fls' directly and clearly summarizes the main change—adding OCI credential support to the flashers tool.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

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: 1

Caution

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

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

122-130: ⚠️ Potential issue | 🔴 Critical

Bug: elif oci_username: breaks the URL-handling control flow for non-OCI paths.

The new elif oci_username: branch is inserted into the if/elif/else chain that determines the download strategy (should_download_to_httpd, image_url). When a user accidentally supplies --oci-username with an HTTP URL, this branch fires, clears the credentials, but skips the HTTP URL handling that would normally set image_url and should_download_to_httpd = False. The result: image_url stays "" and the code falls through to the storage-transfer path with no operator or URL configured, causing a runtime failure.

The credential-clearing logic should be a separate, standalone check that doesn't participate in the download-strategy chain.

🐛 Proposed fix: decouple credential check from download-strategy chain
         if path.startswith("oci://"):
             # OCI URLs are always passed directly to fls
             image_url = path
             should_download_to_httpd = False
-        elif oci_username:
-            self.logger.warning("OCI credentials provided for non-OCI image path; ignoring credentials")
-            oci_username = None
-            oci_password = None
-        elif path.startswith(("http://", "https://")) and not force_exporter_http:
+        elif path.startswith(("http://", "https://")) and not force_exporter_http:
             # the flasher image can handle the http(s) from a remote directly, unless target is isolated
             image_url = path
             should_download_to_httpd = False
         else:
             # use the exporter's http server for the flasher image, we should download it first
             if operator is None:
                 if path.startswith(("http://", "https://")) and bearer_token:
                     parsed = urlparse(path)
                     self.logger.info(f"Using Bearer token authentication for {parsed.netloc}")
                     original_http_url = path
                     operator = Operator(
                         "http", root="/", endpoint=f"{parsed.scheme}://{parsed.netloc}", token=bearer_token
                     )
                     operator_scheme = "http"
                     path = Path(parsed.path)
                 else:
                     path, operator, operator_scheme = operator_for_path(path)
             image_url = self.http.get_url() + "/" + path.name
+
+        if not path.startswith("oci://") and oci_username:
+            self.logger.warning("OCI credentials provided for non-OCI image path; ignoring credentials")
+            oci_username = None
+            oci_password = None

596-607: ⚠️ Potential issue | 🟡 Minor

Broad except Exception re-wraps the FlashRetryableError raised for panics.

If "panicked at" is detected at line 598–599, the raised FlashRetryableError is immediately caught by the except Exception handler at line 605, which wraps it in a new FlashRetryableError with a generic message, losing the specific panic context.

🐛 Proposed fix: catch FlashRetryableError before the generic handler, or restructure
             except pexpect.EOF as err:
                 # End of file - connection closed
                 self.logger.error("Console connection closed unexpectedly")
                 raise FlashRetryableError("Console connection closed during flash operation") from err
+            except FlashRetryableError:
+                raise
             except Exception as err:
                 self.logger.error(f"Error monitoring FLS progress: {err}")
                 raise FlashRetryableError(f"Error monitoring FLS progress: {err}") from err
🤖 Fix all issues with AI agents
In
`@python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py`:
- Around line 558-564: The flash command currently injects
FLS_REGISTRY_USERNAME/PASSWORD inline (built in _fls_oci_auth_env and flash_cmd)
and is sent via console.sendline, risking credential leakage to
console.logfile_read and _monitor_fls_progress prints; fix by removing inline
credentials: create a temp cred file on the target with strict perms (via
console.sendline: write file with username/password, chmod 600), change
_fls_oci_auth_env to source that file (e.g., "set -o allexport; source
/tmp/fls_creds; set +o allexport") so flash_cmd contains no cleartext creds, and
ensure any debug/log output that prints flash_cmd or fls_oci_auth_env (including
_monitor_fls_progress and any places setting console.logfile_read) redacts
sensitive values (replace username/password with "***") before logging.

Comment thread python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py Outdated
Currently OCI credentials weren't passed, so it was not possible to work with private
registries.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
@bennyz bennyz force-pushed the oci-creds-flashers branch from b4088bc to a378c3e Compare February 10, 2026 17:01
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.

Caution

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

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

628-638: ⚠️ Potential issue | 🟠 Major

FlashRetryableError raised on panic is caught by the generic except Exception and double-wrapped.

When "panicked at" is detected on line 629, the raised FlashRetryableError falls through except pexpect.EOF and is caught by except Exception on line 636, which wraps it in a second FlashRetryableError. This distorts the error message for the retry/categorisation logic upstream.

Proposed fix — re-raise `FlashRetryableError` before the generic handler
             except pexpect.EOF as err:
                 # End of file - connection closed
                 self.logger.error("Console connection closed unexpectedly")
                 raise FlashRetryableError("Console connection closed during flash operation") from err
+            except FlashRetryableError:
+                raise
             except Exception as err:
                 self.logger.error(f"Error monitoring FLS progress: {err}")
                 raise FlashRetryableError(f"Error monitoring FLS progress: {err}") from err

640-645: ⚠️ Potential issue | 🟡 Minor

Missing error handling for exit-code parsing after FLS completes.

Line 643 can raise IndexError (empty output) or ValueError (non-numeric line) without a guard. Compare with the defensive handling in _download_fls_binary (lines 526-530) which wraps the same pattern in a try/except.

Proposed fix — add try/except like _download_fls_binary
         console.sendline("echo $?")
         console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
-        exit_code = int(console.before.decode(errors="ignore").strip().splitlines()[-1])
+        try:
+            lines = console.before.decode(errors="ignore").strip().splitlines()
+            exit_code = int(lines[-1]) if lines else -1
+        except (IndexError, ValueError) as e:
+            raise FlashRetryableError("FLS flash operation failed, could not parse exit code") from e
         if exit_code != 0:
             raise FlashRetryableError(f"FLS flash operation failed, exit code: {exit_code}")
🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client_test.py (1)

128-170: Positional-index access in capture_perform is fragile.

The mock captures args[2], args[3], args[13], args[14] — if _perform_flash_operation's parameter order changes, this test will silently assert wrong values instead of failing clearly. Consider using keyword arguments or named indices.

Example: using a dict keyed by parameter name
-    def capture_perform(*args):
-        captured["image_url"] = args[2]
-        captured["should_download_to_httpd"] = args[3]
-        captured["oci_username"] = args[13]
-        captured["oci_password"] = args[14]
+    def capture_perform(
+        partition, path, image_url, should_download_to_httpd,
+        storage_thread, error_queue, cacert_file, insecure_tls,
+        headers, bearer_token, method, fls_version, fls_binary_url,
+        oci_username, oci_password,
+    ):
+        captured["image_url"] = image_url
+        captured["should_download_to_httpd"] = should_download_to_httpd
+        captured["oci_username"] = oci_username
+        captured["oci_password"] = oci_password

if "panicked at" in current_output:
raise FlashRetryableError(f"FLS panicked: {current_output}")
if "panicked at" in raw_output:
raise FlashRetryableError(f"FLS panicked: {self._redact_sensitive_values(raw_output)}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice, I like the redacter...

@mangelajo mangelajo merged commit 3b9170c into jumpstarter-dev:main Feb 10, 2026
24 checks passed
@bennyz
Copy link
Copy Markdown
Member Author

bennyz commented Feb 10, 2026

@mangelajo sorry i forgot to mark it draft, i just wanted preliminary coderabbit comments, i still need to try this out...

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