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

Tmtee helper driver#641

Merged
mangelajo merged 3 commits intojumpstarter-dev:mainfrom
mangelajo:tmtee-helper-driver
Sep 23, 2025
Merged

Tmtee helper driver#641
mangelajo merged 3 commits intojumpstarter-dev:mainfrom
mangelajo:tmtee-helper-driver

Conversation

@mangelajo
Copy link
Copy Markdown
Member

@mangelajo mangelajo commented Sep 22, 2025

Adds:

  • TMT wrapper driver
  • Cursor prompts to help the creation of newer drivers (used to bootstrap this one)

Summary by CodeRabbit

  • New Features

    • Added TMT driver package with CLI to run TMT locally/over SSH and automatic provision-arg handling.
    • Exposed a global CLI --log-level option for composite commands.
  • Documentation

    • New guides for project structure and creating drivers; contributing guide updated with AI-assist notes.
    • TMT driver docs and example configuration added and indexed.
  • Style

    • Improved CLI logging with Rich formatting.
  • Tests

    • Comprehensive test suite for the TMT driver and CLI.

@netlify
Copy link
Copy Markdown

netlify Bot commented Sep 22, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit 78321f4
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/68d270f5b598ec00087ba8e9
😎 Deploy Preview https://deploy-preview-641--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 Sep 22, 2025

Warning

Rate limit exceeded

@mangelajo has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f6dcce9 and 78321f4.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • docs/source/reference/package-apis/drivers/index.md (2 hunks)
  • docs/source/reference/package-apis/drivers/tmt.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2 hunks)
  • packages/jumpstarter-driver-tmt/.gitignore (1 hunks)
  • packages/jumpstarter-driver-tmt/README.md (1 hunks)
  • packages/jumpstarter-driver-tmt/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-tmt/pyproject.toml (1 hunks)

Walkthrough

Adds project and driver-authoring documentation, updates the driver scaffold to normalize module names, introduces a new jumpstarter-driver-tmt package (driver, client CLI, tests, examples, packaging), enables Rich-based logging and a --log-level CLI option in the composite client, and replaces core client default handler with RichHandler.

Changes

Cohort / File(s) Summary
Docs / Contributor guidance
docs/source/contributing.md, .cursor/rules/creating-new-drivers.mdc, .cursor/rules/project-structure.mdc
New and updated docs: AI-assistant guidance, Cursor rules, detailed driver-creation workflow, and monorepo/project structure.
Driver scaffold template
__templates__/create_driver.sh
Introduces DRIVER_MODULE_NAME (hyphens→underscores) and updates generated module paths, README/docs templating, and usage hints.
Composite client CLI & logging
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py, packages/jumpstarter-driver-composite/pyproject.toml
Adds --log-level Click option with callback that installs rich.traceback and sets logger levels; passes click_group to child CLIs when supported; adds rich dependency.
Core client logging
packages/jumpstarter/jumpstarter/client/core.py
Replaces default StreamHandler with rich.logging.RichHandler in AsyncDriverClient initialization.
New TMT driver implementation
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py, packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py
Adds TMT driver (reboot/default creds, requires ssh child) and TMTClient (Click CLI, local/SSH execution, port-forwarding, provision-args rewriting, password redaction).
TMT package metadata, tests, examples
packages/jumpstarter-driver-tmt/pyproject.toml, packages/jumpstarter-driver-tmt/README.md, packages/jumpstarter-driver-tmt/.gitignore, packages/jumpstarter-driver-tmt/examples/exporter.yaml, packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py
Adds packaging config, README, .gitignore, example exporter YAML, and comprehensive tests covering CLI, forwarding, arg rewriting, and driver behavior.
Docs: driver reference
docs/source/reference/package-apis/drivers/index.md, docs/source/reference/package-apis/drivers/tmt.md
Registers TMT driver in drivers index and links package README in reference docs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as CompositeClient CLI
  participant TMTc as TMTClient
  participant SSH as SSH Child Driver
  participant PF as TcpPortforwardAdapter
  participant TMT as Local TMT (subprocess)

  User->>CLI: jumpstarter ... tmt [options] [args]
  Note over CLI: --log-level handled (installs rich.traceback, sets log levels)
  CLI->>TMTc: dispatch tmt command (passes click_group if supported)
  alt --tmt-on-exporter
    TMTc-->>User: not-implemented message
  else Local execution
    TMTc->>TMTc: get_default_user_pass(), get_reboot_cmd()
    alt --forward-ssh
      TMTc->>PF: request forwarded host:port via SSH child
      PF-->>TMTc: forwarded host:port
      TMTc->>TMT: run tmt_cmd with replaced provision args
      TMT-->>TMTc: returncode
    else Direct address
      TMTc->>SSH: address()
      alt address supported
        SSH-->>TMTc: host:port
        TMTc->>TMT: run tmt_cmd with replaced provision args
        TMT-->>TMTc: returncode
      else not implemented
        TMTc->>PF: fallback to port-forward
        PF-->>TMTc: forwarded host:port
        TMTc->>TMT: run tmt_cmd with replaced provision args
        TMT-->>TMTc: returncode
      end
    end
    TMTc-->>User: exit code
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant CCLI as CompositeClient CLI
  participant CB as Click Base Group
  participant Child as Child Client

  User->>CCLI: run composite command
  CCLI->>CCLI: --log-level callback sets logger levels + rich.traceback
  loop register child commands
    CCLI->>Child: call cli(click_group=CB) if accepts it
    alt supports click_group
      Child-->>CCLI: registers into parent group
    else
      Child-->>CCLI: returns standalone command (added)
    end
  end
  CCLI-->>User: commands available with unified log level
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • NickCao
  • kirkbrauer
  • bennyz

