Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d37ac6c
Add new admin create cluster command to create a cluster and enable c…
kirkbrauer Sep 12, 2025
607ea70
Add support for more Kind and Minikube backends
kirkbrauer Sep 12, 2025
86606dc
Fix cluster creation
kirkbrauer Sep 12, 2025
78c3a41
Add ability to get clusters
kirkbrauer Sep 12, 2025
41b32ad
Improve cluster list output
kirkbrauer Sep 12, 2025
06ae927
Add connectivity checks to cluster list
kirkbrauer Sep 12, 2025
45d3a82
Tweak connectivity checks
kirkbrauer Sep 15, 2025
d643882
Remove connectivity checks and allow single cluster get
kirkbrauer Sep 15, 2025
d43cbf2
Remove cluster management from jmp admin install command
kirkbrauer Sep 17, 2025
0edd6eb
Add jumpstarter-cli-admin tests
kirkbrauer Sep 27, 2025
9be1415
Migrate tests to cluster module and fix remaining tests
kirkbrauer Sep 27, 2025
e0ec165
Fix broken docs build
kirkbrauer Sep 27, 2025
d446b1d
Merge branch 'main' into improve-cluster-setup
kirkbrauer Sep 27, 2025
23bb720
Fix cluster creation and Jumpstarter version
kirkbrauer Sep 27, 2025
5fe36c3
Fix cluster detection logic
kirkbrauer Sep 27, 2025
f56ff21
Fix lint errors
kirkbrauer Sep 27, 2025
76d8dae
Update docs
kirkbrauer Sep 27, 2025
6e42c88
Tweak docs
kirkbrauer Sep 27, 2025
4855326
Fix issues and improve tests
kirkbrauer Oct 4, 2025
c8bfc6b
Merge branch 'main' into improve-cluster-setup
kirkbrauer Oct 4, 2025
63e5ba0
Increase test coverage
kirkbrauer Oct 4, 2025
77749b4
Fix click exception handling for get_cluster
kirkbrauer Oct 4, 2025
9a2ef10
Fix remaining issues and nitpicks
kirkbrauer Oct 4, 2025
e119b40
Fix line length
kirkbrauer Oct 4, 2025
04b8cf5
Add support for passing values files for the Helm install (supports E…
kirkbrauer Oct 4, 2025
332d9a4
Fix lint errors
kirkbrauer Oct 4, 2025
fcb60ab
Fix remaining issues
kirkbrauer Oct 4, 2025
ac6937a
Fix double nodes issue in YAML
kirkbrauer Oct 4, 2025
d0d86f0
Merge branch 'main' into improve-cluster-setup
mangelajo Oct 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import sys

from jumpstarter_cli_admin.controller import get_latest_compatible_controller_version
from jumpstarter_kubernetes.controller import get_latest_compatible_controller_version

os.environ["TERM"] = "dumb"

Expand All @@ -32,7 +32,7 @@
"sphinx_click",
"sphinx_substitution_extensions",
"sphinx_copybutton",
"sphinx_inline_tabs"
"sphinx_inline_tabs",
]

templates_path = ["_templates"]
Expand Down
78 changes: 58 additions & 20 deletions docs/source/getting-started/installation/service/service-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ Before installing locally, ensure you have:

## Install with Jumpstarter CLI

The Jumpstarter CLI provides the `jmp admin install` command to automatically
run Helm with the correct arguments, simplifying installation in your Kubernetes
cluster. This is the recommended approach for getting started quickly.
The Jumpstarter CLI provides convenient commands for local demo/test cluster management and Jumpstarter installation:

- `jmp admin create cluster` - Creates a local cluster and installs Jumpstarter (recommended for getting started quickly)
- `jmp admin delete cluster` - Deletes a local cluster completely
- `jmp admin get clusters` - Get local clusters from a Kubeconfig
- `jmp admin install` - Installs Jumpstarter on an existing cluster
- `jmp admin uninstall` - Removes Jumpstarter from a cluster (but keeps the cluster)

