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

Cluster Management Improvements#614

Merged
kirkbrauer merged 29 commits intomainfrom
improve-cluster-setup
Oct 9, 2025
Merged

Cluster Management Improvements#614
kirkbrauer merged 29 commits intomainfrom
improve-cluster-setup

Conversation

@kirkbrauer
Copy link
Copy Markdown
Member

@kirkbrauer kirkbrauer commented Sep 12, 2025

  • Add new jmp admin create cluster sub-command to automatically create a local minikube or kind cluster.
  • Add new jmp admin get clusters sub-command to list available clusters based on user's local kubecontexts.
  • Implement --custom-certs parameter to pass a custom certificate bundle (injected into Kind/Minikube clusters).

Summary by CodeRabbit

  • New Features

    • CLI: add local cluster management commands (create, delete, get/clusters) with rich options and optional Helm-based Jumpstarter install; support multiple values files for Helm installs.
  • Refactor

    • Cluster management moved to a modular Kubernetes cluster API; install/uninstall flows separated from cluster lifecycle.
  • Documentation

    • Installation docs rewritten with step-by-step create/install/delete workflows and examples.
  • Tests

    • Large expansion of tests covering cluster ops, detection, endpoints, Helm, callbacks, models, and CLI flows.
  • Chores

    • Dependency adjustments and minor config updates.

@netlify
Copy link
Copy Markdown

netlify bot commented Sep 12, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit d0d86f0
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/68e7d6429504cc0008244e4f
😎 Deploy Preview https://deploy-preview-614--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 12, 2025

Walkthrough

Refactors cluster management into a new jumpstarter_kubernetes.cluster package (split into submodules), re-exports cluster/controller APIs, adds CLI subcommands for create/get/delete cluster that delegate to the new library, updates install/uninstall to use controller version from jumpstarter_kubernetes, adjusts JSON dumping and deps, and adds extensive tests and docs updates.

Changes