Poem

In burrows of code I softly tread,
I scaffold drivers, docs, and thread.
With rich-lit logs and tunnels neat,
I nibble args and forward feet.
Hop-hop the CLI, then off I go—🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Tmtee helper driver" is concise and references the main work (adding a TMT wrapper/helper driver and related tooling), so it is related to the changeset, though the term "Tmtee" appears to be a likely misspelling or nonstandard form of "TMT" which may confuse readers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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

Caution

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

⚠️ Outside diff range comments (2)
packages/jumpstarter/jumpstarter/client/core.py (1)

165-169: Bug: passing a string log level to logger.log() (expects int).

logging.getLevelName(int) returns a string like "DEBUG"; logger.log requires an int level. This will mis-log or fail.

-                self.__log(logging.getLevelName(response.severity), response.message)
+                # severity is expected to be a numeric logging level
+                self.__log(int(response.severity), response.message)
__templates__/create_driver.sh (1)

26-33: Bug: gsed path does not include -i; use arrays for sed command.

On systems with gsed present, in-place edit won’t happen. Using an array handles macOS empty arg safely.

-if command -v gsed &> /dev/null; then
-    sed_cmd="gsed"
-elif [[ "$(uname)" == "Darwin" ]]; then
-    sed_cmd="sed -i ''"
-else
-    sed_cmd="sed -i"
-fi
+if command -v gsed &>/dev/null; then
+    sed_cmd=(gsed -i)
+elif [[ "$(uname)" == "Darwin" ]]; then
+    sed_cmd=(sed -i '')
+else
+    sed_cmd=(sed -i)
+fi
🧹 Nitpick comments (21)
packages/jumpstarter-driver-tmt/.gitignore (1)

1-3: Consider expanding ignores to common Python artifacts.

Recommend adding .pytest_cache, htmlcov/, .mypy_cache/, .ruff_cache/, .venv/, build/, dist/, *.egg-info for consistency across packages.

 __pycache__/
 .coverage
 coverage.xml
+htmlcov/
+.pytest_cache/
+.mypy_cache/
+.ruff_cache/
+*.egg-info
+build/
+dist/
+.venv/
packages/jumpstarter/jumpstarter/client/core.py (1)

74-77: Avoid duplicate logs; normalize level.

  • If root already has handlers, adding one here may duplicate messages via propagation.
  • setLevel() with a lowercase string raises ValueError.
