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
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
26 changes: 18 additions & 8 deletions packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
V1Alpha1ExporterStatus,
V1Alpha1Lease,
V1Alpha1LeaseList,
V1Alpha1LeaseSelector,
V1Alpha1LeaseSpec,
V1Alpha1LeaseStatus,
)
Expand Down Expand Up @@ -837,7 +838,7 @@ async def test_get_exporters_devices(_load_kube_config_mock, list_exporters_mock
spec=V1Alpha1LeaseSpec(
client=V1ObjectReference(name="test_client"),
duration="5m",
selector={"hardware": "rpi4"},
selector=V1Alpha1LeaseSelector(match_labels={"hardware": "rpi4"}),
),
)

Expand Down Expand Up @@ -868,7 +869,7 @@ async def test_get_exporters_devices(_load_kube_config_mock, list_exporters_mock
spec=V1Alpha1LeaseSpec(
client=V1ObjectReference(name="test_client"),
duration="1h",
selector={},
selector=V1Alpha1LeaseSelector(match_labels={}),
),
)

Expand All @@ -885,7 +886,9 @@ async def test_get_exporters_devices(_load_kube_config_mock, list_exporters_mock
"name": "test_client"
},
"duration": "1h",
"selector": {}
"selector": {
"matchLabels": {}
}
},
"status": {
"beginTime": "2024-01-01T21:00:00Z",
Expand Down Expand Up @@ -918,7 +921,8 @@ async def test_get_exporters_devices(_load_kube_config_mock, list_exporters_mock
client:
name: test_client
duration: 1h
selector: {}
selector:
matchLabels: {}
status:
beginTime: '2024-01-01T21:00:00Z'
conditions:
Expand Down Expand Up @@ -1022,7 +1026,9 @@ async def test_get_lease(_load_kube_config_mock, get_lease_mock: AsyncMock):
},
"duration": "5m",
"selector": {
"hardware": "rpi4"
"matchLabels": {
"hardware": "rpi4"
}
}
},
"status": {
Expand Down Expand Up @@ -1057,7 +1063,9 @@ async def test_get_lease(_load_kube_config_mock, get_lease_mock: AsyncMock):
"name": "test_client"
},
"duration": "1h",
"selector": {}
"selector": {
"matchLabels": {}
}
},
"status": {
"beginTime": "2024-01-01T21:00:00Z",
Expand Down Expand Up @@ -1096,7 +1104,8 @@ async def test_get_lease(_load_kube_config_mock, get_lease_mock: AsyncMock):
name: test_client
duration: 5m
selector:
hardware: rpi4
matchLabels:
hardware: rpi4
status:
beginTime: '2024-01-01T21:00:00Z'
conditions:
Expand All @@ -1120,7 +1129,8 @@ async def test_get_lease(_load_kube_config_mock, get_lease_mock: AsyncMock):
client:
name: test_client
duration: 1h
selector: {}
selector:
matchLabels: {}
status:
beginTime: '2024-01-01T21:00:00Z'
conditions:
Expand Down
6 changes: 4 additions & 2 deletions packages/jumpstarter-cli-admin/jumpstarter_cli_admin/print.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,14 @@ def get_reason(lease: V1Alpha1Lease):
return "Expired"
else:
return "Complete"
else:
return reason


def make_lease_row(lease: V1Alpha1Lease):
selectors = []
for label in lease.spec.selector:
selectors.append(f"{label}:{str(lease.spec.selector[label])}")
for label in lease.spec.selector.match_labels:
selectors.append(f"{label}:{str(lease.spec.selector.match_labels[label])}")
return {
"NAME": lease.metadata.name,
"CLIENT": lease.spec.client.name if lease.spec.client is not None else "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from jumpstarter_cli_common import AliasedGroup, opt_log_level, version
from jumpstarter_cli_common.exceptions import handle_exceptions

from .client_exporter import list_client_exporters
from .client_lease import client_lease
from .client_login import client_login
from .client_shell import client_shell
from .config import config
from .create import create
from .delete import delete
from .get import get
from .update import update
from jumpstarter.common.utils import env


Expand Down Expand Up @@ -38,9 +40,11 @@ def cli():
sys.exit(1)


client.add_command(list_client_exporters)
client.add_command(config)
client.add_command(client_lease)
client.add_command(create)
client.add_command(get)
client.add_command(delete)
client.add_command(update)
client.add_command(client_login)
client.add_command(client_shell)
client.add_command(version)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,24 @@
import asyncclick as click
from jumpstarter_cli_common.exceptions import handle_exceptions

from .common import opt_config, opt_selector_simple, selector_to_labels
from jumpstarter.common import MetadataFilter
from jumpstarter.common.utils import launch_shell
from jumpstarter.config import (
ClientConfigV1Alpha1,
UserConfigV1Alpha1,
)


@click.command("shell", short_help="Spawns a shell connecting to a leased remote exporter")
@click.argument("name", type=str, default="")
@click.option("-l", "--label", "labels", type=(str, str), multiple=True)
@click.option("-n", "--lease", "lease_name", type=str)
@opt_config
@opt_selector_simple
@handle_exceptions
def client_shell(name: str, labels, lease_name):
def client_shell(config, selector: str, lease_name):
"""Spawns a shell connecting to a leased remote exporter"""
if name:
config = ClientConfigV1Alpha1.load(name)
else:
config = UserConfigV1Alpha1.load_or_create().config.current_client
if not config:
raise click.BadParameter(
"no client specified, and no default client set: specify a client name, or use jmp client config use",
param_hint="name",
)

exit_code = 0
with config.lease(metadata_filter=MetadataFilter(labels=dict(labels)), lease_name=lease_name) as lease:

with config.lease(
metadata_filter=MetadataFilter(labels=selector_to_labels(selector)), lease_name=lease_name
) as lease:
with lease.serve_unix() as path:
with lease.monitor():
exit_code = launch_shell(path, "remote", config.drivers.allow, config.drivers.unsafe)
Expand Down
64 changes: 64 additions & 0 deletions packages/jumpstarter-cli-client/jumpstarter_cli_client/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from datetime import timedelta

import asyncclick as click
from pydantic import TypeAdapter

from jumpstarter.config import (
ClientConfigV1Alpha1,
UserConfigV1Alpha1,
)

opt_selector = click.option(
"-l",
"--selector",
help="Selector (label query) to filter on, supports '=', '==', and '!=' (e.g. -l key1=value1,key2=value2)."
" Matching objects must satisfy all of the specified label constraints.",
)

opt_selector_simple = click.option(
"-l",
"--selector",
help="Selector (label query) to filter on, only supports '=', (e.g. -l key1=value1,key2=value2)."
" Matching objects must satisfy all of the specified label constraints.",
required=True,
)


def selector_to_labels(selector: str):
# TODO: support complex selectors (e.g. !=)
return dict([term.split("=") for term in selector.split(",")])


class ClientParamType(click.ParamType):
name = "client"

def convert(self, value, param, ctx):
if isinstance(value, ClientConfigV1Alpha1):
return value

if isinstance(value, bool): # hack to allow loading the default config
config = UserConfigV1Alpha1.load_or_create().config.current_client
if config is None:
self.fail("no client config specified, and no default client config set", param, ctx)
return config
else:
return ClientConfigV1Alpha1.load(value)


class DurationParamType(click.ParamType):
name = "duration"

def convert(self, value, param, ctx):
if isinstance(value, timedelta):
return value

try:
return TypeAdapter(timedelta).validate_python(value)
except ValueError:
self.fail(f"{value!r} is not a valid duration", param, ctx)


DURATION = DurationParamType()
CLIENT = ClientParamType()

opt_config = click.option("--client", "config", type=CLIENT, default=False, help="Name of client config")
Loading