Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9accb01
Add pre/post lease hooks
kirkbrauer Sep 4, 2025
ed5f23c
Merge branch 'main' into add-hooks
kirkbrauer Sep 23, 2025
64962be
Add enums and exporter status reporting
kirkbrauer Sep 29, 2025
6e8b44f
Improve logging infrastructure
kirkbrauer Sep 29, 2025
c2aae20
Merge branch 'main' into add-hooks
kirkbrauer Oct 10, 2025
8581946
Merge branch 'main' into add-hooks
kirkbrauer Oct 22, 2025
0d7607a
Fix circular dependency in logging.py
kirkbrauer Oct 31, 2025
1ede9a5
Merge branch 'main' into add-hooks
kirkbrauer Oct 31, 2025
b5de172
Update hook behavior to match spec
kirkbrauer Oct 31, 2025
7581373
Improve hook error handling
kirkbrauer Nov 3, 2025
c85e583
Merge branch 'main' into add-hooks
kirkbrauer Nov 13, 2025
aa951a3
Add strongly-typed Protobuf and gRPC codegen and refactor exporter fo…
kirkbrauer Nov 24, 2025
fac4810
Merge branch 'main' into add-hooks
kirkbrauer Nov 24, 2025
c7ca1e5
Improve messaging and typing
kirkbrauer Nov 24, 2025
4e4a42b
Finish refactoring the Exporter class and improve hooks handling
kirkbrauer Nov 25, 2025
aa13aea
Fix broken tests due to field name change and status not being correct
kirkbrauer Nov 25, 2025
d140613
Fix typing issues
kirkbrauer Nov 25, 2025
3b71d7a
Fix controller registration issue
kirkbrauer Nov 26, 2025
cab55c9
Add status field to jmp admin get exporter
kirkbrauer Nov 26, 2025
cc44664
Fix lease status race condition causing E2E tests to fail
kirkbrauer Nov 26, 2025
0cb3cab
Fix additional status update race conditions breaking E2E
kirkbrauer Nov 26, 2025
a129ff7
Fix unit test failures
kirkbrauer Nov 26, 2025
6aa6a1f
Fix broken unit tests
kirkbrauer Nov 26, 2025
e0d3571
Merge branch 'main' into add-hooks
mangelajo Nov 27, 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: 4 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ plugins:
out: ./packages/jumpstarter-protocol/jumpstarter_protocol
- remote: buf.build/grpc/python
out: ./packages/jumpstarter-protocol/jumpstarter_protocol
- remote: buf.build/community/nipunn1313-mypy:v3.7.0
out: ./packages/jumpstarter-protocol/jumpstarter_protocol
- remote: buf.build/community/nipunn1313-mypy-grpc:v3.7.0
out: ./packages/jumpstarter-protocol/jumpstarter_protocol
inputs:
- git_repo: https://github.com/jumpstarter-dev/jumpstarter-protocol.git
branch: main
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ def test_create_client(
"name": "{name}-credential"
}},
"devices": [],
"endpoint": "{endpoint}"
"endpoint": "{endpoint}",
"exporterStatus": null,
"statusMessage": null
}}
}}
""".format(name=EXPORTER_NAME, endpoint=EXPORTER_ENDPOINT)
Expand All @@ -250,6 +252,8 @@ def test_create_client(
name: {name}-credential
devices: []
endpoint: {endpoint}
exporterStatus: null
statusMessage: null
""".format(name=EXPORTER_NAME, endpoint=EXPORTER_ENDPOINT)

Expand Down
50 changes: 43 additions & 7 deletions packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,10 @@ def test_get_clients(_load_kube_config_mock, list_clients_mock: AsyncMock):
kind="Exporter",
metadata=V1ObjectMeta(name="test", namespace="testing", creation_timestamp="2024-01-01T21:00:00Z"),
status=V1Alpha1ExporterStatus(
endpoint="grpc://example.com:443", credential=V1ObjectReference(name="test-credential"), devices=[]
endpoint="grpc://example.com:443",
credential=V1ObjectReference(name="test-credential"),
devices=[],
exporter_status="Available",
),
)

Expand All @@ -318,7 +321,9 @@ def test_get_clients(_load_kube_config_mock, list_clients_mock: AsyncMock):
"name": "test-credential"
},
"devices": [],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
}
"""
Expand All @@ -334,6 +339,8 @@ def test_get_clients(_load_kube_config_mock, list_clients_mock: AsyncMock):
name: test-credential
devices: []
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null

"""