-        self.logger.setLevel(self.log_level)
+        self.logger.setLevel(self.log_level.upper() if isinstance(self.log_level, str) else self.log_level)
...
-        if not self.logger.handlers:
-            handler = RichHandler()
-            self.logger.addHandler(handler)
+        if not self.logger.handlers:
+            handler = RichHandler()
+            self.logger.addHandler(handler)
+            self.logger.propagate = False  # prevent double logging if root is configured
packages/jumpstarter-driver-tmt/pyproject.toml (1)

4-4: Replace placeholder description.

Update description to something meaningful for packaging and docs.

-description = "Add your description here"
+description = "Jumpstarter TMT driver to run TMT locally with SSH/DUT integration"
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2)

11-26: Log setup is heavy-handed and may suppress/override library logger intent.

  • Forcing every existing logger’s level can be surprising; configuring only the root is standard.
  • No handler is configured; raising the level alone may still produce no output.
  • Duplicates functionality that already exists in jumpstarter-cli-common.
-from rich import traceback
+from rich import traceback
+from rich.logging import RichHandler
+from functools import partial
...
-def _opt_log_level_callback(ctx, param, value):
-    traceback.install()
-
-    # Set the log level
-    log_level = value.upper() if value else "INFO"
-
-    # Update the root logger level to ensure all loggers inherit it
-    root_logger = logging.getLogger()
-    root_logger.setLevel(log_level)
-
-    # Update all existing loggers to use the new level
-    for logger_name in logging.Logger.manager.loggerDict:
-        logger = logging.getLogger(logger_name)
-        logger.setLevel(log_level)
+def _opt_log_level_callback(ctx, param, value):
+    traceback.install()
+    basicConfig = partial(logging.basicConfig, handlers=[RichHandler()])
+    basicConfig(level=(value.upper() if value else logging.INFO))

Alternatively, import and reuse the shared helper:

-from rich import traceback
+from jumpstarter_cli_common.opt import _opt_log_level_callback

If adopting the shared helper, ensure jumpstarter-driver-composite depends on jumpstarter-cli-common in its pyproject.toml.


51-58: Child CLI registration: good flexibility, but guard for callables returning None.

If a child cli() unexpectedly returns None, add_command will raise. Consider a small guard.

-                if "click_group" in sig.parameters:
-                    base.add_command(v.cli(click_group=base), k)
-                else:
-                    base.add_command(v.cli(), k)
+                cmd = v.cli(click_group=base) if "click_group" in sig.parameters else v.cli()
+                if cmd is not None:
+                    base.add_command(cmd, k)
__templates__/create_driver.sh (2)

23-25: SC2155: assign in two steps; also safer without export if not needed.

Avoid masking return values and keep env clean.

-# Convert hyphens to underscores for Python module name
-export DRIVER_MODULE_NAME=$(echo "${DRIVER_NAME}" | sed 's/-/_/g')
+# Convert hyphens to underscores for Python module name
+DRIVER_MODULE_NAME="$(printf '%s' "${DRIVER_NAME}" | sed 's/-/_/g')"
+export DRIVER_MODULE_NAME

47-71: In-place README substitutions: invoke sed array; minor hardening.

Use the array form and avoid unquoted var expansions in sed script values.

-$sed_cmd "s/\${DRIVER_CLASS}/${DRIVER_CLASS}/g; s/\${DRIVER_NAME}/${DRIVER_NAME}/g; s/\${DRIVER_MODULE_NAME}/${DRIVER_MODULE_NAME}/g" "${README_FILE}"
+"${sed_cmd[@]}" "s|\${DRIVER_CLASS}|${DRIVER_CLASS}|g; s|\${DRIVER_NAME}|${DRIVER_NAME}|g; s|\${DRIVER_MODULE_NAME}|${DRIVER_MODULE_NAME}|g" "${README_FILE}"

Optional: drop -v from set -euxv to reduce noisy output.

Also applies to: 77-77

.cursor/rules/creating-new-drivers.mdc (1)

