Skip to content
Open
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
14 changes: 14 additions & 0 deletions e2e/tests.bats
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,20 @@ EOF
done
}

@test "lease listing shows expires at and remaining columns" {
wait_for_exporter

jmp config client use test-client-oidc

jmp create lease --selector example.com/board=oidc --duration 1d

run jmp get leases
assert_success
assert_output --partial "EXPIRES AT"
assert_output --partial "REMAINING"
jmp delete leases --all
}

@test "can transfer lease to another client" {
wait_for_exporter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_get_leases_calls_list_leases(self):

with patch("jumpstarter_cli.get.model_print"):
get_leases.callback.__wrapped__.__wrapped__(
config=config, selector=None, output="text", show_all=False
config=config, selector=None, output="text", show_all=False, all_clients=False
)

config.list_leases.assert_called_once_with(filter=None, only_active=True)
Expand Down
50 changes: 37 additions & 13 deletions python/packages/jumpstarter/jumpstarter/client/grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,27 +203,51 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
def rich_add_columns(cls, table):
table.add_column("NAME", no_wrap=True)
table.add_column("SELECTOR")
table.add_column("BEGIN TIME")
table.add_column("DURATION")
table.add_column("EXPIRES AT")
table.add_column("REMAINING")
table.add_column("CLIENT")
table.add_column("EXPORTER")

def rich_add_rows(self, table):
# Show effective_begin_time if active, otherwise show scheduled begin_time
begin_time = ""
if self.effective_begin_time:
begin_time = self.effective_begin_time.strftime("%Y-%m-%d %H:%M:%S")
elif self.begin_time:
begin_time = self.begin_time.strftime("%Y-%m-%d %H:%M:%S")
def _compute_expires_at(self):
if self.effective_end_time:
return self.effective_end_time
if self.effective_begin_time and self.duration:
return self.effective_begin_time + self.duration
if self.begin_time and self.duration:
return self.begin_time + self.duration
return None

@staticmethod
def _format_remaining(expires_at):
if expires_at is None:
return ""
now = datetime.now(tz=expires_at.tzinfo)
remaining = expires_at - now
if remaining.total_seconds() <= 0:
return "expired"
total_seconds = int(remaining.total_seconds())
days, remainder = divmod(total_seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, _ = divmod(remainder, 60)
parts = []
if days:
parts.append(f"{days}d")
if hours:
parts.append(f"{hours}h")
if minutes or not parts:
parts.append(f"{minutes}m")
return " ".join(parts)

# Show actual duration for ended leases, requested duration otherwise
duration = str(self.effective_duration if self.effective_end_time else self.duration or "")
def rich_add_rows(self, table):
expires_at = self._compute_expires_at()
expires_at_str = expires_at.strftime("%Y-%m-%d %H:%M:%S") if expires_at else ""
remaining_str = self._format_remaining(expires_at)

table.add_row(
self.name,
self.selector,
begin_time,
duration,
expires_at_str,
remaining_str,
self.client,
self.exporter,
)
Expand Down
98 changes: 98 additions & 0 deletions python/packages/jumpstarter/jumpstarter/client/grpc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,101 @@ def test_exporter_scheduled_lease_expected_release(self):
assert "my-client" in output
assert "Scheduled" in output
assert "2023-01-01 11:00:00" in output # begin_time (10:00) + duration (1h)


class TestLeaseRichDisplay:
def create_lease(
self,
name="test-lease",
selector="env=test",
duration=timedelta(hours=1),
effective_duration=None,
begin_time=None,
effective_begin_time=None,
effective_end_time=None,
client="test-client",
exporter="test-exporter",
):
return Lease(
namespace="default",
name=name,
selector=selector,
duration=duration,
effective_duration=effective_duration,
begin_time=begin_time,
effective_begin_time=effective_begin_time,
effective_end_time=effective_end_time,
client=client,
exporter=exporter,
conditions=[],
)

def test_rich_add_columns_has_expires_at_and_remaining(self):
table = Table()
Lease.rich_add_columns(table)
columns = [col.header for col in table.columns]
assert columns == ["NAME", "SELECTOR", "EXPIRES AT", "REMAINING", "CLIENT", "EXPORTER"]

def test_rich_add_columns_excludes_begin_time_and_duration(self):
table = Table()
Lease.rich_add_columns(table)
columns = [col.header for col in table.columns]
assert "BEGIN TIME" not in columns
assert "DURATION" not in columns

def test_compute_expires_at_from_effective_end_time(self):
lease = self.create_lease(
effective_end_time=datetime(2023, 1, 1, 11, 0, 0),
)
assert lease._compute_expires_at() == datetime(2023, 1, 1, 11, 0, 0)

def test_compute_expires_at_from_effective_begin_and_duration(self):
lease = self.create_lease(
effective_begin_time=datetime(2023, 6, 15, 14, 30, 0),
duration=timedelta(hours=2),
)
assert lease._compute_expires_at() == datetime(2023, 6, 15, 16, 30, 0)

def test_compute_expires_at_from_begin_time_and_duration(self):
lease = self.create_lease(
begin_time=datetime(2023, 3, 10, 8, 0, 0),
duration=timedelta(minutes=30),
)
assert lease._compute_expires_at() == datetime(2023, 3, 10, 8, 30, 0)

def test_compute_expires_at_none_when_no_begin_time(self):
lease = self.create_lease()
assert lease._compute_expires_at() is None

def test_format_remaining_expired(self):
past = datetime(2020, 1, 1, 0, 0, 0)
assert Lease._format_remaining(past) == "expired"

def test_format_remaining_none(self):
assert Lease._format_remaining(None) == ""

def test_rich_add_rows_shows_expires_at(self):
lease = self.create_lease(
effective_begin_time=datetime(2023, 1, 1, 10, 0, 0),
effective_end_time=datetime(2023, 1, 1, 11, 0, 0),
)
table = Table()
Lease.rich_add_columns(table)
lease.rich_add_rows(table)

console = Console(file=StringIO(), width=200)
console.print(table)
output = console.file.getvalue()
assert "2023-01-01 11:00:00" in output

def test_rich_add_rows_empty_when_no_timing_data(self):
lease = self.create_lease()
table = Table()
Lease.rich_add_columns(table)
lease.rich_add_rows(table)

console = Console(file=StringIO(), width=200)
console.print(table)
output = console.file.getvalue()
assert "test-lease" in output
assert "test-client" in output
Loading