Expand All @@ -348,6 +355,7 @@ def test_get_exporter(_load_kube_config_mock, get_exporter_mock: AsyncMock):
result = runner.invoke(get, ["exporter", "test"])
assert result.exit_code == 0
assert "test" in result.output
assert "Available" in result.output
assert "grpc://example.com:443" in result.output
get_exporter_mock.reset_mock()

Expand Down Expand Up @@ -396,6 +404,7 @@ def test_get_exporter(_load_kube_config_mock, get_exporter_mock: AsyncMock):
V1Alpha1ExporterDevice(labels={"hardware": "rpi4"}, uuid="82a8ac0d-d7ff-4009-8948-18a3c5c607b1"),
V1Alpha1ExporterDevice(labels={"hardware": "rpi4"}, uuid="f7cd30ac-64a3-42c6-ba31-b25f033b97c1"),
],
exporter_status="Available",
),
)

Expand Down Expand Up @@ -425,7 +434,9 @@ def test_get_exporter(_load_kube_config_mock, get_exporter_mock: AsyncMock):
"uuid": "f7cd30ac-64a3-42c6-ba31-b25f033b97c1"
}
],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
}
"""
Expand All @@ -447,6 +458,8 @@ def test_get_exporter(_load_kube_config_mock, get_exporter_mock: AsyncMock):
hardware: rpi4
uuid: f7cd30ac-64a3-42c6-ba31-b25f033b97c1
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null

"""