106-108: Typo: “Drives” → “Drivers”.

Small wording fix.

-Drives which have children drivers should be composite drivers, and the client interface should
+Drivers which have children drivers should be composite drivers, and the client interface should
docs/source/reference/package-apis/drivers/tmt.md (3)

1-1: Remove stray path line.

The raw path line will render as text; the content below already documents the driver.

-../../../../../packages/jumpstarter-driver-tmt/README.md

67-69: Fix broken line wraps in the example command.

The command is split mid-word; keep it on one line or use backslashes.

-j --log-level DEBUG tmt --root . -c tracing=off -c arch=aarch64 -c distro=rhel-9 -c hw_target=r
-car_s4 run --workdir-root /tmp/ -a -vv provision .. some other provisioning... plan -vv --name ^/podman/plans/fusa/te
-sts$
+j --log-level DEBUG tmt --root . -c tracing=off -c arch=aarch64 -c distro=rhel-9 -c hw_target=rcar_s4 run --workdir-root /tmp/ -a -vv provision .. some other provisioning... plan -vv --name '^/podman/plans/fusa/tests$'

88-91: Avoid documenting plaintext passwords in CLI history.

Suggest env var or prompt-based input to reduce exposure.

docs/source/contributing.md (1)

52-66: Clarify license policy for AI-generated code.

This doc mentions licensing diligence but doesn’t state the repo’s “Apache-2.0 only” policy (documented elsewhere). Add it here to avoid ambiguity for AI-assisted contributions.

Apply this diff:

-This project accepts contributions from AI assistants, although you should be careful when creating code from AI assistants,
+This project accepts contributions from AI assistants, although you should be careful when creating code from AI assistants,
 and figure out if the code you are submitting could infringe any licensing, for example, reusing code from other incompatible
-GPL licenses, you should do your due diligence.
+GPL licenses, you should do your due diligence. Note: this repository only accepts contributions under the Apache-2.0 license.
packages/jumpstarter-driver-tmt/README.md (1)

67-69: Fix broken command wrapping in example.

The example is split mid-token, which makes it unusable when copy-pasted.

Apply this diff to keep it on a single line:

-j --log-level DEBUG tmt --root . -c tracing=off -c arch=aarch64 -c distro=rhel-9 -c hw_target=r
-car_s4 run --workdir-root /tmp/ -a -vv provision .. some other provisioning... plan -vv --name ^/podman/plans/fusa/te
-sts$
+j --log-level DEBUG tmt --root . -c tracing=off -c arch=aarch64 -c distro=rhel-9 -c hw_target=rcar_s4 run --workdir-root /tmp/ -a -vv provision .. some other provisioning... plan -vv --name ^/podman/plans/fusa/tests$
.cursor/rules/project-structure.mdc (1)

128-136: Add guidance on handling secrets in configs.

Key conventions omit secret management; given drivers accept credentials, add a note to avoid plaintext secrets in repo configs.

Apply this diff:

 6. **Dependencies**: Minimal, focused dependencies per package
+7. **Secrets**: Do not commit plaintext credentials in examples; prefer environment variables, secret stores, or redacted placeholders.
packages/jumpstarter-driver-tmt/examples/exporter.yaml (2)

11-15: Avoid plaintext passwords in examples.

Use a placeholder and a comment pointing to a secret source to prevent copy‑pasting real creds into repos.

Apply this diff:

-      default_password: "somePass"
+      # Avoid plaintext in real configs. Use a secret store or env var.
+      default_password: "<password>"

13-14: Keep examples consistent with README.

README uses “somePassword”; this file uses “somePass”. Unify to a single placeholder to reduce confusion.

packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1)

27-33: Add return type hints to exported methods.

Improves API clarity and downstream typing in clients/tests.

Apply this diff:

-    def get_reboot_cmd(self):
+    def get_reboot_cmd(self) -> str:
         return self.reboot_cmd

-    def get_default_user_pass(self):
+    def get_default_user_pass(self) -> tuple[str, str]:
         return self.default_username, self.default_password
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (3)