Cohort / File(s) Summary
Docs
docs/source/conf.py, docs/source/getting-started/installation/service/service-local.md
Added sphinx_inline_tabs to Sphinx extensions; updated import path for controller version lookup; rewrote local-cluster docs to separate cluster create/install/delete flows and document new CLI flags/examples.
CLI — admin commands
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py, packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py, packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py, packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py
Added create cluster, delete cluster, get cluster(s) subcommands that call jumpstarter_kubernetes cluster APIs; install/uninstall no longer manage cluster lifecycle and resolve controller-version via jumpstarter_kubernetes; note: duplicate create_cluster definition exists in create.py.
CLI — tests & config
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py, .../delete_test.py, .../install_test.py, .../get_test.py, packages/jumpstarter-cli-admin/pyproject.toml
Large new/updated test suites covering cluster CLI flows and refactored install tests to mock new library helpers; pyproject dependencies changed (removed aiohttp, packaging, semver; added grpcio-reflection>=1.60.0).
CLI common — printing & Click adapter
packages/jumpstarter-cli-common/jumpstarter_cli_common/print.py, packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py
JSON output now uses model.model_dump(mode="json", ...); added ClickCallback adapter to bridge OutputCallback semantics to Click (Force and Silent/confirm variants).
Kubernetes pkg — top-level exports
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py
Re-exported cluster APIs, new Pydantic cluster models, and get_latest_compatible_controller_version; extended __all__.
Kubernetes pkg — cluster package (new)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py
New re-export surface consolidating submodules to preserve previous public API while splitting implementation.
Kubernetes pkg — removed monolith
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster.py (removed)
Removed legacy monolithic cluster module; functionality moved into cluster subpackages.
Kubernetes pkg — cluster submodules
.../cluster/common.py, .../cluster/detection.py, .../cluster/endpoints.py, .../cluster/helm.py, .../cluster/kind.py, .../cluster/minikube.py, .../cluster/kubectl.py, .../cluster/operations.py
Added typed utilities, runtime detection, endpoint config, Helm install wrapper (with values_files), Kind/Minikube lifecycle and cert handling, kubectl orchestration (contexts/check/get/list), and high-level operations (validate_cluster_type_selection, create_cluster_and_install, delete_cluster_by_name).
Kubernetes pkg — models, controller, exceptions, callbacks
.../clusters.py, .../controller.py, .../exceptions.py, .../callbacks.py
Added Pydantic models (V1Alpha1ClusterInfo/List/JumpstarterInstance), implemented get_latest_compatible_controller_version(client_version), introduced domain-specific exceptions, and callback implementations (Silent/Logging/Force).
Kubernetes pkg — tests added / removed
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/*_test.py, packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster_test.py (removed), other ..._test.py files
Comprehensive unit tests added per new cluster submodule; removed legacy combined cluster_test.py; model and utility tests added; some stray prints removed.
Kubernetes pkg — project config
packages/jumpstarter-kubernetes/pyproject.toml
Added runtime deps: aiohttp, semver, packaging to support controller version resolution and HTTP fetching.
Misc test cleanup
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/test_leases.py
Removed stray print statements from tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CLI as jmp (admin CLI)
  participant Ops as jumpstarter_kubernetes.cluster.operations
  participant Provider as kind/minikube module
  participant EP as endpoints
  participant Helm as helm installer

  U->>CLI: jmp admin create cluster [--kind|--minikube|auto ...]
  CLI->>Ops: create_cluster_and_install(params)
  Ops->>Ops: validate_cluster_type_selection(...)
  alt kind
    Ops->>Provider: create_kind_cluster_with_options(...)
  else minikube
    Ops->>Provider: create_minikube_cluster_with_options(...)
  end
  alt install enabled
    Ops->>EP: configure_endpoints(...)
    Ops->>Helm: install_jumpstarter_helm_chart(..., values_files?)
  else skip install
    Ops-->>CLI: cluster created (no install)
  end
  CLI-->>U: status/output
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant CLI as jmp (admin CLI)
  participant Ops as jumpstarter_kubernetes.cluster.operations

  U->>CLI: jmp admin delete cluster [--kind|--minikube|auto] [--force]
  CLI->>Ops: delete_cluster_by_name(name, type?, force)
  Ops->>Ops: validate/auto-detect & confirm
  Ops->>Ops: delegate to delete_kind_cluster_with_feedback / delete_minikube_cluster_with_feedback
  Ops-->>CLI: result/name
  CLI-->>U: printed result
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant CLI as jmp (admin CLI)
  participant K8s as jumpstarter_kubernetes.cluster.kubectl

  U->>CLI: jmp admin get cluster [name?]
  alt name provided
    CLI->>K8s: get_cluster_info(name,...)
    K8s-->>CLI: V1Alpha1ClusterInfo
  else list
    CLI->>K8s: list_clusters(...)
    K8s-->>CLI: V1Alpha1ClusterList
  end
  CLI-->>U: printed model output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

backport release-0.7

Suggested reviewers

  • NickCao
  • mangelajo

Poem

I nibble configs, hop through code,
New clusters sprout where commands are sowed.
Kind or Minikube, seeds take root,
Helm hums softly — a joyful reboot.
I twitch my whiskers: tests all pass! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Cluster Management Improvements” broadly references the key area of change but is too generic and does not specify the new subcommands or custom certificate support introduced in this PR. It relates to the changeset but lacks the specificity needed for a clear summary. Consider revising the title to explicitly mention the new CLI subcommands and features, for example “Add jmp admin create cluster and get clusters commands with custom cert support.”
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improve-cluster-setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mangelajo mangelajo added this to the 0.8.0 milestone Sep 19, 2025
@kirkbrauer kirkbrauer marked this pull request as ready for review September 27, 2025 22:19
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: 16

Caution

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

⚠️ Outside diff range comments (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (1)

149-168: Same: raise on errors for get clusters

Mirror the error handling fix for consistent CLI behavior.

-    except Exception as e:
-        click.echo(f"Error listing clusters: {e}", err=True)
+    except Exception as e:
+        raise click.ClickException(f"Error listing clusters: {e}") from e
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (1)

162-171: Hard-coded cluster name breaks Minikube installs

Locking _configure_endpoints to "jumpstarter-lab" means jmp admin install --minikube now shells out to minikube ip -p jumpstarter-lab. On a stock Minikube setup (profile name "minikube"), this returns a non-zero exit code, so get_ip_generic raises and the install command fails unless the user manually supplies --ip. That’s a regression from the previous flow which accepted --cluster-name (defaulting to the actual Minikube profile). Please restore an argument so callers can pass the profile name and feed it through instead of the fixed literal.

 @click.option("-g", "--grpc-endpoint", type=str, help="The gRPC endpoint to use for the Jumpstarter API", default=None)
 @click.option("-r", "--router-endpoint", type=str, help="The gRPC endpoint to use for the router", default=None)
+@click.option("--cluster-name", type=str, help="Name of the local cluster profile to use", default="jumpstarter-lab")
 @click.option("--nodeport", "mode", flag_value="nodeport", help="Use Nodeport routing (recommended)", default=True)
@@
 async def install(
@@
-    router_endpoint: Optional[str],
+    router_endpoint: Optional[str],
+    cluster_name: str,
@@
-    ip, basedomain, grpc_endpoint, router_endpoint = await _configure_endpoints(
-        cluster_type, minikube or "minikube", "jumpstarter-lab", ip, basedomain, grpc_endpoint, router_endpoint
+    ip, basedomain, grpc_endpoint, router_endpoint = await _configure_endpoints(
+        cluster_type, minikube or "minikube", cluster_name, ip, basedomain, grpc_endpoint, router_endpoint
     )
🧹 Nitpick comments (22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py (3)

55-67: Empty-string inputs should not select a cluster type

Treating "" as a valid selection causes surprising outcomes (e.g., "" vs "minikube" returns "kind"). Prefer normalizing empty strings to None so only explicit selections count.

If you agree, update tests and implementation:

-        result = validate_cluster_type("", "")
-        assert result == "kind"  # First parameter is returned since "" is not None
+        result = validate_cluster_type("", "")
+        assert result is None  # Empty strings treated as not provided

-        result = validate_cluster_type("", "minikube")
-        assert result == "kind"  # Empty string is not None, so kind is returned
+        result = validate_cluster_type("", "minikube")
+        assert result == "minikube"

Outside this file (common.py), normalize inputs before validating:

# common.py
def validate_cluster_type(kind: Optional[str], minikube: Optional[str]) -> Optional[ClusterType]:
    kind = None if kind is not None and kind.strip() == "" else kind
    minikube = None if minikube is not None and minikube.strip() == "" else minikube
    ...

102-108: Consider early validation for custom certs path

Returning an absolute path even when the file doesn’t exist defers failure downstream. For a better UX, validate existence (isfile) and raise ClickException early when a bundle is missing. This will align with the new --custom-certs flow.


164-168: Type hint mismatch in validate_cluster_name(None) test

Runtime behavior is fine, but the function signature is validate_cluster_name(cluster_name: str). Either avoid passing None in tests or change the signature to accept Optional[str] and document None handling.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)

23-29: Handle pagination from Quay API to avoid missing newer tags

Quay’s tag API is paginated; only reading the first page risks selecting an outdated version. Consider following pagination (e.g., using page / has_additional) or requesting a sufficiently large limit.

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (2)

116-129: Avoid shadowing built-in type; use a clearer name

Rename the parameter to cluster_type to improve readability and avoid shadowing.

-@get.command("cluster")
+@get.command("cluster")
@@
-@click.option(
-    "--type", type=click.Choice(["kind", "minikube", "remote", "all"]), default="all", help="Filter clusters by type"
-)
+@click.option(
+    "--type", "cluster_type", type=click.Choice(["kind", "minikube", "remote", "all"]), default="all",
+    help="Filter clusters by type"
+)
@@
-async def get_cluster(
-    name: Optional[str], type: str, kubectl: str, helm: str, kind: str, minikube: str, output: OutputType
-):
+async def get_cluster(
+    name: Optional[str], cluster_type: str, kubectl: str, helm: str, kind: str, minikube: str, output: OutputType
+):
@@
-            cluster_list = await list_clusters(type, kubectl, helm, kind, minikube, check_connectivity=False)
+            cluster_list = await list_clusters(cluster_type, kubectl, helm, kind, minikube, check_connectivity=False)

136-143: Fragile string match for “not found”

Matching substrings in cluster_info.error is brittle. Prefer a structured error indicator (e.g., a boolean not_found or error code) returned by get_cluster_info, then raise ClickException when it’s set. If changing the model is out of scope, at least match the full phrase Context "<name>" not found exactly.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (2)

247-276: Mock cluster-type detection to keep tests hermetic

get_cluster_info calls detect_cluster_type which may shell out. Consider patching it in these tests to avoid environment coupling and flakiness.

@patch("jumpstarter_kubernetes.cluster.kubectl.detect_cluster_type", return_value="kind")

359-366: Verify parameter pass‑through more thoroughly

You assert custom kubectl is used; also assert get_cluster_info was called with custom helm and minikube to catch regressions in wiring.

mock_get_cluster_info.assert_not_called()  # when no contexts
# Or, with contexts:
# mock_get_cluster_info.assert_called_with(ANY, "custom-kubectl", "custom-helm", "custom-minikube", ANY)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (1)

83-103: Optional: parametrize to reduce duplication

Several tests differ only by provided/None parameters. Parametrizing would shorten and clarify the suite without losing intent.

Also applies to: 126-167, 189-210, 212-231

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)

27-31: Expand user home and normalize extra certs path

Handle "~" and return a normalized absolute path to reduce surprises when users pass tilde paths.

 def get_extra_certs_path(extra_certs: Optional[str]) -> Optional[str]:
     """Get the absolute path to extra certificates file if provided."""
     if extra_certs is None:
         return None
-    return os.path.abspath(extra_certs)
+    return os.path.abspath(os.path.expanduser(extra_certs))
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (3)

128-143: Add coverage for --custom-certs pass-through in Kind path

We’re not asserting that extra_certs propagates from _handle_cluster_creation to the wrapper. Add a test to lock this behavior.

 @pytest.mark.asyncio
-@patch("jumpstarter_kubernetes.cluster._create_kind_cluster")
-async def test_handle_cluster_creation_kind(self, mock_create_kind):
+@patch("jumpstarter_kubernetes.cluster._create_kind_cluster")
+async def test_handle_cluster_creation_kind(self, mock_create_kind):
     await _handle_cluster_creation(
         create_cluster=True,
         cluster_type="kind",
         force_recreate_cluster=False,
         cluster_name="test-cluster",
         kind_extra_args="--verbosity=1",
         minikube_extra_args="",
         kind="kind",
         minikube="minikube",
     )
 
-    mock_create_kind.assert_called_once_with("kind", "test-cluster", "--verbosity=1", False, None)
+    mock_create_kind.assert_called_once_with("kind", "test-cluster", "--verbosity=1", False, None)
+
+@pytest.mark.asyncio
+@patch("jumpstarter_kubernetes.cluster._create_kind_cluster")
+async def test_handle_cluster_creation_kind_with_custom_certs(self, mock_create_kind):
+    await _handle_cluster_creation(
+        create_cluster=True,
+        cluster_type="kind",
+        force_recreate_cluster=False,
+        cluster_name="test-cluster",
+        kind_extra_args="",
+        minikube_extra_args="",
+        kind="kind",
+        minikube="minikube",
+        extra_certs="/tmp/ca.crt",
+    )
+    mock_create_kind.assert_called_once_with("kind", "test-cluster", "", False, "/tmp/ca.crt")

201-211: Assert cert preparation for Minikube and --embed-certs flag injection

Add a focused test ensuring prepare_minikube_certs is called and --embed-certs is appended.

 @pytest.mark.asyncio
 @patch("jumpstarter_kubernetes.cluster.minikube_installed")
 @patch("jumpstarter_kubernetes.cluster.create_minikube_cluster")
+@patch("jumpstarter_kubernetes.cluster.operations.prepare_minikube_certs")
-async def test_create_minikube_cluster_success(self, mock_create_minikube, mock_minikube_installed):
+async def test_create_minikube_cluster_with_custom_certs(self, mock_prepare_certs, mock_create_minikube, mock_minikube_installed):
     mock_minikube_installed.return_value = True
-    mock_create_minikube.return_value = True
+    mock_create_minikube.return_value = True
 
-    await _create_minikube_cluster("minikube", "test-cluster", "--memory=4096", False)
+    await _create_minikube_cluster("minikube", "test-cluster", "--memory=4096", False, "/tmp/ca.crt")
 
-    mock_create_minikube.assert_called_once_with("minikube", "test-cluster", ["--memory=4096"], False)
+    args = mock_create_minikube.call_args[0]
+    assert args[:3] == ("minikube", "test-cluster", ["--memory=4096", "--embed-certs"])
+    mock_prepare_certs.assert_called_once_with("/tmp/ca.crt")

347-373: Reduce brittleness of CLI output assertions

Rely on presence checks rather than a strict single call to install_helm_chart and complete output shape, which can change with benign logging additions.

 result = self.runner.invoke(install, ["--kind", "kind"])
 
 assert result.exit_code == 0
-mock_install_helm.assert_called_once()
+# Helm install should be invoked
+assert mock_install_helm.called
 # Verify that kind cluster type was detected
 mock_validate_cluster.assert_called_once_with("kind", None)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (2)

50-65: Avoid brittle call count checks for logging

Keeping assertions to “contains these lines” makes tests resilient to future, benign log additions.

-assert mock_click_echo.call_count == len(expected_calls)
-for _, expected_call in enumerate(expected_calls):
+for _, expected_call in enumerate(expected_calls):
     mock_click_echo.assert_any_call(expected_call)

179-185: Prefer explicit predicate over stringifying call objects

Filter success messages using call.args to avoid depending on the repr of mock calls.

-success_calls = [call for call in mock_click_echo.call_args_list if "Installed Helm release" in str(call)]
+success_calls = [c for c in mock_click_echo.call_args_list if c.args and "Installed Helm release" in c.args[0]]
 assert len(success_calls) == 0
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (2)

139-142: Ensure --embed-certs is appended only once, accounting for “--embed-certs=true” forms

Be robust to value forms by checking startswith to avoid duplicates.

-# Always add --embed-certs for container drivers
-if "--embed-certs" not in extra_args_list:
+# Always add --embed-certs for container drivers
+if not any(a == "--embed-certs" or a.startswith("--embed-certs=") for a in extra_args_list):
     extra_args_list.append("--embed-certs")

188-245: Public surface looks consistent; minor note on dual validators

Both validate_cluster_type (optional) and validate_cluster_type_selection (auto-detect) are exported. Consider clarifying docstrings/names to prevent accidental misuse across CLI vs programmatic paths.

Would you like me to add short docstrings to both functions and a brief note in README to guide when to use each?

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (3)

54-63: Simplify force-recreate flow; unreachable False branch after delete

delete_kind_cluster returns True or raises; the if not await ...: return False branch never executes. Simplify and let exceptions bubble.

-    if cluster_exists:
-        if not force_recreate:
-            raise RuntimeError(f"Kind cluster '{cluster_name}' already exists.")
-        else:
-            if not await delete_kind_cluster(kind, cluster_name):
-                return False
+    if cluster_exists:
+        if not force_recreate:
+            raise RuntimeError(f"Kind cluster '{cluster_name}' already exists.")
+        else:
+            await delete_kind_cluster(kind, cluster_name)

21-25: Use kind get clusters -q to check existence (cheaper and more canonical)

Querying clusters and matching by name avoids invoking kubeconfig generation and reduces noise.

-    try:
-        returncode, _, _ = await run_command([kind, "get", "kubeconfig", "--name", cluster_name])
-        return returncode == 0
+    try:
+        returncode, stdout, _ = await run_command([kind, "get", "clusters", "-q"])
+        return returncode == 0 and any(line.strip() == cluster_name for line in stdout.splitlines())

110-115: Move import os to top-level

Minor hygiene; avoid importing inside finally blocks.

 import shutil
 import tempfile
 from typing import List, Optional
+import os
@@
-        import os
         try:
             os.unlink(config_file)
         except OSError:
             pass

Also applies to: 3-5

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (1)

80-98: Don’t export private underscore helpers in public __all__

Re-exporting underscore-prefixed helpers as part of __all__ broadens the public API surface and invites external use. Keep them importable for tests, but omit from __all__.

     "delete_kind_cluster",
-    "create_cluster_and_install",
-    "create_cluster_only",
-    "delete_cluster_by_name",
-    "get_cluster_info",
-    "list_clusters",
-    "get_ip_generic",
-    "list_kubectl_contexts",
-    "detect_cluster_type",
-    "check_jumpstarter_installation",
-    "_validate_cluster_type",
-    "_configure_endpoints",
-    "_create_kind_cluster",
-    "_create_minikube_cluster",
-    "_delete_kind_cluster",
-    "_delete_minikube_cluster",
-    "_handle_cluster_creation",
-    "_handle_cluster_deletion",
+    "create_cluster_and_install",
+    "create_cluster_only",
+    "delete_cluster_by_name",
+    "get_cluster_info",
+    "list_clusters",
+    "get_ip_generic",
+    "list_kubectl_contexts",
+    "detect_cluster_type",
+    "check_jumpstarter_installation",
     "get_latest_compatible_controller_version",
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (1)

377-382: Add “get clusters” exit-code test (kubectl-compatible behavior)

Add a test ensuring jmp admin get clusters returns exit code 0 when no clusters are found, matching kubectl semantics. Based on learnings.

I can draft a small test (e.g., get_test.py) that patches the list function to return an empty list and asserts exit code 0. Want me to add it?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2d584c and 6e42c88.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • docs/source/conf.py (2 hunks)
  • docs/source/getting-started/installation/service/service-local.md (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (18 hunks)
  • packages/jumpstarter-cli-admin/pyproject.toml (1 hunks)
  • packages/jumpstarter-cli-common/jumpstarter_cli_common/print.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (3 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster.py (0 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster_test.py (0 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (2 hunks)
  • packages/jumpstarter-kubernetes/pyproject.toml (1 hunks)
💤 Files with no reviewable changes (2)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster.py
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster_test.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NickCao
PR: jumpstarter-dev/jumpstarter#463
File: packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py:270-270
Timestamp: 2025-05-28T15:09:35.768Z
Learning: The jumpstarter CLI is designed to match kubectl's behavior, including returning exit code 0 (success) when no resources are found, rather than exit code 1 (failure).
📚 Learning: 2025-05-28T15:09:35.768Z
Learnt from: NickCao
PR: jumpstarter-dev/jumpstarter#463
File: packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py:270-270
Timestamp: 2025-05-28T15:09:35.768Z
Learning: The jumpstarter CLI is designed to match kubectl's behavior, including returning exit code 0 (success) when no resources are found, rather than exit code 1 (failure).

Applied to files:

  • docs/source/getting-started/installation/service/service-local.md
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py
🧬 Code graph analysis (27)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (2)
  • get_cluster_info (195-279)
  • list_clusters (282-320)
packages/jumpstarter-cli-common/jumpstarter_cli_common/print.py (1)
  • model_print (9-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (6)
  • format_cluster_name (34-36)
  • get_extra_certs_path (27-31)
  • run_command (46-55)
  • run_command_with_output (58-64)
  • validate_cluster_name (39-43)
  • validate_cluster_type (12-24)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster (44-115)
  • delete_kind_cluster (28-41)
  • kind_cluster_exists (16-25)
  • kind_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (5)
  • check_jumpstarter_installation (72-192)
  • check_kubernetes_access (12-23)
  • get_cluster_info (195-279)
  • get_kubectl_contexts (26-69)
  • list_clusters (282-320)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (2)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (2)
  • configure_endpoints (28-47)
  • get_ip_generic (11-25)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (1)
  • ip (187-196)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster (45-90)
  • delete_minikube_cluster (29-42)
  • minikube_cluster_exists (17-26)
  • minikube_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (3)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • run_command (46-55)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_cluster_type (105-140)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
packages/jumpstarter/jumpstarter/common/exceptions.py (1)
  • FileNotFoundError (66-69)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/json.py (1)
  • JsonBaseModel (5-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/list.py (1)
  • V1Alpha1List (10-15)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2)
  • install (141-171)
  • ip (187-196)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • install_helm_chart (11-60)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (46-55)
  • run_command_with_output (58-64)
packages/jumpstarter/jumpstarter/common/ipaddr.py (1)
  • get_minikube_ip (29-47)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (3)
  • TestClusterCreation (97-195)
  • setup_method (291-292)
  • setup_method (528-529)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (1)
  • create (39-40)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (46-55)
  • run_command_with_output (58-64)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (9)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • helm_installed (6-8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command_with_output (58-64)
  • validate_cluster_name (39-43)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (3)
  • auto_detect_cluster_type (91-102)
  • detect_existing_cluster_type (59-88)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • configure_endpoints (28-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (10-38)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster (44-115)
  • delete_kind_cluster (28-41)
  • kind_cluster_exists (16-25)
  • kind_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster (45-90)
  • delete_minikube_cluster (29-42)
  • minikube_cluster_exists (17-26)
  • minikube_installed (10-12)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (1)
  • create_cluster (236-303)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (2)
  • _create_kind_cluster (84-119)
  • _create_minikube_cluster (122-157)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (3)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • run_command (46-55)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (2)
  • kind_cluster_exists (16-25)
  • kind_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (2)
  • minikube_cluster_exists (17-26)
  • minikube_installed (10-12)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (5)
packages/jumpstarter-cli-common/jumpstarter_cli_common/version.py (2)
  • version (49-56)
  • get_client_version (12-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (9-61)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (3)
  • helm_installed (6-8)
  • install_helm_chart (11-60)
  • uninstall_helm_chart (63-85)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1)
  • minikube_installed (10-12)
packages/jumpstarter/jumpstarter/common/ipaddr.py (2)
  • get_ip_address (7-26)
  • get_minikube_ip (29-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1)
  • minikube_installed (10-12)
packages/jumpstarter/jumpstarter/common/ipaddr.py (2)
  • get_ip_address (7-26)
  • get_minikube_ip (29-47)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (4)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2)
  • _validate_cluster_type (18-30)
  • ip (187-196)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • create_cluster_and_install (233-306)
packages/jumpstarter-cli-common/jumpstarter_cli_common/version.py (2)
  • version (49-56)
  • get_client_version (12-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (9-61)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (3)
  • TestClusterDeletion (463-522)
  • setup_method (291-292)
  • setup_method (528-529)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (1)
  • delete (28-29)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (6)
  • format_cluster_name (34-36)
  • get_extra_certs_path (27-31)
  • run_command (46-55)
  • run_command_with_output (58-64)
  • validate_cluster_name (39-43)
  • validate_cluster_type (12-24)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (3)
  • auto_detect_cluster_type (91-102)
  • detect_cluster_type (105-140)
  • detect_existing_cluster_type (59-88)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (2)
  • configure_endpoints (28-47)
  • get_ip_generic (11-25)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (10-38)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (5)
  • create_kind_cluster (44-115)
  • delete_kind_cluster (28-41)
  • kind_cluster_exists (16-25)
  • kind_installed (10-12)
  • list_kind_clusters (118-130)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (5)
  • check_jumpstarter_installation (72-192)
  • check_kubernetes_access (12-23)
  • get_cluster_info (195-279)
  • get_kubectl_contexts (26-69)
  • list_clusters (282-320)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (6)
  • create_minikube_cluster (45-90)
  • delete_minikube_cluster (29-42)
  • get_minikube_cluster_ip (110-112)
  • list_minikube_clusters (93-107)
  • minikube_cluster_exists (17-26)
  • minikube_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (7)
  • _handle_cluster_creation (333-374)
  • create_cluster_and_install (233-306)
  • create_cluster_only (309-330)
  • delete_cluster_by_name (191-230)
  • validate_cluster_type_selection (177-188)
  • inject_certs_in_kind (18-48)
  • prepare_minikube_certs (51-75)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (3)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (4)
  • _create_kind_cluster (84-119)
  • _create_minikube_cluster (122-157)
  • _delete_kind_cluster (160-170)
  • _delete_minikube_cluster (173-183)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • _handle_cluster_creation (333-374)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2)
  • install (141-171)
  • _configure_endpoints (40-58)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (5)
  • auto_detect_cluster_type (91-102)
  • detect_cluster_type (105-140)
  • detect_container_runtime (15-26)
  • detect_existing_cluster_type (59-88)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (9)
  • create_cluster_and_install (233-306)
  • create_cluster_only (309-330)
  • create_kind_cluster_wrapper (78-109)
  • create_minikube_cluster_wrapper (112-148)
  • delete_cluster_by_name (191-230)
  • delete_kind_cluster_wrapper (151-161)
  • delete_minikube_cluster_wrapper (164-174)
  • inject_certs_in_kind (18-48)
  • prepare_minikube_certs (51-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (10)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (3)
  • _configure_endpoints (40-58)
  • _validate_cluster_type (18-30)
  • get_ip_generic (91-104)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (4)
  • _create_kind_cluster (84-119)
  • _create_minikube_cluster (122-157)
  • _delete_kind_cluster (160-170)
  • _delete_minikube_cluster (173-183)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (5)
  • _handle_cluster_creation (333-374)
  • _handle_cluster_deletion (377-383)
  • create_cluster_and_install (233-306)
  • create_cluster_only (309-330)
  • delete_cluster_by_name (191-230)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (3)
  • check_jumpstarter_installation (72-192)
  • get_cluster_info (195-279)
  • list_clusters (282-320)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (3)
  • create_kind_cluster (44-115)
  • delete_kind_cluster (28-41)
  • kind_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (3)
  • create_minikube_cluster (45-90)
  • delete_minikube_cluster (29-42)
  • minikube_installed (10-12)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_cluster_type (105-140)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • get_ip_generic (11-25)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (9-61)
packages/jumpstarter-cli-common/jumpstarter_cli_common/print.py (1)
packages/jumpstarter/jumpstarter/client/grpc.py (1)
  • model_dump (238-249)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (10-38)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • delete_cluster_by_name (191-230)
🪛 markdownlint-cli2 (0.18.1)
docs/source/getting-started/installation/service/service-local.md

44-44: Dollar signs used before commands without showing output

(MD014, commands-show-output)


120-120: Dollar signs used before commands without showing output

(MD014, commands-show-output)

⏰ 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). (3)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: e2e
🔇 Additional comments (8)
packages/jumpstarter-cli-admin/pyproject.toml (1)

9-13: All packaging/semver imports removed Verified no remaining packaging or semver imports in packages/jumpstarter-cli-admin.

packages/jumpstarter-kubernetes/pyproject.toml (1)

14-17: Dependency-groups need packaging 25+

Good call bumping to packaging>=25.0; tooling prior to 25.0 can’t parse PEP 751 dependency_groups metadata, so this guards against resolver crashes when our project metadata is inspected. 👍

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py (1)

181-197: Nice coverage of async subprocess helpers

Mocks assert the right exec args and decoding/stripping behavior; not-found errors are surfaced cleanly as RuntimeError. LGTM.

Also applies to: 213-217, 219-229, 231-246

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (1)

18-20: Import usage looks correct

Re-exported functions from jumpstarter_kubernetes are used directly. LGTM.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (1)

19-71: Comprehensive coverage; assertions read well

Good use of AsyncIO patches and explicit stderr messages. JSON parsing and error paths are exercised thoroughly. LGTM.

Also applies to: 73-165, 166-246, 303-366

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (1)

11-33: Solid, scenario‑driven tests

Covers success and failure paths for Minikube/Kind and all defaulting logic for endpoints. Mocks are precise; outputs validated clearly. LGTM.

Also applies to: 44-78, 80-115, 126-188, 190-210, 212-231, 233-251, 253-267

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (1)

23-25: list_kubectl_contexts export verified
Defined via from .kubectl import get_kubectl_contexts as list_kubectl_contexts in cluster/__init__.py and included in the module’s __all__.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1)

64-92: Make NodePort range and host port mappings configurable

  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (lines 64–92) hard-codes the NodePort range (3000–32767) and specific hostPort mappings (5080/5443/8082/8083); extract these into function parameters or CLI flags to avoid conflicts on developer machines.
  • Unable to locate any Helm chart values in the repo—please verify that these NodePort assignments match your service chart defaults.

@mangelajo mangelajo enabled auto-merge October 2, 2025 09:11
Copy link
Copy Markdown
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

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

tested it, works great :)

@kirkbrauer kirkbrauer force-pushed the improve-cluster-setup branch from 3f7f08b to 4855326 Compare October 4, 2025 19:33
@kirkbrauer kirkbrauer disabled auto-merge October 4, 2025 19:33
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: 9

♻️ Duplicate comments (1)
docs/source/getting-started/installation/service/service-local.md (1)

18-21: Polish the new bullet description

To stay consistent with the other entries (imperative verbs) and prior guidance, please tweak this bullet to read “Lists the local clusters detected from your kubeconfig contexts” (or similar). That keeps the grammar aligned with the surrounding list.

🧹 Nitpick comments (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)

22-27: Tighten the callback type annotation

callback defaults to None, so its type should be Optional[OutputCallback]; today’s signature violates the protocol contract and will upset type checkers. Update the annotation accordingly to keep static analysis quiet.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e42c88 and c8bfc6b.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • docs/source/getting-started/installation/service/service-local.md (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (2 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (2 hunks)
  • packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (3 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/__init__.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/test_leases.py (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/test_leases.py
✅ Files skipped from review due to trivial changes (1)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/init.py
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-28T15:09:35.768Z
Learnt from: NickCao
PR: jumpstarter-dev/jumpstarter#463
File: packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py:270-270
Timestamp: 2025-05-28T15:09:35.768Z
Learning: The jumpstarter CLI is designed to match kubectl's behavior, including returning exit code 0 (success) when no resources are found, rather than exit code 1 (failure).

Applied to files:

  • docs/source/getting-started/installation/service/service-local.md
🧬 Code graph analysis (22)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (20)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
  • warning (23-25)
  • warning (51-53)
  • warning (82-84)
  • warning (114-116)
  • error (27-29)
  • error (55-57)
  • error (86-88)
  • error (118-120)
  • confirm (31-33)
  • confirm (59-61)
  • confirm (90-93)
  • confirm (122-124)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (3)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (2)
  • ClickCallback (10-47)
  • ForceClickCallback (50-59)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • delete_cluster_by_name (46-93)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • JumpstarterKubernetesError (8-11)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (2)
  • get_cluster_info (198-281)
  • list_clusters (284-321)
packages/jumpstarter-cli-common/jumpstarter_cli_common/print.py (1)
  • model_print (9-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ClusterTypeValidationError (79-82)
  • ClusterNameValidationError (85-90)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • run_command (52-76)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (2)
  • kind_cluster_exists (25-34)
  • kind_installed (19-21)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (2)
  • minikube_cluster_exists (26-74)
  • minikube_installed (21-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • install_helm_chart (11-60)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (5)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1)
  • ClickCallback (10-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (2)
  • create_cluster_and_install (96-184)
  • validate_cluster_type_selection (32-43)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • JumpstarterKubernetesError (8-11)
packages/jumpstarter-cli-common/jumpstarter_cli_common/version.py (2)
  • version (49-56)
  • get_client_version (12-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (9-74)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/__init__.py (6)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (3)
  • check_jumpstarter_installation (75-195)
  • get_cluster_info (198-281)
  • list_clusters (284-321)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (4)
  • create_cluster_and_install (96-184)
  • create_cluster_only (187-210)
  • delete_cluster_by_name (46-93)
  • validate_cluster_type_selection (32-43)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_cluster_type (112-147)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • get_ip_generic (10-24)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (9-74)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster (100-157)
  • delete_minikube_cluster (77-97)
  • minikube_cluster_exists (26-74)
  • minikube_installed (21-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (1)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (6)
  • progress (21-24)
  • success (26-29)
  • warning (31-34)
  • error (36-39)
  • confirm (41-47)
  • confirm (57-59)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • run_command (52-76)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • KubeconfigError (71-76)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_cluster_type (112-147)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (9)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (18)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • confirm (31-33)
  • confirm (59-61)
  • confirm (90-93)
  • confirm (122-124)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
  • warning (23-25)
  • warning (51-53)
  • warning (82-84)
  • warning (114-116)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (5)
  • ClusterNameValidationError (85-90)
  • ClusterNotFoundError (25-35)
  • ClusterOperationError (48-60)
  • ClusterTypeValidationError (79-82)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • helm_installed (6-8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • validate_cluster_name (44-49)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (2)
  • auto_detect_cluster_type (97-109)
  • detect_existing_cluster_type (59-94)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • configure_endpoints (27-46)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (9-41)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster_with_options (179-215)
  • delete_kind_cluster_with_feedback (218-229)
  • kind_cluster_exists (25-34)
  • kind_installed (19-21)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster_with_options (211-247)
  • delete_minikube_cluster_with_feedback (250-263)
  • minikube_cluster_exists (26-74)
  • minikube_installed (21-23)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (3)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (1)
  • TestClusterCreation (63-114)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (1)
  • setup_method (257-258)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (1)
  • create (41-42)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (3)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (2)
  • configure_endpoints (27-46)
  • get_ip_generic (10-24)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ToolNotInstalledError (14-22)
  • EndpointConfigurationError (93-98)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (1)
  • ip (187-196)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (1)
  • setup_method (380-381)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (1)
  • delete (30-31)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (5)
  • auto_detect_cluster_type (97-109)
  • detect_cluster_type (112-147)
  • detect_container_runtime (14-26)
  • detect_existing_cluster_type (59-94)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (9-41)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (8)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • EndpointConfigurationError (93-98)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1)
  • minikube_installed (21-23)
packages/jumpstarter/jumpstarter/common/ipaddr.py (2)
  • get_ip_address (7-26)
  • get_minikube_ip (29-47)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2)
  • get_ip_generic (91-104)
  • ip (187-196)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install_test.py (5)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (1)
  • SilentCallback (36-61)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1)
  • create_kind_cluster_with_options (179-215)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1)
  • create_minikube_cluster_with_options (211-247)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • validate_cluster_type_selection (32-43)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • ClusterTypeValidationError (79-82)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (4)
  • CertificateError (63-68)
  • ClusterAlreadyExistsError (38-45)
  • ClusterOperationError (48-60)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (52-76)
  • run_command_with_output (79-95)
packages/jumpstarter/jumpstarter/common/ipaddr.py (1)
  • get_minikube_ip (29-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (4)
  • CertificateError (63-68)
  • ClusterAlreadyExistsError (38-45)
  • ClusterOperationError (48-60)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (52-76)
  • run_command_with_output (79-95)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (3)
  • create_cluster_and_install (96-184)
  • create_cluster_only (187-210)
  • delete_cluster_by_name (46-93)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (3)
  • ClusterNotFoundError (25-35)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
🪛 GitHub Check: ruff
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py

[failure] 97-97: Ruff (E501)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py:97:121: E501 Line too long (121 > 120)

🪛 markdownlint-cli2 (0.18.1)
docs/source/getting-started/installation/service/service-local.md

45-45: Dollar signs used before commands without showing output

(MD014, commands-show-output)


121-121: Dollar signs used before commands without showing output

(MD014, commands-show-output)

⏰ 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 (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (1)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (1)

208-211: Allow ~/env vars in --extra-certs paths

click.Path(resolve_path=True) doesn't expand the user’s home (~) or env vars, so a very common input like --extra-certs ~/corp.pem fails validation. Enable expansion so the new feature works with home-directory bundles.

 @click.option(
     "--extra-certs",
-    type=click.Path(exists=True, readable=True, dir_okay=False, resolve_path=True),
+    type=click.Path(
+        exists=True,
+        readable=True,
+        dir_okay=False,
+        resolve_path=True,
+        expand_user=True,
+    ),

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1)

80-82: Wrap long assertion to satisfy Ruff E501.

Similar to the minikube test below (lines 91-102, which was correctly fixed), this assertion line exceeds the 120-character limit. Please split the call across multiple lines.

Apply this diff to fix the line length:

         mock_create_and_install.assert_called_once_with(
-            "kind", False, "test-cluster", "", "", "kind", "minikube", None, install_jumpstarter=False, callback=None
+            "kind",
+            False,
+            "test-cluster",
+            "",
+            "",
+            "kind",
+            "minikube",
+            None,
+            install_jumpstarter=False,
+            callback=None,
         )
🧹 Nitpick comments (9)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (2)

1234-1336: Move imports to module level for consistency.

The imports of V1Alpha1ClusterInfo, V1Alpha1ClusterList, and V1Alpha1JumpstarterInstance at lines 1237, 1260, and 1292 are redundant since these types are already imported at the top of the file (lines 4-20). Remove the duplicate imports from inside the test functions.

Apply this diff to clean up the imports:

 @patch("jumpstarter_cli_admin.get.get_cluster_info")
 def test_get_cluster_by_name(get_cluster_info_mock: AsyncMock):
-    from jumpstarter_kubernetes import V1Alpha1ClusterInfo, V1Alpha1JumpstarterInstance
-
     runner = CliRunner()
     cluster_info = V1Alpha1ClusterInfo(
 @patch("jumpstarter_cli_admin.get.get_cluster_info")
 def test_get_cluster_not_found(get_cluster_info_mock: AsyncMock):
-    from jumpstarter_kubernetes import V1Alpha1ClusterInfo, V1Alpha1JumpstarterInstance
-
     runner = CliRunner()
     cluster_info = V1Alpha1ClusterInfo(
 @patch("jumpstarter_cli_admin.get.list_clusters")
 def test_get_clusters_list(list_clusters_mock: AsyncMock):
-    from jumpstarter_kubernetes import V1Alpha1ClusterInfo, V1Alpha1ClusterList, V1Alpha1JumpstarterInstance
-
     runner = CliRunner()
     cluster_list = V1Alpha1ClusterList(

1234-1336: Consider adding output format tests for cluster commands.

The cluster tests only verify the default (table) output format. For consistency with tests for other resources (clients, exporters, leases), consider adding tests for JSON, YAML, and name output formats. This would ensure the cluster commands properly support all output options.

Example test structure to add:

@patch("jumpstarter_cli_admin.get.get_cluster_info")
def test_get_cluster_json_output(get_cluster_info_mock: AsyncMock):
    runner = CliRunner()
    cluster_info = V1Alpha1ClusterInfo(...)
    get_cluster_info_mock.return_value = cluster_info
    result = runner.invoke(get, ["cluster", "kind-test", "--output", "json"])
    assert result.exit_code == 0
    assert json_equal(result.output, EXPECTED_JSON)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters_test.py (2)

108-167: Consider adding edge case tests for rich table rendering.

The existing tests for rich_add_columns and rich_add_rows are well-structured and use appropriate mocking. However, consider adding tests for edge cases such as:

  • Exporters with empty device lists
  • Exporters with None status
  • Exporters with devices that have empty or None labels

These scenarios may occur in real usage and should be tested to ensure robust table rendering.


177-262: Consider adding test for empty exporter list.

The list tests are comprehensive and properly validate delegation to individual exporter methods. However, consider adding a test for an empty V1Alpha1ExporterList to ensure the rich rendering methods handle this edge case gracefully.

Example test to add:

def test_exporter_list_empty():
    """Test V1Alpha1ExporterList with no items"""
    from unittest.mock import MagicMock
    from jumpstarter_kubernetes import V1Alpha1ExporterList
    
    exporter_list = V1Alpha1ExporterList(items=[])
    mock_table = MagicMock()
    exporter_list.rich_add_rows(mock_table, devices=False)
    assert mock_table.add_row.call_count == 0
    
    names = []
    exporter_list.rich_add_names(names)
    assert names == []
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients_test.py (2)

126-236: Consider consolidating imports at module level.

Several tests import MagicMock and V1Alpha1ClientList inside the function body (lines 126, 137, 146, 168, 210, 221, 231), while V1Alpha1Client is imported at the top. For consistency and clarity, consider moving these to the module-level imports.

Apply this diff to consolidate imports:

+from unittest.mock import MagicMock
+
 from kubernetes_asyncio.client.models import V1ObjectMeta
 
-from jumpstarter_kubernetes import V1Alpha1Client, V1Alpha1ClientStatus
+from jumpstarter_kubernetes import V1Alpha1Client, V1Alpha1ClientList, V1Alpha1ClientStatus

Then remove the inline imports from the test functions.


194-194: Invalid UUID format in test data.

The UID contains non-hexadecimal characters: "8b36fc92-7554-48fd-b73g-61276cggfef9" (note the 'g' characters). While this doesn't affect test functionality since it's just test data, using a valid UUID format would better represent production data.

Apply this diff:

-                    "uid": "8b36fc92-7554-48fd-b73g-61276cggfef9",
+                    "uid": "8b36fc92-7554-48fd-b73f-61276cffef9",
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters_test.py (3)

6-17: Verify column order with assert_has_calls.

Using assert_any_call checks that each column was added but doesn't verify the order. Since column order matters for table display, consider using assert_has_calls with ordered=True to ensure columns are added in the correct sequence.

Apply this diff:

 def test_cluster_info_rich_add_columns():
     """Test that ClusterInfo can add columns to a rich table"""
     mock_table = MagicMock()
     V1Alpha1ClusterInfo.rich_add_columns(mock_table)
     assert mock_table.add_column.call_count == 7
-    mock_table.add_column.assert_any_call("CURRENT")
-    mock_table.add_column.assert_any_call("NAME")
-    mock_table.add_column.assert_any_call("TYPE")
-    mock_table.add_column.assert_any_call("STATUS")
-    mock_table.add_column.assert_any_call("JUMPSTARTER")
-    mock_table.add_column.assert_any_call("VERSION")
-    mock_table.add_column.assert_any_call("NAMESPACE")
+    from unittest.mock import call
+    expected_calls = [
+        call("CURRENT"),
+        call("NAME"),
+        call("TYPE"),
+        call("STATUS"),
+        call("JUMPSTARTER"),
+        call("VERSION"),
+        call("NAMESPACE"),
+    ]
+    mock_table.add_column.assert_has_calls(expected_calls, any_order=False)

77-97: Add assertions for all row fields in error test.

The test only verifies the jumpstarter status field (args[4]) but doesn't check that other fields are still rendered correctly when an error occurs. Consider adding assertions for all seven row fields to ensure the error case is fully covered.

Apply this diff:

     cluster_info.rich_add_rows(mock_table)
     mock_table.add_row.assert_called_once()
     args = mock_table.add_row.call_args[0]
+    assert args[0] == "*"  # current
+    assert args[1] == "test-cluster"  # name
+    assert args[2] == "kind"  # type
+    assert args[3] == "Running"  # status
     assert args[4] == "Error"  # jumpstarter status shows error
+    assert args[5] == "-"  # version placeholder when error
+    assert args[6] == "-"  # namespace placeholder when error

1-188: Consider using pytest fixtures to reduce test data duplication.

Several tests create similar V1Alpha1ClusterInfo instances with only minor variations. Extracting common test data into pytest fixtures would reduce duplication and improve maintainability.

Example approach:

import pytest

@pytest.fixture
def base_cluster_info():
    """Base cluster info with common fields"""
    return {
        "name": "test-cluster",
        "cluster": "test-cluster",
        "server": "https://127.0.0.1:6443",
        "user": "test-user",
        "namespace": "default",
    }

@pytest.fixture
def running_cluster_with_jumpstarter(base_cluster_info):
    """Cluster that's current, running, with Jumpstarter installed"""
    return V1Alpha1ClusterInfo(
        **base_cluster_info,
        is_current=True,
        type="kind",
        accessible=True,
        jumpstarter=V1Alpha1JumpstarterInstance(
            installed=True,
            version="0.1.0",
            namespace="jumpstarter",
        ),
    )

def test_cluster_info_rich_add_rows_current_running_installed(running_cluster_with_jumpstarter):
    """Test rich table row for current, running cluster with Jumpstarter installed"""
    mock_table = MagicMock()
    running_cluster_with_jumpstarter.rich_add_rows(mock_table)
    # ... assertions
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8bfc6b and 63e5ba0.

📒 Files selected for processing (7)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (2 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients_test.py (2 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/datetime_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters_test.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (3)
  • ForceCallback (96-124)
  • LoggingCallback (64-93)
  • SilentCallback (36-61)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/datetime_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/datetime.py (1)
  • time_since (4-32)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py (10)
  • V1Alpha1Client (27-69)
  • from_dict (34-54)
  • from_dict (76-77)
  • rich_add_columns (57-60)
  • rich_add_columns (80-81)
  • rich_add_rows (62-66)
  • rich_add_rows (83-85)
  • rich_add_names (68-69)
  • rich_add_names (87-89)
  • V1Alpha1ClientList (72-89)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py (1)
  • ClientsV1Alpha1Api (92-165)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py (1)
  • ExportersV1Alpha1Api (123-200)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/leases.py (1)
  • LeasesV1Alpha1Api (151-166)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1JumpstarterInstance (9-23)
  • V1Alpha1ClusterList (79-94)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (3)
  • create_cluster_and_install (96-184)
  • create_cluster_only (187-210)
  • delete_cluster_by_name (46-93)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (3)
  • ClusterNotFoundError (25-35)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py (10)
  • V1Alpha1Exporter (31-100)
  • from_dict (38-59)
  • from_dict (107-108)
  • rich_add_columns (62-73)
  • rich_add_columns (111-112)
  • rich_add_rows (75-97)
  • rich_add_rows (114-116)
  • rich_add_names (99-100)
  • rich_add_names (118-120)
  • V1Alpha1ExporterList (103-120)
⏰ 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: pytest-matrix (macos-15, 3.13)
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (3)

15-68: Well-structured deletion tests.

The test class thoroughly covers the delete_cluster_by_name operation, including auto-detection paths for both kind and minikube, explicit type specification, and error handling for non-existent clusters. The use of ANY for callback arguments is appropriate.


84-102: LGTM! Proper line formatting.

The minikube test correctly splits the assertion arguments across multiple lines, satisfying the line length requirement.


105-149: Comprehensive installation tests.

The test class effectively covers the create_cluster_and_install operation, including the success path with proper mocking of helm, cluster creation, endpoint configuration, and chart installation. Error paths are also well-tested for missing helm and missing version scenarios.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks_test.py (3)

7-34: LGTM! Comprehensive coverage of SilentCallback.

The test suite thoroughly validates SilentCallback's no-op behavior for all message methods and confirms the auto-confirm behavior for prompts. The tests are clear, well-documented, and appropriate for the implementation.


36-85: LGTM! Excellent test coverage with proper mocking.

The test suite effectively validates LoggingCallback's logging behavior:

  • Verifies both default and custom logger initialization
  • Confirms correct log levels for each message type
  • Validates the exact confirmation message format
  • Uses proper mocking techniques to isolate the logger

87-136: LGTM! Thorough validation of forwarding behavior.

The test suite comprehensively validates ForceCallback's delegation pattern:

  • Verifies default and custom output callback initialization
  • Confirms all message methods correctly forward to the output callback
  • Critically validates that confirm() returns True without forwarding (line 136), which is the key behavior for force mode

The use of assert_not_called() on line 136 is particularly important for verifying the intended non-forwarding behavior of confirmations.

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (2)

23-23: LGTM!

The import of ConfigException is necessary for the new kubeconfig error handling tests.


1203-1231: LGTM!

The ConfigException tests properly verify error handling when kubeconfig issues occur. The tests follow the existing pattern used for ApiException tests and correctly check for kubeconfig-related error messages.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters_test.py (4)

1-21: LGTM! Well-structured test fixture.

The imports are clean and the TEST_EXPORTER fixture provides comprehensive test data that's reused effectively across multiple tests.


24-77: LGTM! Comprehensive serialization tests.

Both JSON and YAML serialization tests validate the complete output format, ensuring proper field naming and structure.


80-105: LGTM! Solid deserialization test.

The test validates from_dict correctly reconstructs the object and verifies key fields including metadata, status, and nested device information.


170-174: LGTM! Clean test for name formatting.

The test correctly validates the expected name format with the jumpstarter.dev namespace.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients_test.py (1)

62-236: Excellent test coverage!

The new test suite comprehensively covers V1Alpha1Client and V1Alpha1ClientList functionality including:

  • Deserialization scenarios (with/without credential, with/without status)
  • Rich table rendering (columns, rows, names)
  • Both single-item and list operations

All assertions correctly match the implementation from clients.py.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/datetime_test.py (1)

1-147: LGTM! Comprehensive test coverage with correct mocking approach.

The test suite thoroughly covers all branches of the time_since function across different time ranges (seconds, minutes, hours, days, months, years). The mocking strategy is correct:

  • mock_datetime.strptime.return_value = past.replace(tzinfo=None) accurately simulates real strptime behavior (returns naive datetime)
  • The implementation correctly adds UTC timezone via .replace(tzinfo=timezone.utc)
  • mock_datetime.now.return_value = now provides deterministic test behavior

Test structure is clear with descriptive names and docstrings. The month/year approximations (30 days/month, 365 days/year) are appropriate for relative time display.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77749b4 and e119b40.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (2 hunks)
  • packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1 hunks)
  • packages/jumpstarter-kubernetes/pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl_test.py
🧰 Additional context used
🧬 Code graph analysis (9)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (3)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (3)
  • ClickCallback (10-47)
  • ForceClickCallback (50-59)
  • SilentWithConfirmCallback (62-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)
  • delete_cluster_by_name (46-100)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • JumpstarterKubernetesError (8-11)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • JumpstarterKubernetesError (8-11)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py (3)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py (1)
  • setup_method (380-381)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete.py (1)
  • delete (30-31)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (2)
  • SilentWithConfirmCallback (62-75)
  • ForceClickCallback (50-59)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (4)
  • CertificateError (63-68)
  • ClusterAlreadyExistsError (38-45)
  • ClusterOperationError (48-60)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (52-76)
  • run_command_with_output (79-95)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (9)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (18)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • confirm (31-33)
  • confirm (59-61)
  • confirm (90-93)
  • confirm (122-124)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
  • warning (23-25)
  • warning (51-53)
  • warning (82-84)
  • warning (114-116)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (5)
  • ClusterNameValidationError (89-94)
  • ClusterNotFoundError (25-35)
  • ClusterOperationError (48-60)
  • ClusterTypeValidationError (79-86)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • helm_installed (6-8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • validate_cluster_name (44-49)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (2)
  • auto_detect_cluster_type (97-109)
  • detect_existing_cluster_type (59-94)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • configure_endpoints (27-46)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (9-41)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster_with_options (179-215)
  • delete_kind_cluster_with_feedback (218-229)
  • kind_cluster_exists (25-34)
  • kind_installed (19-21)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster_with_options (211-247)
  • delete_minikube_cluster_with_feedback (250-263)
  • minikube_cluster_exists (26-74)
  • minikube_installed (21-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kubectl.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clusters.py (3)
  • V1Alpha1ClusterInfo (26-76)
  • V1Alpha1ClusterList (79-94)
  • V1Alpha1JumpstarterInstance (9-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • run_command (52-76)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • KubeconfigError (71-76)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_cluster_type (112-147)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (3)
  • create_cluster_and_install (103-191)
  • create_cluster_only (194-217)
  • delete_cluster_by_name (46-100)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (4)
  • ClusterNotFoundError (25-35)
  • ClusterTypeValidationError (79-86)
  • ToolNotInstalledError (14-22)
  • ClusterOperationError (48-60)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (20)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
  • warning (23-25)
  • warning (51-53)
  • warning (82-84)
  • warning (114-116)
  • error (27-29)
  • error (55-57)
  • error (86-88)
  • error (118-120)
  • confirm (31-33)
  • confirm (59-61)
  • confirm (90-93)
  • confirm (122-124)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (2)
  • configure_endpoints (27-46)
  • get_ip_generic (10-24)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (2)
  • EndpointConfigurationError (97-102)
  • ToolNotInstalledError (14-22)
⏰ 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). (9)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)

@kirkbrauer kirkbrauer enabled auto-merge (squash) October 4, 2025 22:29
@kirkbrauer kirkbrauer requested a review from mangelajo October 4, 2025 22:30
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: 6

Caution

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

⚠️ Outside diff range comments (2)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (2)

140-156: Let --kind / --minikube behave as flags again.
These options still require an argument because of is_flag=False; folks running jmp admin install --kind hit Click’s “option requires an argument” error. Removing the explicit is_flag=False restores the flag semantics without breaking existing usage.

 @click.option(
-    "--kind", is_flag=False, flag_value="kind", default=None, help="Use default settings for a local Kind cluster"
+    "--kind", flag_value="kind", default=None, help="Use default settings for a local Kind cluster"
 )
 @click.option(
     "--minikube",
-    is_flag=False,
     flag_value="minikube",
     default=None,
     help="Use default settings for a local Minikube cluster",
 )

6-204: Convert JumpstarterKubernetesError into ClickExceptions in install.
get_latest_compatible_controller_version() now sits on the hot path and raises JumpstarterKubernetesError (e.g., network hiccups). At the moment the exception bubbles straight to the user with a stack trace. Import the exception type and wrap the version lookup + helm install so the CLI reports a clean Click error.

-from jumpstarter_kubernetes import (
-    get_latest_compatible_controller_version,
-    helm_installed,
-    install_helm_chart,
-    minikube_installed,
-    uninstall_helm_chart,
-)
+from jumpstarter_kubernetes import (
+    get_latest_compatible_controller_version,
+    helm_installed,
+    install_helm_chart,
+    minikube_installed,
+    uninstall_helm_chart,
+)
+from jumpstarter_kubernetes.exceptions import JumpstarterKubernetesError
@@
-    if version is None:
-        version = await get_latest_compatible_controller_version(get_client_version())
-
-    await _install_jumpstarter_helm_chart(
-        chart,
-        name,
-        namespace,
-        basedomain,
-        grpc_endpoint,
-        router_endpoint,
-        mode,
-        version,
-        kubeconfig,
-        context,
-        helm,
-        ip,
-        list(values_files) if values_files else None,
-    )
+    try:
+        if version is None:
+            version = await get_latest_compatible_controller_version(get_client_version())
+
+        await _install_jumpstarter_helm_chart(
+            chart,
+            name,
+            namespace,
+            basedomain,
+            grpc_endpoint,
+            router_endpoint,
+            mode,
+            version,
+            kubeconfig,
+            context,
+            helm,
+            ip,
+            list(values_files) if values_files else None,
+        )
+    except JumpstarterKubernetesError as e:
+        raise click.ClickException(str(e)) from e
♻️ Duplicate comments (1)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (1)

192-199: Restore flag behavior for --kind / --minikube.
With is_flag=False, jmp admin create cluster --kind (or --minikube) now errors because Click insists on a value. Please drop the explicit is_flag=False so these work as boolean flags again, matching the help text and previous UX.

-@click.option("--kind", is_flag=False, flag_value="kind", default=None, help="Create a local Kind cluster")
+@click.option("--kind", flag_value="kind", default=None, help="Create a local Kind cluster")
 @click.option(
     "--minikube",
-    is_flag=False,
     flag_value="minikube",
     default=None,
     help="Create a local Minikube cluster",
 )
🧹 Nitpick comments (3)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (1)

3-3: Import MagicMock at module level.

MagicMock is imported inside individual test methods (lines 16, 205). Move this import to the module level alongside the existing patch import for consistency and to avoid redundant imports.

Apply this diff:

-from unittest.mock import patch
+from unittest.mock import MagicMock, patch

Then remove the redundant imports from lines 16 and 205.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (2)

83-84: Remove redundant validation check.

Lines 83-84 validate that cluster_type is in ["kind", "minikube"], but this check is unreachable with a bad value:

  • If cluster_type was explicitly provided (line 60), it was already validated on lines 71-73.
  • If cluster_type was auto-detected (lines 76-79), detect_existing_cluster_type only returns "kind", "minikube", or None.

At this point in the flow, cluster_type is guaranteed to be valid.

Apply this diff to remove the dead code:

-    # Validate cluster type is supported for deletion
-    if cluster_type not in ["kind", "minikube"]:
-        raise ClusterTypeValidationError(cluster_type, ["kind", "minikube"])
-
     # Confirm deletion unless force is specified

198-221: Consider renaming custom_certs to extra_certs for consistency.

Line 206 uses the parameter name custom_certs, but it corresponds to the extra_certs parameter in create_cluster_and_install (line 111). While this works correctly due to positional argument passing, using consistent naming would improve readability.

Apply this diff:

 async def create_cluster_only(
     cluster_type: ClusterType,
     force_recreate_cluster: bool,
     cluster_name: str,
     kind_extra_args: str,
     minikube_extra_args: str,
     kind: str,
     minikube: str,
-    custom_certs: Optional[str] = None,
+    extra_certs: Optional[str] = None,
     callback: OutputCallback = None,
 ) -> None:
     """Create a cluster without installing Jumpstarter."""
     await create_cluster_and_install(
         cluster_type,
         force_recreate_cluster,
         cluster_name,
         kind_extra_args,
         minikube_extra_args,
         kind,
         minikube,
-        custom_certs,
+        extra_certs,
         install_jumpstarter=False,
         callback=callback,
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e119b40 and ac6937a.

📒 Files selected for processing (10)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (3 hunks)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (6 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py (1 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (2 hunks)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations_test.py
🧰 Additional context used
🧬 Code graph analysis (8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install_test.py (1)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (2)
  • helm_installed (6-8)
  • install_helm_chart (11-66)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (9)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (18)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • confirm (31-33)
  • confirm (59-61)
  • confirm (90-93)
  • confirm (122-124)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
  • warning (23-25)
  • warning (51-53)
  • warning (82-84)
  • warning (114-116)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (5)
  • ClusterNameValidationError (89-94)
  • ClusterNotFoundError (25-35)
  • ClusterOperationError (48-60)
  • ClusterTypeValidationError (79-86)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • helm_installed (6-8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (1)
  • validate_cluster_name (44-49)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (2)
  • auto_detect_cluster_type (97-109)
  • detect_existing_cluster_type (59-94)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/endpoints.py (1)
  • configure_endpoints (27-46)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (9-53)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster_with_options (181-217)
  • delete_kind_cluster_with_feedback (220-231)
  • kind_cluster_exists (24-33)
  • kind_installed (19-21)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (4)
  • create_minikube_cluster_with_options (211-247)
  • delete_minikube_cluster_with_feedback (250-263)
  • minikube_cluster_exists (26-74)
  • minikube_installed (21-23)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (4)
  • CertificateError (63-68)
  • ClusterAlreadyExistsError (38-45)
  • ClusterOperationError (48-60)
  • ToolNotInstalledError (14-22)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/common.py (2)
  • run_command (52-76)
  • run_command_with_output (79-95)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/detection.py (1)
  • detect_kind_provider (29-56)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)
  • install_jumpstarter_helm_chart (9-53)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (8)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/install.py (5)
packages/jumpstarter-cli-common/jumpstarter_cli_common/version.py (2)
  • version (49-56)
  • get_client_version (12-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (10-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (3)
  • helm_installed (6-8)
  • install_helm_chart (11-66)
  • uninstall_helm_chart (69-91)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/minikube.py (1)
  • minikube_installed (21-23)
packages/jumpstarter/jumpstarter/common/ipaddr.py (2)
  • get_ip_address (7-26)
  • get_minikube_ip (29-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/callbacks.py (10)
  • OutputCallback (12-33)
  • SilentCallback (36-61)
  • progress (15-17)
  • progress (43-45)
  • progress (74-76)
  • progress (106-108)
  • success (19-21)
  • success (47-49)
  • success (78-80)
  • success (110-112)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/install.py (1)
  • install_helm_chart (11-66)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py (5)
packages/jumpstarter-cli-common/jumpstarter_cli_common/callbacks.py (1)
  • ClickCallback (10-47)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (2)
  • create_cluster_and_install (103-195)
  • validate_cluster_type_selection (32-43)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • JumpstarterKubernetesError (8-11)
packages/jumpstarter-cli-common/jumpstarter_cli_common/version.py (2)
  • version (49-56)
  • get_client_version (12-14)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/controller.py (1)
  • get_latest_compatible_controller_version (10-75)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind_test.py (2)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (4)
  • create_kind_cluster (52-125)
  • delete_kind_cluster (36-49)
  • kind_cluster_exists (24-33)
  • kind_installed (19-21)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exceptions.py (1)
  • ClusterAlreadyExistsError (38-45)
🔇 Additional comments (8)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py (3)

13-56: LGTM!

This test properly verifies that all parameters are correctly forwarded to install_helm_chart and that the callback is invoked appropriately.


139-162: LGTM!

This test correctly verifies that exceptions from install_helm_chart propagate to the caller.


201-243: LGTM!

This test properly verifies that the values_files parameter is correctly passed through to install_helm_chart.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm.py (1)

9-53: LGTM!

The function correctly wraps the Helm installation workflow with progress logging and proper callback handling. The parameter passing to install_helm_chart matches its signature, and the callback defaulting pattern is appropriate.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py (1)

103-196: LGTM!

The orchestration logic correctly handles cluster creation, validation, endpoint configuration, and optional Jumpstarter installation. Past review comments about unsupported cluster types have been addressed with the explicit validation on lines 157-158.

packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/kind.py (3)

143-178: LGTM! Path expansion correctly handles ~ and environment variables.

Lines 149-150 properly expand user home directories and environment variables before resolving the absolute path, which addresses the past review comment about --custom-certs path handling. The certificate injection flow is robust with proper error handling.


181-217: LGTM! Past callback parameter issue has been addressed.

The function correctly calls create_kind_cluster without passing the callback parameter (line 201), and the exception handling properly distinguishes between creation and recreation failures. The past review comment about the stray callback argument has been resolved.


220-231: LGTM! Past callback parameter issue has been addressed.

The function correctly calls delete_kind_cluster without passing the callback parameter (line 229), which resolves the past review comment about the incorrect parameter passing.

Comment on lines +271 to +319
cluster_type = validate_cluster_type_selection(kind, minikube)

if output is None:
if kind is None and minikube is None:
click.echo(f"Auto-detected {cluster_type} as the cluster type")
if skip_install:
click.echo(f'Creating {cluster_type} cluster "{name}"...')
else:
click.echo(f'Creating {cluster_type} cluster "{name}" and installing Jumpstarter...')

# Auto-detect version if not specified and installing Jumpstarter
if not skip_install and version is None:
from jumpstarter_cli_common.version import get_client_version
from jumpstarter_kubernetes import get_latest_compatible_controller_version

version = await get_latest_compatible_controller_version(get_client_version())

# Create callback for library functions
# Use silent mode when JSON/YAML output is requested
callback = ClickCallback(silent=(output is not None))

try:
await create_cluster_and_install(
cluster_type,
force_recreate,
name,
kind_extra_args,
minikube_extra_args,
kind or "kind",
minikube or "minikube",
extra_certs,
install_jumpstarter=not skip_install,
helm=helm,
chart=chart,
chart_name=chart_name,
namespace=namespace,
version=version,
kubeconfig=kubeconfig,
context=context,
ip=ip,
basedomain=basedomain,
grpc_endpoint=grpc_endpoint,
router_endpoint=router_endpoint,
callback=callback,
values_files=list(values_files) if values_files else None,
)
except JumpstarterKubernetesError as e:
# Convert library exceptions to CLI exceptions
raise click.ClickException(str(e)) from e
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Catch K8s errors early and honor --nointeractive.
validate_cluster_type_selection and get_latest_compatible_controller_version can raise JumpstarterKubernetesError, but they currently sit outside the try, so the CLI surfaces a raw stack trace. In addition, --nointeractive keeps prompting because the callback always calls click.confirm. Move the pre-flight logic inside the try and bypass confirmation when non-interactive.

-    cluster_type = validate_cluster_type_selection(kind, minikube)
-
-    if output is None:
-        if kind is None and minikube is None:
-            click.echo(f"Auto-detected {cluster_type} as the cluster type")
-        if skip_install:
-            click.echo(f'Creating {cluster_type} cluster "{name}"...')
-        else:
-            click.echo(f'Creating {cluster_type} cluster "{name}" and installing Jumpstarter...')
-
-    # Auto-detect version if not specified and installing Jumpstarter
-    if not skip_install and version is None:
-        from jumpstarter_cli_common.version import get_client_version
-        from jumpstarter_kubernetes import get_latest_compatible_controller_version
-
-        version = await get_latest_compatible_controller_version(get_client_version())
-
-    # Create callback for library functions
-    # Use silent mode when JSON/YAML output is requested
-    callback = ClickCallback(silent=(output is not None))
-
-    try:
-        await create_cluster_and_install(
+    try:
+        cluster_type = validate_cluster_type_selection(kind, minikube)
+
+        if output is None:
+            if kind is None and minikube is None:
+                click.echo(f"Auto-detected {cluster_type} as the cluster type")
+            if skip_install:
+                click.echo(f'Creating {cluster_type} cluster "{name}"...')
+            else:
+                click.echo(f'Creating {cluster_type} cluster "{name}" and installing Jumpstarter...')
+
+        # Auto-detect version if not specified and installing Jumpstarter
+        if not skip_install and version is None:
+            from jumpstarter_cli_common.version import get_client_version
+            from jumpstarter_kubernetes import get_latest_compatible_controller_version
+
+            version = await get_latest_compatible_controller_version(get_client_version())
+
+        # Create callback for library functions
+        # Use silent mode when JSON/YAML output is requested
+        callback = ClickCallback(silent=(output is not None))
+        if nointeractive:
+            callback.confirm = lambda prompt: True
+
+        await create_cluster_and_install(
             cluster_type,
             force_recreate,
             name,
@@
             router_endpoint=router_endpoint,
             callback=callback,
             values_files=list(values_files) if values_files else None,
         )
-    except JumpstarterKubernetesError as e:
-        # Convert library exceptions to CLI exceptions
-        raise click.ClickException(str(e)) from e
+    except JumpstarterKubernetesError as e:
+        # Convert library exceptions to CLI exceptions
+        raise click.ClickException(str(e)) from e
📝 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
cluster_type = validate_cluster_type_selection(kind, minikube)
if output is None:
if kind is None and minikube is None:
click.echo(f"Auto-detected {cluster_type} as the cluster type")
if skip_install:
click.echo(f'Creating {cluster_type} cluster "{name}"...')
else:
click.echo(f'Creating {cluster_type} cluster "{name}" and installing Jumpstarter...')
# Auto-detect version if not specified and installing Jumpstarter
if not skip_install and version is None:
from jumpstarter_cli_common.version import get_client_version
from jumpstarter_kubernetes import get_latest_compatible_controller_version
version = await get_latest_compatible_controller_version(get_client_version())
# Create callback for library functions
# Use silent mode when JSON/YAML output is requested
callback = ClickCallback(silent=(output is not None))
try:
await create_cluster_and_install(
cluster_type,
force_recreate,
name,
kind_extra_args,
minikube_extra_args,
kind or "kind",
minikube or "minikube",
extra_certs,
install_jumpstarter=not skip_install,
helm=helm,
chart=chart,
chart_name=chart_name,
namespace=namespace,
version=version,
kubeconfig=kubeconfig,
context=context,
ip=ip,
basedomain=basedomain,
grpc_endpoint=grpc_endpoint,
router_endpoint=router_endpoint,
callback=callback,
values_files=list(values_files) if values_files else None,
)
except JumpstarterKubernetesError as e:
# Convert library exceptions to CLI exceptions
raise click.ClickException(str(e)) from e
try:
cluster_type = validate_cluster_type_selection(kind, minikube)
if output is None:
if kind is None and minikube is None:
click.echo(f"Auto-detected {cluster_type} as the cluster type")
if skip_install:
click.echo(f'Creating {cluster_type} cluster "{name}"...')
else:
click.echo(f'Creating {cluster_type} cluster "{name}" and installing Jumpstarter...')
# Auto-detect version if not specified and installing Jumpstarter
if not skip_install and version is None:
from jumpstarter_cli_common.version import get_client_version
from jumpstarter_kubernetes import get_latest_compatible_controller_version
version = await get_latest_compatible_controller_version(get_client_version())
# Create callback for library functions
# Use silent mode when JSON/YAML output is requested
callback = ClickCallback(silent=(output is not None))
if nointeractive:
callback.confirm = lambda prompt: True
await create_cluster_and_install(
cluster_type,
force_recreate,
name,
kind_extra_args,
minikube_extra_args,
kind or "kind",
minikube or "minikube",
extra_certs,
install_jumpstarter=not skip_install,
helm=helm,
chart=chart,
chart_name=chart_name,
namespace=namespace,
version=version,
kubeconfig=kubeconfig,
context=context,
ip=ip,
basedomain=basedomain,
grpc_endpoint=grpc_endpoint,
router_endpoint=router_endpoint,
callback=callback,
values_files=list(values_files) if values_files else None,
)
except JumpstarterKubernetesError as e:
# Convert library exceptions to CLI exceptions
raise click.ClickException(str(e)) from e

Comment on lines +93 to +94
# Verify success message with correct values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or complete the incomplete assertion.

The comment suggests verification of success messages, but no assertion follows. Either remove this comment or add the corresponding callback verification.

If callback verification is intended, add assertions similar to the first test:

# Verify success message with correct values
assert mock_callback.success.call_count == 1

Or remove the comment if no additional verification is needed.

🤖 Prompt for AI Agents
In packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py
around lines 93 to 94, there is an incomplete comment "Verify success message
with correct values" with no assertion; either remove the comment or add the
missing callback assertion(s). If callback verification is intended, add an
assertion like asserting mock_callback.success.call_count == 1 (and any other
expected calls/args) to mirror the first test; otherwise delete the comment to
avoid dead code.

Comment on lines +115 to +116
# Verify gRPC mode is correctly displayed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or complete the incomplete assertion.

The comment suggests verification of gRPC mode display, but no assertion follows. Either remove this comment or add callback verification to ensure the mode is correctly logged.

🤖 Prompt for AI Agents
In packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py
around lines 115-116, there is an incomplete comment "Verify gRPC mode is
correctly displayed" with no assertion; either delete the stray comment or
replace it with an assertion that verifies the logging/callback was invoked with
the expected gRPC-mode message (e.g., assert the mocked callback/logger was
called_once_with or contains the expected substring), ensuring the test sets up
and inspects the mock correctly and fails if the mode is not logged.

Comment on lines +137 to +138
# Verify custom endpoints are displayed correctly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or complete the incomplete assertion.

The comment suggests verification of custom endpoint display, but no assertion follows. Either remove this comment or add callback verification.

🤖 Prompt for AI Agents
In packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py
around lines 137 to 138, there's a trailing comment "Verify custom endpoints are
displayed correctly" with no assertion; remove this dangling comment or replace
it with an actual assertion/callback verification that checks the function
output or mock was called with the expected custom endpoints (e.g., assert
output contains the endpoint strings or assert
mock_display.called_with(expected_endpoints)). Ensure the test exercises the
code path that renders endpoints and validates the expected display values, or
delete the comment if no check is required.

Comment on lines +199 to +200
# Verify appropriate echo calls were made

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or complete the incomplete assertion.

The comment suggests verification of callback calls, but no assertion follows. Either remove this comment or add the corresponding callback verification.

🤖 Prompt for AI Agents
In packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/helm_test.py
around lines 199-200 there is a stray comment "Verify appropriate echo calls
were made" with no assertion; either remove that comment or add the missing
assertion(s) that verify the mocked echo/callbacks were invoked with the
expected arguments (e.g., use the existing mock variables in the test and call
assert_called_once_with / assert_has_calls or check call_count/args accordingly)
so the test actually verifies the expected echo behavior.

Comment on lines +32 to +44
def validate_cluster_type_selection(kind: Optional[str], minikube: Optional[str]) -> ClusterType:
"""Validate cluster type selection and return the cluster type."""
if kind and minikube:
raise ClusterTypeValidationError('You can only select one local cluster type "kind" or "minikube"')

if kind is not None:
return "kind"
elif minikube is not None:
return "minikube"
else:
# Auto-detect cluster type when neither is specified
return auto_detect_cluster_type()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix exception constructor call.

Line 35 passes a message string as the first argument to ClusterTypeValidationError, but the exception's __init__ expects cluster_type: str as the first parameter (see exceptions.py lines 78-85). This causes a confusing error message.

Apply this diff:

-    if kind and minikube:
-        raise ClusterTypeValidationError('You can only select one local cluster type "kind" or "minikube"')
+    if kind and minikube:
+        raise ClusterTypeValidationError("both", ["kind", "minikube"])
📝 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
def validate_cluster_type_selection(kind: Optional[str], minikube: Optional[str]) -> ClusterType:
"""Validate cluster type selection and return the cluster type."""
if kind and minikube:
raise ClusterTypeValidationError('You can only select one local cluster type "kind" or "minikube"')
if kind is not None:
return "kind"
elif minikube is not None:
return "minikube"
else:
# Auto-detect cluster type when neither is specified
return auto_detect_cluster_type()
def validate_cluster_type_selection(kind: Optional[str], minikube: Optional[str]) -> ClusterType:
"""Validate cluster type selection and return the cluster type."""
if kind and minikube:
raise ClusterTypeValidationError("both", ["kind", "minikube"])
if kind is not None:
return "kind"
elif minikube is not None:
return "minikube"
else:
# Auto-detect cluster type when neither is specified
return auto_detect_cluster_type()
🤖 Prompt for AI Agents
In packages/jumpstarter-kubernetes/jumpstarter_kubernetes/cluster/operations.py
around lines 32 to 44, the code raises ClusterTypeValidationError with the
message string as the first argument but the exception expects cluster_type: str
as its first parameter; change the raise to pass a valid cluster_type value (for
the conflicting case use something like "both") as the first argument and move
the human-readable message to the second argument (e.g.
ClusterTypeValidationError("both", 'You can only select one local cluster type
"kind" or "minikube"')) so the exception constructor receives the expected
parameters.

import click


class ClickCallback:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is nice! :D

auto-merge was automatically disabled October 9, 2025 15:32

Pull Request is not mergeable

@kirkbrauer kirkbrauer merged commit 17cf5d2 into main Oct 9, 2025
18 checks passed
@kirkbrauer kirkbrauer deleted the improve-cluster-setup branch October 9, 2025 19:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants