Skip to content

feat: kubevirt provisioner - #927

Draft
bennyz wants to merge 1 commit into
jumpstarter-dev:mainfrom
bennyz:kubevirt-provisioner
Draft

feat: kubevirt provisioner#927
bennyz wants to merge 1 commit into
jumpstarter-dev:mainfrom
bennyz:kubevirt-provisioner

Conversation

@bennyz

@bennyz bennyz commented Jul 29, 2026

Copy link
Copy Markdown
Member

experimenting

@mangelajo

Copy link
Copy Markdown
Member

haha surprise provisioner, but honestly probably not a bad idea since now it's very easy to make, we can have a comparison point or alternative if the qemu is problematic at some point and the users don't need any of the special stuff like can or future bluetooth emulation.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds KubeVirt provisioning to the exporter-set controller, including remote VM and exporter Pod creation, parameter validation, driver enrichment, cleanup, and Python power, console, and storage drivers.

Changes

KubeVirt exporter support

Layer / File(s) Summary
Provisioner selection and enrichment contract
controller/cmd/..., controller/internal/exporterset/..., controller/internal/exporterset/provisioners/qemu/...
Provisioner selection now occurs after manager creation, KubeVirt receives the manager client, and exporter context is added to driver enrichment.
Virtual machine and exporter provisioning
controller/internal/exporterset/provisioners/kubevirt/*
KubeVirt parameters, remote clients, VM and exporter Pod rendering, driver configuration, cleanup, and tests are added.
Python KubeVirt runtime drivers
python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py, test_driver.py
Power, console, and storage operations are implemented against KubeVirt and covered by tests.
Python client package and registration
python/packages/jumpstarter-driver-kubevirt/*, python/pyproject.toml
Interactive console and storage clients, package metadata, workspace registration, documentation, and client tests are added.
Go dependency refresh
controller/go.mod, controller/deploy/operator/go.mod
Kubernetes, gRPC, telemetry, YAML, and related indirect dependencies are refreshed.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExporterSetController
  participant KubevirtProvisioner
  participant RemoteKubeVirtCluster
  participant ExporterPod
  participant PythonKubevirtDriver
  ExporterSetController->>KubevirtProvisioner: RenderPod with exporter parameters
  KubevirtProvisioner->>RemoteKubeVirtCluster: Create halted VirtualMachine
  KubevirtProvisioner->>ExporterPod: Build exporter Pod and driver configuration
  PythonKubevirtDriver->>RemoteKubeVirtCluster: Power, console, or storage operation
Loading

Possibly related PRs

Suggested reviewers: raballew, mangelajo

Poem

A rabbit hops where KubeVirt flies,
VMs awaken under cloud-lit skies.
Power and console, storage too,
New drivers carry the work through.
“Hop!” says the bunny, “the cluster is bright!”

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is too vague to convey meaningful information about the change. Replace it with a brief summary of the KubeVirt provisioner work and any key behavior changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a KubeVirt provisioner.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

@bennyz

bennyz commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

haha surprise provisioner, but honestly probably not a bad idea since now it's very easy to make, we can have a comparison point or alternative if the qemu is problematic at some point and the users don't need any of the special stuff like can or future bluetooth emulation.

i wanted to play around with cuttlefish and it's easy getting kubevirt instances internally 😅

it also help find some issues in the general provisioner and also offer some insights into the storage handling

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
python/packages/jumpstarter-driver-kubevirt/README.md (1)

1-11: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Symlink the KubeVirt README into the docs tree.

Driver package READMEs must also be linked from docs/source/reference/package-apis/drivers/; add jumpstarter-driver-kubevirt.md pointing to python/packages/jumpstarter-driver-kubevirt/README.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter-driver-kubevirt/README.md` around lines 1 - 11,
Add the required documentation symlink named jumpstarter-driver-kubevirt.md
under the drivers package API reference directory, targeting
python/packages/jumpstarter-driver-kubevirt/README.md. Preserve the existing
README content and follow the established package README link convention.

Source: Path instructions

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py (1)

254-266: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 20Gi/ReadWriteOnce diverge from the configurable disk parameters used for VM creation.

kubevirt.go's buildVirtualMachine derives DiskSize/AccessModes from provisioning parameters, but flash here hardcodes "20Gi" and ["ReadWriteOnce"], independent of those settings.

Consider passing the configured disk size/access modes through the driver config (mirroring kubeconfig/namespace/vm_name) so flash stays consistent with the VM's actual storage configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`
around lines 254 - 266, Update the flash method’s PVC construction to use
configurable disk size and access modes instead of hardcoded "20Gi" and
["ReadWriteOnce"]. Add these values to the driver configuration alongside
kubeconfig, namespace, and vm_name, initialize them from the provisioning
parameters used by buildVirtualMachine, and have flash reuse the configured
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/internal/exporterset/provisioners/kubevirt/kubevirt.go`:
- Line 247: Replace the resource.MustParse call in the disk-size handling with
fallible quantity parsing, propagate or return the parse error consistently with
the Memory handling in ParseParams, and ensure invalid DiskSize values are
rejected without panicking.
- Around line 161-181: Normalize nil Exporter handling across the KubeVirt
provisioner: update buildVirtualMachine, RenderPod, and EnrichExporterExport so
nil input never dereferences exporter.Name or panics. Preserve the existing
GenerateName behavior in buildExporterPod, or remove it only if all KubeVirt
callers are guaranteed to provide a non-nil Exporter; keep behavior consistent
with the QEMU implementation.

In `@controller/internal/exporterset/provisioners/kubevirt/params.go`:
- Around line 94-122: Update the numeric validation in the resources CPU
handling and forwardPorts loop to reject fractional values rather than allowing
toInt truncation. When cpu or a forwarded port is supplied but is not a valid
positive integer within the existing port range, return a validation error
instead of silently ignoring or truncating it; preserve valid integer handling
and existing field assignments.

In `@controller/internal/exporterset/provisioners/kubevirt/remote.go`:
- Around line 77-79: Update the cached-client lookup in the remote client
provisioning flow to detect changes to the referenced Secret or VTC credentials
reference before returning the cached client. Evict and recreate the entry when
credentials change, or key the cache by a credential revision, while preserving
reuse for unchanged credentials.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py`:
- Around line 1-14: Run make lint-fix to resolve the I001 import-order violation
in client.py, ensuring the import block is formatted according to Ruff’s
automatic sorting rules without manually reordering imports.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`:
- Around line 1-17: Fix the I001 import-order violation in driver.py by running
make lint-fix, ensuring Ruff separates the jumpstarter_driver_* first-party
imports from the third-party websockets imports and applies the canonical
ordering.
- Line 140: Fix the ruff E501 violation on the _build_console_ws_url function
signature by formatting its parameters across multiple lines while preserving
the existing type annotations and return type.
- Around line 119-137: Update the PowerReading construction in read to use only
fields defined by PowerReading, replacing the invalid online argument with the
appropriate existing voltage/current representation. Also update the MockPower
examples to avoid the invalid status field, keeping all PowerReading call sites
consistent with the model definition.
- Around line 271-279: Update the DataVolume replacement flow around
client.replace to catch lightkube.ApiError only, call client.create solely when
the error status code is 404, and re-raise all other API errors unchanged.
- Around line 19-26: Update the optional lightkube import block around
AsyncClient so the ImportError fallback does not rebind the imported class name
to None; use a separate availability marker or consistently typed alias, and
adjust downstream checks to use it while preserving behavior when lightkube is
unavailable. Confirm the change with make pkg-ty-jumpstarter-driver-kubevirt.

In `@python/pyproject.toml`:
- Line 28: Reorder the jumpstarter-driver-kubevirt entry in the
[tool.uv.sources] configuration so it appears before jumpstarter-driver-network,
preserving alphabetical ordering of driver package registrations.

---

Outside diff comments:
In `@python/packages/jumpstarter-driver-kubevirt/README.md`:
- Around line 1-11: Add the required documentation symlink named
jumpstarter-driver-kubevirt.md under the drivers package API reference
directory, targeting python/packages/jumpstarter-driver-kubevirt/README.md.
Preserve the existing README content and follow the established package README
link convention.

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`:
- Around line 254-266: Update the flash method’s PVC construction to use
configurable disk size and access modes instead of hardcoded "20Gi" and
["ReadWriteOnce"]. Add these values to the driver configuration alongside
kubeconfig, namespace, and vm_name, initialize them from the provisioning
parameters used by buildVirtualMachine, and have flash reuse the configured
values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 071b1318-a165-4f4d-aba3-a482868c9c93

📥 Commits

Reviewing files that changed from the base of the PR and between fd206b1 and 15a5693.

⛔ Files ignored due to path filters (3)
  • controller/deploy/operator/go.sum is excluded by !**/*.sum
  • controller/go.sum is excluded by !**/*.sum
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • controller/cmd/exporter-set-controller/main.go
  • controller/deploy/operator/go.mod
  • controller/go.mod
  • controller/internal/exporterset/exporterconfig.go
  • controller/internal/exporterset/provisioner.go
  • controller/internal/exporterset/provisioners/kubevirt/kubevirt.go
  • controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go
  • controller/internal/exporterset/provisioners/kubevirt/params.go
  • controller/internal/exporterset/provisioners/kubevirt/remote.go
  • controller/internal/exporterset/provisioners/qemu/enrich_test.go
  • controller/internal/exporterset/provisioners/qemu/qemu.go
  • python/packages/jumpstarter-driver-kubevirt/README.md
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/__init__.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/py.typed
  • python/packages/jumpstarter-driver-kubevirt/pyproject.toml
  • python/pyproject.toml

Comment on lines +161 to +181
func (p *Provisioner) buildVirtualMachine(
exporter *jumpstarterdevv1alpha1.Exporter,
params *KubevirtParams,
) (*kubevirtv1.VirtualMachine, error) {
runStrategy := kubevirtv1.RunStrategyHalted

memory, err := resource.ParseQuantity(params.Memory)
if err != nil {
return nil, fmt.Errorf("parse memory %q: %w", params.Memory, err)
}

vm := &kubevirtv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: exporter.Name,
Namespace: params.Namespace,
Labels: map[string]string{
labelManagedBy: "jumpstarter",
labelExporter: exporter.Name,
labelProvenance: ProvisionerName,
},
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'kubevirt.go|kubevirt.*|params.go|exporterset' . | sed 's#^\./##' | head -100

echo "== file outline =="
ast-grep outline controller/internal/exporterset/provisioners/kubevirt/kubevirt.go --view expanded || true

echo "== relevant buildVirtualMachine/RenderPod/EnrichExporterExport sections =="
cat -n controller/internal/exporterset/provisioners/kubevirt/kubevirt.go | sed -n '1,470p'

echo "== search RenderPod/EnrichExporterExport usages and nil exporter patterns =="
rg -n "RenderPod|EnrichExporterExport|buildExporterPod|exporter == nil|GenerateName|Name:" controller/internal/exporterset -S

Repository: jumpstarter-dev/jumpstarter

Length of output: 45672


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate interface definitions and provisioner implementations =="
rg -n "type Provisioner|\bRenderPod\b|\bEnrichExporterExport\b|\bEnrichExporterExport\b" controller/internal/exporterset -S

echo "== git diff stat/name-only =="
git diff --stat -- controller/internal/exporterset/provisioners/kubevirt/kubevirt.go
git diff -- controller/internal/exporterset/provisioners/kubevirt/kubevirt.go | sed -n '1,240p'

Repository: jumpstarter-dev/jumpstarter

Length of output: 5441


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== renderer interfaces and comments =="
cat -n controller/internal/exporterset/provisioner.go | sed -n '1,80p'

echo "== controller reconciler RenderPod path =="
cat -n controller/internal/exporterset/reconciler.go | sed -n '400,470p'

echo "== controller reconciler EnrichExporterExport path =="
cat -n controller/internal/exporterset/exporterconfig.go | sed -n '80,145p'

echo "== qemu provisioner nil exporter paths for comparison =="
cat -n controller/internal/exporterset/provisioners/qemu/qemu.go | sed -n '133,205p'
cat -n controller/internal/exporterset/provisioners/qemu/qemu.go | sed -n '275,335p'
cat -n controller/internal/exporterset/provisioners/qemu/enrich_test.go | sed -n '25,50p'

Repository: jumpstarter-dev/jumpstarter

Length of output: 15215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== kubevirt tests around nil exporter/vm name =="
cat -n controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go | sed -n '1,245p'

echo "== direct semantic check: nil exporter untyped dereference would panic if reached =="
python3 - <<'PY'
print("Programmatic evidence: buildExporterPod nil guard exists while buildVirtualMachine and EnrichExporterExport dereference exporter.Name unconditionally.")
print("Call path creates exporter CR; nil path is not exercised in kubevirt tests.")
PY

Repository: jumpstarter-dev/jumpstarter

Length of output: 8281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== createExporterPod callers around exporter lifecycle =="
cat -n controller/internal/exporterset/reconciler.go | sed -n '240,500p'

echo "== all direct nil exporter construction/tests outside kubevirt =="
rg -n "new.*Exporter\b|\bExporter\\b|render Pod|enrich drivers|EnrichExporterExport|RenderPod" controller/internal/exporterset/provisioners controller/internal/exporterset -S

Repository: jumpstarter-dev/jumpstarter

Length of output: 37121


Normalize kubevirt’s nil-exporter handling.

RenderPod/EnrichExporterExport currently receive a non-nil Exporter from the reconciler’s normal scale-up path, but EnrichExporterExport can be called with nil (and the QEMU implementor treats nil as allowed), while buildVirtualMachine and EnrichExporterExport dereference exporter.Name unconditionally. Either update the provisioner interface/consumers to pass a non-nil Exporter for KubeVirt and remove this defensive GenerateName branch in buildExporterPod, or handle nil exporter consistently across RenderPod and EnrichExporterExport without panicking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/kubevirt.go` around
lines 161 - 181, Normalize nil Exporter handling across the KubeVirt
provisioner: update buildVirtualMachine, RenderPod, and EnrichExporterExport so
nil input never dereferences exporter.Name or panics. Preserve the existing
GenerateName behavior in buildExporterPod, or remove it only if all KubeVirt
callers are guaranteed to provide a non-nil Exporter; keep behavior consistent
with the QEMU implementation.

}
}

diskSize := resource.MustParse(params.DiskSize)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

resource.MustParse panics on invalid DiskSize.

Memory uses the fallible resource.ParseQuantity with error handling (line 167), but DiskSize uses resource.MustParse, which panics on malformed quantities. Test coverage only validates memory (TestParseParams_rejectsInvalidMemory), not disk size, so this input's validity isn't demonstrably guaranteed before reaching this call.

🛡️ Proposed fix
-		diskSize := resource.MustParse(params.DiskSize)
+		diskSize, err := resource.ParseQuantity(params.DiskSize)
+		if err != nil {
+			return nil, fmt.Errorf("parse disk size %q: %w", params.DiskSize, err)
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
diskSize := resource.MustParse(params.DiskSize)
diskSize, err := resource.ParseQuantity(params.DiskSize)
if err != nil {
return nil, fmt.Errorf("parse disk size %q: %w", params.DiskSize, err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/kubevirt.go` at line
247, Replace the resource.MustParse call in the disk-size handling with fallible
quantity parsing, propagate or return the parse error consistently with the
Memory handling in ParseParams, and ensure invalid DiskSize values are rejected
without panicking.

Comment on lines +94 to +122
if resources, ok := params["resources"].(map[string]any); ok {
if cpu, ok := toInt(resources["cpu"]); ok && cpu > 0 {
p.CPU = cpu
}
if mem, ok := resources["memory"].(string); ok && mem != "" {
p.Memory = mem
}
if disk, ok := resources["storage"].(string); ok && disk != "" {
p.DiskSize = disk
}
}

if ssh, ok := params["sshAccess"].(string); ok {
switch ssh {
case "", "NodePort", "LoadBalancer":
p.SSHAccess = ssh
default:
return nil, fmt.Errorf("invalid sshAccess %q; must be \"NodePort\", \"LoadBalancer\", or empty", ssh)
}
}

if fwd, ok := params["forwardPorts"].(map[string]any); ok {
p.ForwardPorts = make(map[string]int, len(fwd))
for name, v := range fwd {
if port, ok := toInt(v); ok && port > 0 && port <= 65535 {
p.ForwardPorts[name] = port
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject fractional numeric parameters instead of truncating them.

resources.cpu: 1.9 becomes 1, and forwardPorts.<name>: 2222.5 becomes 2222. Reject non-integral values explicitly; otherwise provisioning uses values the user did not request.

Proposed fix
+import "math"
+
 func toInt(v any) (int, bool) {
   switch n := v.(type) {
   case int:
     return n, true
   case int64:
     return int(n), true
   case float64:
+    if math.Trunc(n) != n {
+      return 0, false
+    }
     return int(n), true
   default:
     return 0, false
   }
 }

Also return a validation error when cpu or a forwarded port is supplied but cannot be converted to a valid positive integer.

Also applies to: 220-232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/params.go` around lines
94 - 122, Update the numeric validation in the resources CPU handling and
forwardPorts loop to reject fractional values rather than allowing toInt
truncation. When cpu or a forwarded port is supplied but is not a valid positive
integer within the existing port range, return a validation error instead of
silently ignoring or truncating it; preserve valid integer handling and existing
field assignments.

Comment on lines +77 to +79
if cl, ok := p.remoteClients.get(vtc.Name); ok {
return cl, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Invalidate cached clients when remote credentials change.

This early return bypasses the Secret permanently after first use. Kubeconfig rotation or a changed credential reference will keep remote provisioning on stale credentials until the controller restarts. Evict this entry when the referenced Secret or VTC credentials reference changes, or key cached clients by a credential revision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/remote.go` around lines
77 - 79, Update the cached-client lookup in the remote client provisioning flow
to detect changes to the referenced Secret or VTC credentials reference before
returning the cached client. Evict and recreate the entry when credentials
change, or key the cache by a credential revision, while preserving reuse for
unchanged credentials.

Comment on lines +19 to +26
try:
from lightkube import AsyncClient
from lightkube.config.kubeconfig import KubeConfig
from lightkube.generic_resource import create_namespaced_resource
from lightkube.models.meta_v1 import ObjectMeta
from lightkube.types import PatchType
except ImportError:
AsyncClient = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix ty type-check failure: implicit shadowing of AsyncClient.

CI's type checker reports AsyncClient is implicitly shadowed — the try branch binds it to the lightkube.AsyncClient class, while except ImportError rebinds it to None, which ty sees as an inconsistent type for the name.

🛠️ Proposed fix
+from typing import Any
+
 try:
     from lightkube import AsyncClient
     from lightkube.config.kubeconfig import KubeConfig
     from lightkube.generic_resource import create_namespaced_resource
     from lightkube.models.meta_v1 import ObjectMeta
     from lightkube.types import PatchType
 except ImportError:
-    AsyncClient = None
+    AsyncClient: Any = None

As per coding guidelines, run make pkg-ty-<package_name> to confirm the fix rather than invoking a type checker manually.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
from lightkube import AsyncClient
from lightkube.config.kubeconfig import KubeConfig
from lightkube.generic_resource import create_namespaced_resource
from lightkube.models.meta_v1 import ObjectMeta
from lightkube.types import PatchType
except ImportError:
AsyncClient = None
from typing import Any
try:
from lightkube import AsyncClient
from lightkube.config.kubeconfig import KubeConfig
from lightkube.generic_resource import create_namespaced_resource
from lightkube.models.meta_v1 import ObjectMeta
from lightkube.types import PatchType
except ImportError:
AsyncClient: Any = None
🧰 Tools
🪛 GitHub Actions: Linters / 2_type-check-python.txt

[error] 26-26: ty error[invalid-assignment]: Implicit shadowing of class AsyncClient. AsyncClient is set to None in except ImportError. Rule: invalid-assignment.

🪛 GitHub Actions: Linters / type-check-python

[error] 26-26: ty error[invalid-assignment]: Implicit shadowing of class AsyncClient. Suggested to annotate to make it explicit. (rule invalid-assignment)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`
around lines 19 - 26, Update the optional lightkube import block around
AsyncClient so the ImportError fallback does not rebind the imported class name
to None; use a separate availability marker or consistently typed alias, and
adjust downstream checks to use it while preserving behavior when lightkube is
unavailable. Confirm the change with make pkg-ty-jumpstarter-driver-kubevirt.

Sources: Coding guidelines, Pipeline failures

Comment thread python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py Outdated
Comment thread python/pyproject.toml
jumpstarter-driver-ridesx = { workspace = true }
jumpstarter-driver-network = { workspace = true }
jumpstarter-driver-obd = { workspace = true }
jumpstarter-driver-kubevirt = { workspace = true }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Place this source entry in alphabetical order.

Move jumpstarter-driver-kubevirt before jumpstarter-driver-network. As per coding guidelines, new driver packages must be registered under [tool.uv.sources] “in alphabetical order.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/pyproject.toml` at line 28, Reorder the jumpstarter-driver-kubevirt
entry in the [tool.uv.sources] configuration so it appears before
jumpstarter-driver-network, preserving alphabetical ordering of driver package
registrations.

Source: Coding guidelines

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
@bennyz
bennyz force-pushed the kubevirt-provisioner branch from 15a5693 to a535e5c Compare July 30, 2026 07:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
controller/internal/exporterset/provisioners/kubevirt/kubevirt.go (1)

161-181: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Inconsistent nil-exporter handling within the same provisioner.

buildVirtualMachine and EnrichExporterExport dereference exporter.Name unconditionally, while buildExporterPod in the same file defensively guards against a nil exporter (L356-360). The Provisioner interface's EnrichExporterExport appears to tolerate nil in some flows (qemu's implementation ignores the param, and its tests pass nil). If a nil Exporter ever reaches these KubeVirt paths, it will panic. Either guarantee callers always pass a non-nil Exporter for KubeVirt (and drop the defensive branch in buildExporterPod for consistency), or handle nil consistently across all three methods.

Also applies to: 425-436

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/kubevirt.go` around
lines 161 - 181, Handle nil exporters consistently across KubeVirt provisioner
methods: update buildVirtualMachine, EnrichExporterExport, and buildExporterPod
to follow the same nil-safe behavior. Preserve existing behavior for non-nil
exporters and avoid dereferencing exporter.Name when the exporter is nil.
controller/internal/exporterset/provisioners/kubevirt/remote.go (1)

77-79: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stale cached client survives credential rotation.

Once cached, this bypasses the Secret permanently. Kubeconfig rotation or a changed CredentialsSecretRef will keep provisioning on stale credentials until the controller restarts. Evict the cache entry when the referenced Secret/credentials reference changes, or key the cache by a credential revision (e.g. Secret resourceVersion).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/remote.go` around lines
77 - 79, Update the cached-client lookup in the provisioner method around
remoteClients.get and associate each client with the current credentials
reference or Secret resourceVersion. Reuse the cached client only when that
credential identity is unchanged; otherwise evict or replace the entry and
create a client from the current Secret so credential rotation takes effect
without restarting the controller.
🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py (1)

20-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_HAS_LIGHTKUBE is dead code that doesn't actually make lightkube optional.

The AsyncClient shadowing bug is fixed, but _HAS_LIGHTKUBE is set and never read anywhere in the file. Since create_namespaced_resource(...) at module scope (lines 37-56) runs unconditionally, a missing lightkube install would still raise NameError at import time regardless of _HAS_LIGHTKUBE. Given lightkube>=0.15 is a required dependency in pyproject.toml, the try/except ImportError is effectively unreachable and misleading — it suggests lightkube is optional when it isn't.

♻️ Proposed simplification (since lightkube is a hard dependency)
-if TYPE_CHECKING:
-    from lightkube import AsyncClient
-
-try:
-    from lightkube import AsyncClient
-    from lightkube.config.kubeconfig import KubeConfig
-    from lightkube.core.exceptions import ApiError
-    from lightkube.generic_resource import create_namespaced_resource
-    from lightkube.models.meta_v1 import ObjectMeta
-    from lightkube.types import PatchType
-
-    _HAS_LIGHTKUBE = True
-except ImportError:
-    _HAS_LIGHTKUBE = False
+from lightkube import AsyncClient
+from lightkube.config.kubeconfig import KubeConfig
+from lightkube.core.exceptions import ApiError
+from lightkube.generic_resource import create_namespaced_resource
+from lightkube.models.meta_v1 import ObjectMeta
+from lightkube.types import PatchType
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`
around lines 20 - 34, Remove the unused _HAS_LIGHTKUBE flag and the surrounding
optional-import try/except in driver.py. Import AsyncClient, KubeConfig,
ApiError, create_namespaced_resource, ObjectMeta, and PatchType directly,
preserving the existing module-scope resource setup and treating lightkube as
the required dependency declared in pyproject.toml.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/internal/exporterset/provisioners/kubevirt/params.go`:
- Around line 220-232: Update toInt to accept float64 values only when they
represent whole numbers, returning false for fractional values instead of
truncating them. Ensure callers handling resources.cpu and forwardPorts
propagate this failure so fractional inputs are rejected rather than silently
dropped.
- Around line 94-97: Add a reasonable maximum CPU core bound to the
resources.cpu validation in params.go, accepting values only when they are
positive and within that limit before assigning p.CPU. Ensure
buildVirtualMachine receives a value that cannot unexpectedly wrap during its
uint32(params.CPU) conversion.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`:
- Around line 131-143: Update the exception handling in read() so unexpected
client errors are logged with their details instead of being silently swallowed.
Preserve the zero-valued PowerReading fallback only if that behavior is
required, while ensuring authentication, RBAC, network, and other failures are
distinguishable from a non-Running VMI.

In `@python/packages/jumpstarter-driver-kubevirt/pyproject.toml`:
- Line 27: Update the KubeVirt package’s dev dependencies to include
pytest-asyncio (at least version 0.26.0), and add asyncio_mode = "auto" under
its [tool.pytest.ini_options] configuration. Keep the existing pytest and
pytest-cov dependencies unchanged.
- Around line 11-18: Add the direct jumpstarter workspace mapping to the
[tool.uv.sources] section in pyproject.toml, using the same workspace
configuration as python/pyproject.toml. Keep the existing child-driver source
mappings unchanged.

---

Duplicate comments:
In `@controller/internal/exporterset/provisioners/kubevirt/kubevirt.go`:
- Around line 161-181: Handle nil exporters consistently across KubeVirt
provisioner methods: update buildVirtualMachine, EnrichExporterExport, and
buildExporterPod to follow the same nil-safe behavior. Preserve existing
behavior for non-nil exporters and avoid dereferencing exporter.Name when the
exporter is nil.

In `@controller/internal/exporterset/provisioners/kubevirt/remote.go`:
- Around line 77-79: Update the cached-client lookup in the provisioner method
around remoteClients.get and associate each client with the current credentials
reference or Secret resourceVersion. Reuse the cached client only when that
credential identity is unchanged; otherwise evict or replace the entry and
create a client from the current Secret so credential rotation takes effect
without restarting the controller.

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`:
- Around line 20-34: Remove the unused _HAS_LIGHTKUBE flag and the surrounding
optional-import try/except in driver.py. Import AsyncClient, KubeConfig,
ApiError, create_namespaced_resource, ObjectMeta, and PatchType directly,
preserving the existing module-scope resource setup and treating lightkube as
the required dependency declared in pyproject.toml.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 008da57d-386b-4b61-965e-71e1beda22a7

📥 Commits

Reviewing files that changed from the base of the PR and between 15a5693 and a535e5c.

⛔ Files ignored due to path filters (3)
  • controller/deploy/operator/go.sum is excluded by !**/*.sum
  • controller/go.sum is excluded by !**/*.sum
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • controller/cmd/exporter-set-controller/main.go
  • controller/deploy/operator/go.mod
  • controller/go.mod
  • controller/internal/exporterset/exporterconfig.go
  • controller/internal/exporterset/provisioner.go
  • controller/internal/exporterset/provisioners/kubevirt/kubevirt.go
  • controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go
  • controller/internal/exporterset/provisioners/kubevirt/params.go
  • controller/internal/exporterset/provisioners/kubevirt/remote.go
  • controller/internal/exporterset/provisioners/qemu/enrich_test.go
  • controller/internal/exporterset/provisioners/qemu/qemu.go
  • python/packages/jumpstarter-driver-kubevirt/README.md
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/__init__.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/py.typed
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_client.py
  • python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_driver.py
  • python/packages/jumpstarter-driver-kubevirt/pyproject.toml
  • python/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter-driver-kubevirt/README.md
  • controller/internal/exporterset/exporterconfig.go

Comment on lines +94 to +97
if resources, ok := params["resources"].(map[string]any); ok {
if cpu, ok := toInt(resources["cpu"]); ok && cpu > 0 {
p.CPU = cpu
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No upper bound on resources.cpu.

Only cpu > 0 is enforced; a very large user-supplied value flows unchecked into uint32(params.CPU) in kubevirt.go (buildVirtualMachine, L187), which static analysis flags as a narrowing conversion that can silently wrap. Add a reasonable upper bound (e.g., a sane max core count) here to prevent downstream truncation to an unexpected value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/params.go` around lines
94 - 97, Add a reasonable maximum CPU core bound to the resources.cpu validation
in params.go, accepting values only when they are positive and within that limit
before assigning p.CPU. Ensure buildVirtualMachine receives a value that cannot
unexpectedly wrap during its uint32(params.CPU) conversion.

Source: Linters/SAST tools

Comment on lines +220 to +232
// toInt converts a numeric interface{} (float64 from JSON, int, etc.) to int.
func toInt(v any) (int, bool) {
switch n := v.(type) {
case int:
return n, true
case int64:
return int(n), true
case float64:
return int(n), true
default:
return 0, false
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fractional numeric params are silently truncated.

toInt truncates float64 (e.g., resources.cpu: 1.91) instead of rejecting non-integral values. This still affects resources.cpu (L95) and forwardPorts (L118), which silently drops invalid ports rather than erroring.

🐛 Proposed fix
+import "math"
+
 func toInt(v any) (int, bool) {
   switch n := v.(type) {
   case int:
     return n, true
   case int64:
     return int(n), true
   case float64:
+    if math.Trunc(n) != n {
+      return 0, false
+    }
     return int(n), true
   default:
     return 0, false
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/exporterset/provisioners/kubevirt/params.go` around lines
220 - 232, Update toInt to accept float64 values only when they represent whole
numbers, returning false for fractional values instead of truncating them.
Ensure callers handling resources.cpu and forwardPorts propagate this failure so
fractional inputs are rejected rather than silently dropped.

Comment on lines +131 to +143
try:
vmi = await client.get(
VirtualMachineInstance,
name=name,
namespace=self.namespace,
)
phase = vmi.get("status", {}).get("phase", "Unknown")
if phase == "Running":
yield PowerReading(voltage=5.0, current=1.0)
else:
yield PowerReading(voltage=0.0, current=0.0)
except Exception:
yield PowerReading(voltage=0.0, current=0.0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silently swallowing all exceptions in read() hides real failures.

Any error (auth failure, RBAC denial, network issue) is reported identically to "VM stopped" with no logging, making it hard to distinguish a genuinely powered-off VM from a broken connection to the cluster.

🩹 Proposed fix
-        except Exception:
+        except Exception:
+            logger.warning("failed to read VMI status for %s/%s", self.namespace, name, exc_info=True)
             yield PowerReading(voltage=0.0, current=0.0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
vmi = await client.get(
VirtualMachineInstance,
name=name,
namespace=self.namespace,
)
phase = vmi.get("status", {}).get("phase", "Unknown")
if phase == "Running":
yield PowerReading(voltage=5.0, current=1.0)
else:
yield PowerReading(voltage=0.0, current=0.0)
except Exception:
yield PowerReading(voltage=0.0, current=0.0)
try:
vmi = await client.get(
VirtualMachineInstance,
name=name,
namespace=self.namespace,
)
phase = vmi.get("status", {}).get("phase", "Unknown")
if phase == "Running":
yield PowerReading(voltage=5.0, current=1.0)
else:
yield PowerReading(voltage=0.0, current=0.0)
except Exception:
logger.warning("failed to read VMI status for %s/%s", self.namespace, name, exc_info=True)
yield PowerReading(voltage=0.0, current=0.0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py`
around lines 131 - 143, Update the exception handling in read() so unexpected
client errors are logged with their details instead of being silently swallowed.
Preserve the zero-valued PowerReading fallback only if that behavior is
required, while ensuring authentication, RBAC, network, and other failures are
distinguishable from a non-Running VMI.

Comment on lines +11 to +18
dependencies = [
"jumpstarter",
"jumpstarter-driver-composite",
"jumpstarter-driver-power",
"jumpstarter-driver-network",
"lightkube>=0.15",
"websockets>=13.0",
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd pyproject.toml python/packages/jumpstarter-driver-network --exec cat -n {}
fd pyproject.toml python/packages/jumpstarter-driver-power --exec cat -n {}

Repository: jumpstarter-dev/jumpstarter

Length of output: 3422


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List workspace/package pyproject files and inspect exact relevant sections.
printf 'workspace pyproject relevant sections:\n'
sed -n '1,140p' python/pyproject.toml

printf '\nkubevirt pyproject relevant sections:\n'
sed -n '1,90p' python/packages/jumpstarter-driver-kubevirt/pyproject.toml

printf '\nother relevant driver/package pyproject files with sources/deps:\n'
for f in $(fd 'pyproject\.toml$' python/packages | sort); do
  if rg -q '^\[tool\.uv\.sources\]|dependencies =|dependency-groups' "$f"; then
    echo "===== $f ====="
    rg -n '(\[project\]|\[tool\.uv\.sources\]|\[dependency-groups\]|name = |dependencies = "jumpstarter"|jumpstarter = { workspace = true })' "$f"
    echo
  fi
done

# Programmatic check: packages with direct "jumpstarter" dependency but no local workspace source mapping.
python3 - <<'PY'
from pathlib import Path
import re
pkg = Path('python/packages')
for tomli_f in sorted(pkg.rglob('pyproject.toml')):
    text = tomli_f.read_text()
    deps = re.search(r'^dependencies\s*=\s*\[(.*?)\]', text, re.S)
    source = re.search(r'^\[tool\.uv\.sources\]\s*\n((?:(?:\S+\s*=\s*\{[^}]+\}[*\n])*)?)', text, re.S)
    has_jumpstarter_dep = deps and bool(re.search(r'"jumpstarter"[^";]+|jumpstarter', deps.group(1)))
    has_jumpstarter_ws = source and 'jumpstarter' in source.group(1)
    if has_jumpstarter_dep:
        print(f"{tomli_f}: has_jumpstarter_dep={has_jumpstarter_dep} has_jumpstarter_ws={has_jumpstarter_ws} has_uv_sources={bool(source)}")
PY

Repository: jumpstarter-dev/jumpstarter

Length of output: 6170


Add jumpstarter = { workspace = true } to [tool.uv.sources].

jumpstarter is a direct dependency, while the child driver dependencies are mapped to the workspace. python/pyproject.toml also declares jumpstarter as a workspace source, so omitting it here leaves this package with inconsistent resolution behavior.

🔧 Proposed fix
 [tool.uv.sources]
+jumpstarter = { workspace = true }
 jumpstarter-driver-composite = { workspace = true }
 jumpstarter-driver-network = { workspace = true }
 jumpstarter-driver-power = { workspace = true }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dependencies = [
"jumpstarter",
"jumpstarter-driver-composite",
"jumpstarter-driver-power",
"jumpstarter-driver-network",
"lightkube>=0.15",
"websockets>=13.0",
]
[tool.uv.sources]
jumpstarter = { workspace = true }
jumpstarter-driver-composite = { workspace = true }
jumpstarter-driver-network = { workspace = true }
jumpstarter-driver-power = { workspace = true }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter-driver-kubevirt/pyproject.toml` around lines 11 -
18, Add the direct jumpstarter workspace mapping to the [tool.uv.sources]
section in pyproject.toml, using the same workspace configuration as
python/pyproject.toml. Keep the existing child-driver source mappings unchanged.

KubevirtStorage = "jumpstarter_driver_kubevirt.driver:KubevirtStorage"

[dependency-groups]
dev = ["pytest>=8.3.2", "pytest-cov>=5.0.0"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "== search for pytest-asyncio across the workspace =="
rg -n "pytest-asyncio|asyncio_mode" python -g '*.toml' -g '*.cfg' -g '*.ini'

Repository: jumpstarter-dev/jumpstarter

Length of output: 3394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== kubevirt marker usages =="
rg -n "`@pytest`\.mark\.asyncio|async def " python/packages/jumpstarter-driver-kubevirt || true

echo
echo "== kubevirt test files =="
fd -a 'test_.*\.py|.*_test\.py' python/packages/jumpstarter-driver-kubevirt | sed 's#^\./##'

echo
echo "== kubevirt pyproject =="
cat -n python/packages/jumpstarter-driver-kubevirt/pyproject.toml | sed -n '1,120p'

echo
echo "== any pytest config files =="
fd -a 'pytest.ini|conftest.py|pyproject.toml' python/packages/jumpstarter-driver-kubevirt . | sort | xargs -r -n1 sh -c 'echo "--- $0"; sed -n "/pytest\|asyncio_mode\|\\[tool\.pytest\\]|\\[tool.hatch|\\[project|\\[build-system\\]|dev =/p" "$0"'

Repository: jumpstarter-dev/jumpstarter

Length of output: 22021


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== python workspace pytest/asyncio deps =="
cat -n python/pyproject.toml | sed -n '1,140p'

echo
echo "== workspace root pytest options and deps =="
rg -n "pytest-asyncio|asyncio_mode|pytest-cov|pytest>=" python/pyproject.toml

Repository: jumpstarter-dev/jumpstarter

Length of output: 5110


Add pytest-asyncio and configure asyncio_mode for the KubeVirt tests.

test_driver.py and test_client.py mark async tests with @pytest.mark.asyncio, but python/packages/jumpstarter-driver-kubevirt/pyproject.toml only declares pytest and pytest-cov in dev; root python/pyproject.toml has no pytest-asyncio dependency or asyncio_mode option. Add the plugin, e.g. pytest-asyncio>=0.26.0, and asyncio_mode = "auto" under [tool.pytest.ini_options] so these tests run correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter-driver-kubevirt/pyproject.toml` at line 27,
Update the KubeVirt package’s dev dependencies to include pytest-asyncio (at
least version 0.26.0), and add asyncio_mode = "auto" under its
[tool.pytest.ini_options] configuration. Keep the existing pytest and pytest-cov
dependencies unchanged.

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