29-40: Invoke the Click command correctly.

You’re passing “tmt” as an argument to the tmt command. When testing the command itself (not the group), call with its args only.

Apply this diff:

-            result = runner.invoke(cli, ["tmt"])
+            result = runner.invoke(cli, [])

68-72: Same issue: remove redundant subcommand token.

Apply this diff:

-            result = runner.invoke(cli, ["tmt", "--tmt-cmd", "custom-tmt", "test"])
+            result = runner.invoke(cli, ["--tmt-cmd", "custom-tmt", "test"])

156-173: Add fallback test when ssh.address() raises ValueError.

TcpNetwork.address raises ValueError when enable_address is false; client should fall back to forwarding in that case too.

Apply this new test at the end of the file:

def test_drivers_tmt_run_tmt_fallback_on_value_error():
    instance = TMT(children={"ssh": TcpNetwork(host="127.0.0.1", port=22)})
    with serve(instance) as client:
        client.ssh.address = MagicMock(side_effect=ValueError("enable_address mode is not true"))
        with patch('jumpstarter_driver_tmt.client.TcpPortforwardAdapter') as mock_adapter:
            mock_adapter.return_value.__enter__.return_value = ("localhost", 2222)
            with patch.object(client, '_run_tmt_local') as mock_run_tmt:
                mock_run_tmt.return_value = 0
                result = client.run_tmt_local(False, "tmt", "user", "pass", ["arg1"])
                assert result == 0
                mock_run_tmt.assert_called_once_with(
                    "localhost", 2222, "tmt", "user", "pass", "j power cycle", ["arg1"]
                )
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1)

12-18: Docstring naming nit.

References “LocalTMT driver” while the class is TMTClient. Update for consistency.

Apply this diff:

-    Client interface for LocalTMT driver
+    Client interface for TMT driver
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5411b73 and 154a5b8.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .cursor/rules/creating-new-drivers.mdc (1 hunks)
  • .cursor/rules/project-structure.mdc (1 hunks)
  • __templates__/create_driver.sh (5 hunks)
  • docs/source/contributing.md (1 hunks)
  • docs/source/reference/package-apis/drivers/index.md (2 hunks)
  • docs/source/reference/package-apis/drivers/tmt.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2 hunks)
  • packages/jumpstarter-driver-tmt/.gitignore (1 hunks)
  • packages/jumpstarter-driver-tmt/README.md (1 hunks)
  • packages/jumpstarter-driver-tmt/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-tmt/pyproject.toml (1 hunks)
  • packages/jumpstarter/jumpstarter/client/core.py (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-29T11:52:43.554Z
Learnt from: bennyz
PR: jumpstarter-dev/jumpstarter#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-tmt/README.md
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py
  • docs/source/reference/package-apis/drivers/tmt.md
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py
📚 Learning: 2025-02-08T16:08:34.616Z
Learnt from: bennyz
PR: jumpstarter-dev/jumpstarter#267
File: packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py:32-40
Timestamp: 2025-02-08T16:08:34.616Z
Learning: In the TFTP driver (jumpstarter_driver_tftp), the TFTP server component is read-only, while file management operations (upload, delete) are handled through the driver's client interface, not through the TFTP protocol itself.

Applied to files:

  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py
🧬 Code graph analysis (4)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-269)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (4)
packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py (2)
  • _opt_log_level_callback (10-17)
  • callback (217-218)
packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py (6)
  • base (406-407)
  • base (541-543)
  • cli (397-511)
  • cli (539-579)
  • cli (706-730)
  • cli (780-782)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1)
  • cli (20-41)
packages/jumpstarter-cli/jumpstarter_cli/j.py (1)
  • cli (18-22)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (4)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1)
  • client (24-25)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/portforward.py (1)
  • TcpPortforwardAdapter (20-32)
packages/jumpstarter/jumpstarter/client/core.py (1)
  • DriverMethodNotImplemented (39-42)
packages/jumpstarter/jumpstarter/client/base.py (1)
  • call (36-46)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (5)
