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

Commit d0d6dbf

Browse files
committed
Implement kubectl style get command
1 parent 07dba82 commit d0d6dbf

File tree

4 files changed

+122
-5
lines changed

4 files changed

+122
-5
lines changed

packages/jumpstarter-cli-client/jumpstarter_cli_client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .client_lease import client_lease
1010
from .client_login import client_login
1111
from .client_shell import client_shell
12+
from .get import get
1213
from jumpstarter.common.utils import env
1314

1415

@@ -27,6 +28,7 @@ def j():
2728
client.cli()(standalone_mode=True)
2829

2930

31+
client.add_command(get)
3032
client.add_command(create_client_config)
3133
client.add_command(delete_client_config)
3234
client.add_command(list_client_configs)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import asyncclick as click
2+
from jumpstarter_cli_common import OutputMode, OutputType, make_table, opt_output_all
3+
4+
from jumpstarter.config import (
5+
ClientConfigV1Alpha1,
6+
UserConfigV1Alpha1,
7+
)
8+
9+
opt_context = click.option("--context")
10+
opt_selector = click.option(
11+
"-l",
12+
"--selector",
13+
help="Selector (label query) to filter on, supports '=', '==', and '!=' (e.g. -l key1=value1,key2=value2)."
14+
" Matching objects must satisfy all of the specified label constraints.",
15+
)
16+
17+
18+
def _load_context(context: str | None) -> ClientConfigV1Alpha1:
19+
if context:
20+
config = ClientConfigV1Alpha1.load(context)
21+
else:
22+
config = UserConfigV1Alpha1.load_or_create().config.current_client
23+
if not config:
24+
raise click.BadOptionUsage(
25+
"--context",
26+
"no client context specified, and no default client context set",
27+
)
28+
return config
29+
30+
31+
@click.group()
32+
def get():
33+
"""
34+
Display one or many resources
35+
"""
36+
37+
38+
@get.command(name="exporters")
39+
@opt_context
40+
@opt_selector
41+
@opt_output_all
42+
def get_exporters(context: str | None, selector: str | None, output: OutputType):
43+
"""
44+
Display one or many exporters
45+
"""
46+
config = _load_context(context)
47+
48+
exporters = config.list_exporters(filter=selector)
49+
50+
match output:
51+
case OutputMode.JSON:
52+
click.echo(exporters.dump_json())
53+
case OutputMode.YAML:
54+
click.echo(exporters.dump_yaml())
55+
case OutputMode.NAME:
56+
for exporter in exporters.exporters:
57+
click.echo(exporter.name)
58+
case _:
59+
columns = ["NAME", "LABELS"]
60+
rows = [
61+
{
62+
"NAME": exporter.name,
63+
"LABELS": ",".join(("{}={}".format(i[0], i[1]) for i in exporter.labels.items())),
64+
}
65+
for exporter in exporters.exporters
66+
]
67+
click.echo(make_table(columns, rows))
68+
69+
70+
@get.command(name="leases")
71+
@opt_context
72+
@opt_selector
73+
@opt_output_all
74+
async def get_leases(context: str | None, selector: str | None, output: OutputType):
75+
"""
76+
Display one or many leases
77+
"""
78+
config = _load_context(context)
79+
80+
leases = config.list_leases(filter=selector)
81+
82+
match output:
83+
case OutputMode.JSON:
84+
click.echo(leases.dump_json())
85+
case OutputMode.YAML:
86+
click.echo(leases.dump_yaml())
87+
case OutputMode.NAME:
88+
for lease in leases.leases:
89+
click.echo(lease.name)
90+
case _:
91+
columns = ["NAME", "CLIENT", "EXPORTER"]
92+
rows = [
93+
{
94+
"NAME": lease.name,
95+
"CLIENT": lease.client,
96+
"EXPORTER": lease.exporter,
97+
}
98+
for lease in leases.leases
99+
]
100+
click.echo(make_table(columns, rows))

packages/jumpstarter/jumpstarter/client/grpc.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
from datetime import timedelta
55

66
import yaml
7-
from google.protobuf import duration_pb2, field_mask_pb2
7+
from google.protobuf import duration_pb2, field_mask_pb2, json_format
88
from grpc.aio import Channel
99
from jumpstarter_protocol import kubernetes_pb2
1010
from jumpstarter_protocol.jumpstarter.client.v1 import client_pb2, client_pb2_grpc
11-
from pydantic import BaseModel, ConfigDict
11+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
1212

1313

1414
def parse_identifier(identifier: str, kind: str) -> (str, str):
@@ -22,6 +22,10 @@ def parse_identifier(identifier: str, kind: str) -> (str, str):
2222
return segments[1], segments[3]
2323

2424

25+
def parse_client_identifier(identifier: str) -> (str, str):
26+
return parse_identifier(identifier, "clients")
27+
28+
2529
def parse_exporter_identifier(identifier: str) -> (str, str):
2630
return parse_identifier(identifier, "exporters")
2731

@@ -44,25 +48,35 @@ def from_protobuf(cls, data: client_pb2.Exporter) -> Exporter:
4448
class Lease(BaseModel):
4549
namespace: str
4650
name: str
51+
client: str
4752
exporter: str
4853
conditions: list[kubernetes_pb2.Condition]
4954

5055
model_config = ConfigDict(arbitrary_types_allowed=True)
5156

57+
@field_serializer("conditions")
58+
def serialize_conditions(self, conditions: list[kubernetes_pb2.Condition], _info):
59+
return [json_format.MessageToDict(condition) for condition in conditions]
60+
5261
@classmethod
5362
def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
5463
namespace, name = parse_lease_identifier(data.name)
64+
65+
_, client = parse_client_identifier(data.client)
66+
_, exporter = parse_exporter_identifier(data.exporter)
67+
5568
return cls(
5669
namespace=namespace,
5770
name=name,
58-
exporter=data.exporter,
71+
client=client,
72+
exporter=exporter,
5973
conditions=data.conditions,
6074
)
6175

6276

6377
class ExporterList(BaseModel):
6478
exporters: list[Exporter]
65-
next_page_token: str | None
79+
next_page_token: str | None = Field(exclude=True)
6680

6781
@classmethod
6882
def from_protobuf(cls, data: client_pb2.ListExportersResponse) -> ExporterList:
@@ -80,7 +94,7 @@ def dump_yaml(self):
8094

8195
class LeaseList(BaseModel):
8296
leases: list[Lease]
83-
next_page_token: str | None
97+
next_page_token: str | None = Field(exclude=True)
8498

8599
@classmethod
86100
def from_protobuf(cls, data: client_pb2.ListLeasesResponse) -> LeaseList:

packages/jumpstarter/jumpstarter/config/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ async def request_lease_async(
127127

128128
lease = Lease(
129129
channel=await self.channel(),
130+
namespace=self.metadata.namespace,
130131
name=None,
131132
metadata_filter=metadata_filter,
132133
portal=portal,

0 commit comments

Comments
 (0)