Expand All @@ -460,6 +473,7 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
result = runner.invoke(get, ["exporter", "test", "--devices"])
assert result.exit_code == 0
assert "test" in result.output
assert "Available" in result.output
assert "grpc://example.com:443" in result.output
assert "hardware:rpi4" in result.output
assert "82a8ac0d-d7ff-4009-8948-18a3c5c607b1" in result.output
Expand Down Expand Up @@ -510,6 +524,7 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
endpoint="grpc://example.com:443",
credential=V1ObjectReference(name="test-credential"),
devices=[],
exporter_status="Available",
),
),
V1Alpha1Exporter(
Expand All @@ -520,6 +535,7 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
endpoint="grpc://example.com:443",
credential=V1ObjectReference(name="another-credential"),
devices=[],
exporter_status="Available",
),
),
]
Expand All @@ -541,7 +557,9 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
"name": "test-credential"
},
"devices": [],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
},
{
Expand All @@ -557,7 +575,9 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
"name": "another-credential"
},
"devices": [],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
}
],
Expand All @@ -578,6 +598,8 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
name: test-credential
devices: []
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null
- apiVersion: jumpstarter.dev/v1alpha1
kind: Exporter
metadata:
Expand All @@ -589,6 +611,8 @@ def test_get_exporter_devices(_load_kube_config_mock, get_exporter_mock: AsyncMo
name: another-credential
devices: []
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null
kind: ExporterList

"""
Expand All @@ -609,6 +633,7 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
assert result.exit_code == 0
assert "test" in result.output
assert "another" in result.output
assert "Available" in result.output
list_exporters_mock.reset_mock()

# List exporters JSON output
Expand Down Expand Up @@ -655,6 +680,7 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
devices=[
V1Alpha1ExporterDevice(labels={"hardware": "rpi4"}, uuid="82a8ac0d-d7ff-4009-8948-18a3c5c607b1")
],
exporter_status="Available",
),
),
V1Alpha1Exporter(
Expand All @@ -667,6 +693,7 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
devices=[
V1Alpha1ExporterDevice(labels={"hardware": "rpi4"}, uuid="f7cd30ac-64a3-42c6-ba31-b25f033b97c1"),
],
exporter_status="Available",
),
),
]
Expand Down Expand Up @@ -695,7 +722,9 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
"uuid": "82a8ac0d-d7ff-4009-8948-18a3c5c607b1"
}
],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
},
{
Expand All @@ -718,7 +747,9 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
"uuid": "f7cd30ac-64a3-42c6-ba31-b25f033b97c1"
}
],
"endpoint": "grpc://example.com:443"
"endpoint": "grpc://example.com:443",
"exporterStatus": "Available",
"statusMessage": null
}
}
],
Expand All @@ -742,6 +773,8 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
hardware: rpi4
uuid: 82a8ac0d-d7ff-4009-8948-18a3c5c607b1
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null
- apiVersion: jumpstarter.dev/v1alpha1
kind: Exporter
metadata:
Expand All @@ -756,6 +789,8 @@ def test_get_exporters(_load_kube_config_mock, list_exporters_mock: AsyncMock):
hardware: rpi4
uuid: f7cd30ac-64a3-42c6-ba31-b25f033b97c1
endpoint: grpc://example.com:443
exporterStatus: Available
statusMessage: null
kind: ExporterList

"""
Expand All @@ -774,6 +809,7 @@ def test_get_exporters_devices(_load_kube_config_mock, list_exporters_mock: Asyn
assert result.exit_code == 0
assert "test" in result.output
assert "another" in result.output
assert "Available" in result.output
assert "hardware:rpi4" in result.output
assert "82a8ac0d-d7ff-4009-8948-18a3c5c607b1" in result.output
assert "f7cd30ac-64a3-42c6-ba31-b25f033b97c1" in result.output
Expand Down
9 changes: 6 additions & 3 deletions packages/jumpstarter-cli/jumpstarter_cli/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def get():
@opt_output_all
@opt_comma_separated(
"with",
{"leases", "online"},
help_text="Include fields: leases, online (comma-separated or repeated)"
{"leases", "online", "status"},
help_text="Include fields: leases, online, status (comma-separated or repeated)",
)
@handle_exceptions_with_reauthentication(relogin_client)
def get_exporters(config, selector: str | None, output: OutputType, with_options: list[str]):
Expand All @@ -32,7 +32,10 @@ def get_exporters(config, selector: str | None, output: OutputType, with_options

include_leases = "leases" in with_options
include_online = "online" in with_options
exporters = config.list_exporters(filter=selector, include_leases=include_leases, include_online=include_online)
include_status = "status" in with_options
exporters = config.list_exporters(
filter=selector, include_leases=include_leases, include_online=include_online, include_status=include_status
Copy link
Member

Choose a reason for hiding this comment

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

It is not your patches fault, but I hate this API, we need to improve it hehehe :D

)

model_print(exporters, output)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class V1Alpha1ExporterStatus(JsonBaseModel):
credential: SerializeV1ObjectReference
devices: list[V1Alpha1ExporterDevice]
endpoint: str
exporter_status: str | None = Field(alias="exporterStatus", default=None)
status_message: str | None = Field(alias="statusMessage", default=None)


class V1Alpha1Exporter(JsonBaseModel):
Expand Down Expand Up @@ -55,24 +57,29 @@ def from_dict(dict: dict):
devices=[V1Alpha1ExporterDevice(labels=d["labels"], uuid=d["uuid"]) for d in dict["status"]["devices"]]
if "devices" in dict["status"]
else [],
exporter_status=dict["status"].get("exporterStatus"),
status_message=dict["status"].get("statusMessage"),
),
)

@classmethod
def rich_add_columns(cls, table, devices: bool = False):
if devices:
table.add_column("NAME", no_wrap=True)
table.add_column("STATUS")
table.add_column("ENDPOINT")
table.add_column("AGE")
table.add_column("LABELS")
table.add_column("UUID")
else:
table.add_column("NAME", no_wrap=True)
table.add_column("STATUS")
table.add_column("ENDPOINT")
table.add_column("DEVICES")
table.add_column("AGE")

def rich_add_rows(self, table, devices: bool = False):
status = self.status.exporter_status if self.status else "Unknown"
if devices:
if self.status is not None:
for d in self.status.devices:
Expand All @@ -82,6 +89,7 @@ def rich_add_rows(self, table, devices: bool = False):
labels.append(f"{label}:{str(d.labels[label])}")
table.add_row(
self.metadata.name,
status or "Unknown",
self.status.endpoint,
time_since(self.metadata.creation_timestamp),
",".join(labels),
Expand All @@ -91,6 +99,7 @@ def rich_add_rows(self, table, devices: bool = False):
else:
table.add_row(
self.metadata.name,
status or "Unknown",
self.status.endpoint,
str(len(self.status.devices) if self.status and self.status.devices else 0),
time_since(self.metadata.creation_timestamp),
Expand Down
Loading