packages/jumpstarter-driver-network/jumpstarter_driver_network/driver.py (1)
  • TcpNetwork (88-121)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (2)
  • client (24-25)
  • TMT (8-33)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (4)
  • replace_provision_args (90-128)
  • cli (20-41)
  • run_tmt_local (43-69)
  • _run_tmt_local (71-78)
packages/jumpstarter/jumpstarter/client/core.py (1)
  • DriverMethodNotImplemented (39-42)
packages/jumpstarter/jumpstarter/client/base.py (1)
  • call (36-46)
🪛 Shellcheck (0.11.0)
__templates__/create_driver.sh

[warning] 24-24: Declare and assign separately to avoid masking return values.

(SC2155)

⏰ 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). (6)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
🔇 Additional comments (4)
docs/source/reference/package-apis/drivers/index.md (1)

82-83: Docs entry LGTM.

TMT added consistently to list and toctree.

Also applies to: 106-106

packages/jumpstarter/jumpstarter/client/core.py (1)

15-15: No action required — rich is already declared.
packages/jumpstarter/pyproject.toml lists "rich>=14.0.0"; RichHandler imports are present in packages/jumpstarter and packages/jumpstarter-cli-common.

packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1)

19-21: Good config validation.

Requiring an “ssh” child early prevents misconfiguration at runtime.

packages/jumpstarter-driver-tmt/README.md (1)

7-9: Confirm package is published to pkg.jumpstarter.dev/simple or update README.

Comment thread packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py
Comment thread packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py Outdated
Comment thread packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py Outdated
Comment thread packages/jumpstarter-driver-tmt/pyproject.toml
Comment thread packages/jumpstarter-driver-tmt/README.md
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

🧹 Nitpick comments (5)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (1)

51-58: Child CLI wiring is pragmatic; consider broader param name support

You match on a parameter literally named click_group. Some existing drivers use base or group. Consider accepting any single optional parameter for the click group to improve compatibility.

packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (2)

131-139: Adjust mocks for async context manager (aenter)

TcpPortforwardAdapter is an async context manager; mock aenter instead of enter.

-        with patch('jumpstarter_driver_tmt.client.TcpPortforwardAdapter') as mock_adapter:
-            mock_adapter.return_value.__enter__.return_value = ("localhost", 2222)
+        with patch('jumpstarter_driver_tmt.client.TcpPortforwardAdapter') as mock_adapter:
+            mock_adapter.return_value.__aenter__.return_value = ("localhost", 2222)

114-116: If run_tmt_local becomes async, wrap calls with asyncio.run in tests

After fixing the async context manager usage, update direct invocations accordingly.

Example:

-            result = client.run_tmt_local(False, "tmt", None, None, [])
+            import asyncio
+            result = asyncio.run(client.run_tmt_local(False, "tmt", None, None, []))

-            result = client.run_tmt_local(True, "custom-tmt", "user", "pass", ["arg1", "arg2"])
+            result = asyncio.run(client.run_tmt_local(True, "custom-tmt", "user", "pass", ["arg1", "arg2"]))

-                    result = client.run_tmt_local(True, "tmt", "user", "pass", ["arg1"])
+                    result = asyncio.run(client.run_tmt_local(True, "tmt", "user", "pass", ["arg1"]))

-            result = client.run_tmt_local(False, "tmt", "user", "pass", ["arg1"])
+            result = asyncio.run(client.run_tmt_local(False, "tmt", "user", "pass", ["arg1"]))

-                    result = client.run_tmt_local(False, "tmt", "user", "pass", ["arg1"])
+                    result = asyncio.run(client.run_tmt_local(False, "tmt", "user", "pass", ["arg1"]))

Also applies to: 121-123, 135-139, 149-153, 169-173

packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (2)

12-24: Redact more robustly (multiple occurrences, long flags)

Current logic redacts only the first -p. Support repeated and long-form flags.

-def redact_password_in_args(args):
-    """Redact password arguments in a list for safe logging"""
-    redacted = list(args)
-    try:
-        # Find -p flag and redact the next argument (password)
-        p_index = redacted.index("-p")
-        if p_index + 1 < len(redacted):
-            redacted[p_index + 1] = "******"
-    except ValueError:
-        # -p flag not found, nothing to redact
-        pass
-    return redacted
+def redact_password_in_args(args):
+    """Redact password arguments in a list for safe logging"""
+    out = []
+    i = 0
+    while i < len(args):
+        tok = args[i]
+        out.append(tok)
+        if tok in ("-p", "--password") and i + 1 < len(args):
+            out.append("******")
+            i += 2
+            continue
+        i += 1
+    return out

87-103: Subprocess call is safe; consider check=True and richer error messaging later

Non-blocking note: optional improvement for propagation. No action needed now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 154a5b8 and f6dcce9.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .cursor/rules/project-structure.mdc (1 hunks)
  • docs/source/reference/package-apis/drivers/index.md (2 hunks)
  • docs/source/reference/package-apis/drivers/tmt.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2 hunks)
  • packages/jumpstarter-driver-composite/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-tmt/.gitignore (1 hunks)
  • packages/jumpstarter-driver-tmt/README.md (1 hunks)
  • packages/jumpstarter-driver-tmt/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1 hunks)
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-tmt/pyproject.toml (1 hunks)
  • packages/jumpstarter/jumpstarter/client/core.py (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/jumpstarter-driver-composite/pyproject.toml
  • packages/jumpstarter-driver-tmt/README.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/jumpstarter-driver-tmt/.gitignore
  • docs/source/reference/package-apis/drivers/tmt.md
  • .cursor/rules/project-structure.mdc
  • packages/jumpstarter/jumpstarter/client/core.py
  • docs/source/reference/package-apis/drivers/index.md
  • packages/jumpstarter-driver-tmt/examples/exporter.yaml
  • packages/jumpstarter-driver-tmt/pyproject.toml
  • packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py
🧰 Additional context used
🧬 Code graph analysis (3)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (5)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (1)
  • client (24-25)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2)
  • CompositeClient (28-60)
  • cli (37-60)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/portforward.py (1)
  • TcpPortforwardAdapter (20-32)
packages/jumpstarter/jumpstarter/client/core.py (1)
  • DriverMethodNotImplemented (39-42)
packages/jumpstarter/jumpstarter/client/base.py (1)
  • call (36-46)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (6)
packages/jumpstarter-driver-network/jumpstarter_driver_network/driver.py (1)
  • TcpNetwork (88-121)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver.py (2)
  • client (24-25)
  • TMT (8-33)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (4)
  • replace_provision_args (114-156)
  • cli (34-55)
  • run_tmt_local (57-85)
  • _run_tmt_local (87-102)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (1)
  • cli (37-60)
packages/jumpstarter/jumpstarter/client/core.py (1)
  • DriverMethodNotImplemented (39-42)
packages/jumpstarter/jumpstarter/client/base.py (1)
  • call (36-46)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (5)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver.py (2)
  • client (11-12)
  • client (25-26)
packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py (6)
  • base (406-407)
  • base (541-543)
  • cli (397-511)
  • cli (539-579)
  • cli (706-730)
  • cli (780-782)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (1)
  • cli (34-55)
packages/jumpstarter-cli/jumpstarter_cli/j.py (1)
  • cli (18-22)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)
  • cli (19-28)