```{warning}
Sometimes the automatic IP address detection for will not work correctly, to check if Jumpstarter can determine your IP address, run `jmp admin ip`. If the IP address cannot be determined, use the `--ip` argument to manually set your IP address.
Expand All @@ -27,44 +31,65 @@ If you want to test Jumpstarter locally with more control over the setup, you ca

[**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/) (Kubernetes in Docker) is a tool for running local Kubernetes clusters using Docker or Podman containerized "nodes". It's lightweight and fast to start, making it excellent for CI/CD pipelines and quick local testing.

[**minikube**](https://minikube.sigs.k8s.io/docs/start/) runs local Kubernetes clusters using VMs or container "nodes". It works across several platforms and supports different hypervisors, making it ideal for local development and testing. It's particularly useful in environments requiring untrusted certificates.
[**minikube**](https://minikube.sigs.k8s.io/docs/start/) runs local Kubernetes clusters using VMs or container "nodes". It works across several platforms and supports different hypervisors, making it ideal for local development and testing. Minikube works better if you don't have a local Docker/Podman installation.

The admin CLI can automatically create a local cluster and install Jumpstarter with a single command:

By default, Jumpstarter will try to detect which local cluster tools are installed:

```{tip}
Consider minikube for environments requiring [untrusted certificates](https://minikube.sigs.k8s.io/docs/handbook/untrusted_certs/).
By default, Jumpstarter will use `kind` if available, use the `--minikube` argument to force Jumpstarter to use minikube instead.
```

The admin CLI can automatically create a local cluster and install Jumpstarter with a single command:
```{code-block} console
$ jmp admin create cluster
```

However, you can also explicitly specify a local cluster tool:

````{tab} kind
```{code-block} console
$ jmp admin install --kind --create-cluster
$ jmp admin create cluster --kind
```

Additional options for cluster creation:

- Custom cluster name: Specify as the first argument (default: `jumpstarter-lab`)
- `--kind <PATH>`: Path to the kind binary to use for cluster management
- `--helm <PATH>`: Path to the Helm binary to install the Jumpstarter service with
- `--force-recreate`: Force recreate the cluster if it already exists (destroys all data)
- `--kind-extra-args`: Pass additional arguments to kind cluster creation
- `--skip-install`: Create the cluster without installing Jumpstarter
- `--extra-certs <PATH>`: Path to custom CA certificate bundle file to inject into the cluster
````

````{tab} minikube
```{code-block} console
$ jmp admin install --minikube --create-cluster
$ jmp admin create cluster --minikube
```
````

Additional options for cluster creation:

- `--cluster-name`: Specify a custom cluster name (default: `jumpstarter-lab`)
- `--force-recreate-cluster`: Force recreate the cluster if it already exists (destroys all data)
- `--kind-extra-args`: Pass additional arguments to kind cluster creation
- Custom cluster name: Specify as the first argument (default: `jumpstarter-lab`)
- `--minikube <PATH>`: Path to the minikube binary to use for cluster management
- `--helm <PATH>`: Path to the Helm binary to install the Jumpstarter service with
- `--force-recreate`: Force recreate the cluster if it already exists (destroys all data)
- `--minikube-extra-args`: Pass additional arguments to minikube cluster creation
- `--skip-install`: Create the cluster without installing Jumpstarter
- `--extra-certs <PATH>`: Path to custom CA certificate bundle file to inject into the cluster
````

To set a custom cluster name:

````{tab} kind
```{code-block} console
$ jmp admin install --kind --create-cluster --cluster-name my-jumpstarter-cluster
$ jmp admin create cluster my-jumpstarter-cluster --kind
```
````

````{tab} minikube
```{code-block} console
$ jmp admin install --minikube --create-cluster --cluster-name my-jumpstarter-cluster
$ jmp admin create cluster my-jumpstarter-cluster --minikube
```
````

Expand All @@ -90,28 +115,41 @@ $ jmp admin install --minikube

### Uninstall Jumpstarter

Uninstall Jumpstarter with the CLI:
Uninstall Jumpstarter from the cluster with the CLI:

```{code-block} console
$ jmp admin uninstall
```

To delete the local cluster when uninstalling, use the `--delete-cluster` flag:
To delete the local cluster completely, use the cluster delete command:

````{tab} kind
```{code-block} console
$ jmp admin delete cluster --kind
```
````

````{tab} minikube
```{code-block} console
$ jmp admin delete cluster --minikube
```
````

To delete a cluster with a custom name:

````{tab} kind
```{code-block} console
$ jmp admin uninstall --kind --delete-cluster
$ jmp admin delete cluster my-jumpstarter-cluster --kind
```
````

````{tab} minikube
```{code-block} console
$ jmp admin uninstall --minikube --delete-cluster
$ jmp admin delete cluster my-jumpstarter-cluster --minikube
```
````

For complete documentation of the `jmp admin install` command and all available
options, see the [MAN pages](../../../reference/man-pages/jmp.md).
For complete documentation of the `jmp admin create cluster`, `jmp admin delete cluster`, `jmp admin get clusters`, and `jmp admin install` commands and all available options, see the [MAN pages](../../../reference/man-pages/jmp.md).

## Manual Local Cluster Install

Expand Down
55 changes: 0 additions & 55 deletions packages/jumpstarter-cli-admin/jumpstarter_cli_admin/controller.py

This file was deleted.

147 changes: 146 additions & 1 deletion packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import click
from jumpstarter_cli_common.alias import AliasedGroup
from jumpstarter_cli_common.blocking import blocking
from jumpstarter_cli_common.callbacks import ClickCallback
from jumpstarter_cli_common.opt import (
OutputType,
confirm_insecure_tls,
Expand All @@ -15,7 +16,13 @@
opt_output_all,
)
from jumpstarter_cli_common.print import model_print
from jumpstarter_kubernetes import ClientsV1Alpha1Api, ExportersV1Alpha1Api
from jumpstarter_kubernetes import (
ClientsV1Alpha1Api,
ExportersV1Alpha1Api,
create_cluster_and_install,
validate_cluster_type_selection,
)
from jumpstarter_kubernetes.exceptions import JumpstarterKubernetesError
from kubernetes_asyncio.client.exceptions import ApiException
from kubernetes_asyncio.config.config_exception import ConfigException

Expand Down Expand Up @@ -178,3 +185,141 @@ async def create_exporter(
handle_k8s_api_exception(e)
except ConfigException as e:
handle_k8s_config_exception(e)


@create.command("cluster")
@click.argument("name", type=str, required=False, default="jumpstarter-lab")
@click.option("--kind", is_flag=False, 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",
)
@click.option(
"--force-recreate",
is_flag=True,
help="Force recreate the cluster if it already exists (WARNING: This will destroy all data in the cluster)",
)
@click.option("--kind-extra-args", type=str, help="Extra arguments for the Kind cluster creation", default="")
@click.option("--minikube-extra-args", type=str, help="Extra arguments for the Minikube cluster creation", default="")
@click.option(
"--extra-certs",
type=click.Path(exists=True, readable=True, dir_okay=False, resolve_path=True),
help="Path to custom CA certificate bundle file to inject into the cluster",
)
@click.option(
"--skip-install",
is_flag=True,
help="Skip installing Jumpstarter after creating the cluster",
)
@click.option("--helm", type=str, help="Path or name of a helm executable", default="helm")
@click.option(
"--chart",
type=str,
help="The URL of a Jumpstarter helm chart to install",
default="oci://quay.io/jumpstarter-dev/helm/jumpstarter",
)
@click.option("--chart-name", type=str, help="The name of the chart installation", default="jumpstarter")
@click.option(
"-n", "--namespace", type=str, help="Namespace to install Jumpstarter components in", default="jumpstarter-lab"
)
@click.option("-i", "--ip", type=str, help="IP address of your host machine", default=None)
@click.option("-b", "--basedomain", type=str, help="Base domain of the Jumpstarter service", default=None)
@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("-v", "--version", help="The version of the service to install", default=None)
@click.option(
"-f",
"--values-file",
"values_files",
type=click.Path(exists=True, readable=True, dir_okay=False, resolve_path=True),
multiple=True,
help="Path to custom helm values file (can be specified multiple times)",
)
@opt_kubeconfig
@opt_context
@opt_nointeractive
@opt_output_all
@blocking
async def create_cluster(
name: str,
kind: Optional[str],
minikube: Optional[str],
force_recreate: bool,
kind_extra_args: str,
minikube_extra_args: str,
extra_certs: Optional[str],
skip_install: bool,
helm: str,
chart: str,
chart_name: str,
namespace: str,
ip: Optional[str],
basedomain: Optional[str],
grpc_endpoint: Optional[str],
router_endpoint: Optional[str],
version: Optional[str],
values_files: tuple[str, ...],
kubeconfig: Optional[str],
context: Optional[str],
nointeractive: bool,
output: OutputType,
):
"""Create a Kubernetes cluster for running Jumpstarter"""
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
Comment on lines +271 to +319
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


if output is None:
if skip_install:
click.echo(f'Cluster "{name}" is ready for Jumpstarter installation.')
else:
click.echo(f'Cluster "{name}" created and Jumpstarter installed successfully!')
Loading
Loading