⏰ 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). (10)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 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 (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (5)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py (2)

11-26: Log-level callback: avoid clobbering all third‑party loggers

Setting every existing logger’s level can override library defaults and spam output. Limit changes to root and our namespaces; also iterate over a snapshot to avoid “dict changed size” risks.
[ suggest_recommended_refactor ]

Apply:

 def _opt_log_level_callback(ctx, param, value):
     traceback.install()

     # Set the log level
     log_level = value.upper() if value else "INFO"

-    # Update the root logger level to ensure all loggers inherit it
+    # Update the root logger level to ensure our loggers inherit it
     root_logger = logging.getLogger()
     root_logger.setLevel(log_level)

-    # Update all existing loggers to use the new level
-    for logger_name in logging.Logger.manager.loggerDict:
-        logger = logging.getLogger(logger_name)
-        logger.setLevel(log_level)
+    # Update only our loggers, avoid touching 3rd-party ones
+    for logger_name in list(logging.Logger.manager.loggerDict.keys()):
+        if logger_name.startswith(("jumpstarter", "jumpstarter_driver_")):
+            logging.getLogger(logger_name).setLevel(log_level)

37-47: Nice: global --log-level option wired at top group

Clean way to initialize Rich tracebacks and log levels before subcommands run.

packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/driver_test.py (1)

165-173: Same: use aenter for the async adapter in fallback test

Mirror the fix here.

-        with patch('jumpstarter_driver_tmt.client.TcpPortforwardAdapter') as mock_adapter:
-            mock_adapter.return_value.__enter__.return_value = ("localhost", 2222)
+        with patch('jumpstarter_driver_tmt.client.TcpPortforwardAdapter') as mock_adapter:
+            mock_adapter.return_value.__aenter__.return_value = ("localhost", 2222)
packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py (2)

83-86: Good: fallback catches ValueError too

Covers TcpNetwork.address() disabled mode.

If any other ssh drivers raise different exceptions on address() failure, consider broadening or normalizing in a shared helper.


57-86: Use async with for TcpPortforwardAdapter (it’s an async context manager)

Current sync with will fail at runtime. Make run_tmt_local async and drive it from the CLI via asyncio.run.

-    def run_tmt_local(self, forward_ssh, tmt_cmd, username, password, args):
+    async def run_tmt_local(self, forward_ssh, tmt_cmd, username, password, args):
@@
-        if forward_ssh:
+        if forward_ssh:
             self.logger.debug("Using SSH port forwarding for TMT connection")
-            with TcpPortforwardAdapter(
-                client=self.ssh,
-            ) as addr:
+            async with TcpPortforwardAdapter(
+                client=self.ssh,
+            ) as addr:
                 host = addr[0]
                 port = addr[1]
                 self.logger.debug(f"SSH port forward established - host: {host}, port: {port}")
                 return self._run_tmt_local(host, port, tmt_cmd, username, password, hard_reboot_cmd, args)
@@
-            except (DriverMethodNotImplemented, ValueError) as e:
+            except (DriverMethodNotImplemented, ValueError) as e:
                 self.logger.warning(f"Direct address connection failed ({e}), falling back to SSH port forwarding")
-                return self.run_tmt_local(True, tmt_cmd, username, password, args)
+                return await self.run_tmt_local(True, tmt_cmd, username, password, args)

And drive it from the CLI:

-                result = self.run_tmt_local(forward_ssh, tmt_cmd, tmt_username, tmt_password, args)
+                import asyncio
+                result = asyncio.run(self.run_tmt_local(forward_ssh, tmt_cmd, tmt_username, tmt_password, args))

If not already imported, add near the top:

+import asyncio

Comment thread packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py
For example:

j command

instead of:
j command subcommand

The code now detects if the cli implementation of the client receives click_group
and passes it down, letting the client driver decide the structure
@mangelajo
Copy link
Copy Markdown
Member Author

Moved several independent details to their own PRs

@michalskrivanek
Copy link
Copy Markdown
Contributor

LGTM

@mangelajo
Copy link
Copy Markdown
Member Author

LGTM

@kirkbrauer we have benny/bella/etc out, can you give me a +1 to merge this driver?

from jumpstarter.client import DriverClient


def _opt_log_level_callback(ctx, param, value):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why can't we just import this from jumpstarter_cli_common?

for k, v in self.children.items():
if hasattr(v, "cli"):
base.add_command(v.cli(), k)
# Check if the cli method accepts a click_group parameter
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does click_group do?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mangelajo mangelajo merged commit 94d7e91 into jumpstarter-dev:main Sep 23, 2025
18 checks passed
@mangelajo
Copy link
Copy Markdown
Member Author

Sorry Nick, I clicked merge without seeing your comments.

@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.

4 participants