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

Commit de8861b

Browse files
committed
cli: handle token expiration more gracefully
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
1 parent 3bda0e4 commit de8861b

File tree

1 file changed

+60
-2
lines changed
  • packages/jumpstarter-cli/jumpstarter_cli

1 file changed

+60
-2
lines changed

packages/jumpstarter-cli/jumpstarter_cli/shell.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from anyio import create_task_group, get_cancelled_exc_class
77
from jumpstarter_cli_common.config import opt_config
88
from jumpstarter_cli_common.exceptions import handle_exceptions_with_reauthentication
9+
from jumpstarter_cli_common.oidc import get_token_remaining_seconds
910
from jumpstarter_cli_common.signal import signal_handler
1011

1112
from .common import opt_acquisition_timeout, opt_duration_partial, opt_selector
@@ -15,12 +16,58 @@
1516
from jumpstarter.config.exporter import ExporterConfigV1Alpha1
1617

1718

19+
def _warn_about_expired_token(lease_name: str, selector: str):
20+
"""Warn user that lease won't be cleaned up due to expired token."""
21+
click.echo(click.style("\nToken expired - lease cleanup will fail.", fg="yellow", bold=True))
22+
click.echo(click.style(f"Lease '{lease_name}' will remain active.", fg="yellow"))
23+
click.echo(click.style(f"To reconnect: JMP_LEASE={lease_name} jmp shell -s {selector}", fg="cyan"))
24+
25+
26+
async def _monitor_token_expiry(config, cancel_scope):
27+
"""Monitor token expiry and warn user."""
28+
token = getattr(config, "token", None)
29+
if not token:
30+
return
31+
32+
warned = False
33+
while not cancel_scope.cancel_called:
34+
try:
35+
remaining = get_token_remaining_seconds(token)
36+
if remaining is None:
37+
return
38+
39+
if remaining <= 0:
40+
click.echo(click.style("\nToken expired! Exiting shell.", fg="red", bold=True))
41+
cancel_scope.cancel()
42+
return
43+
elif remaining <= 300 and not warned: # 5 minutes
44+
mins, secs = int(remaining // 60), int(remaining % 60)
45+
click.echo(
46+
click.style(
47+
f"\nToken expires in {mins}m {secs}s. Session will continue but cleanup may fail on exit.",
48+
fg="yellow",
49+
bold=True,
50+
)
51+
)
52+
warned = True
53+
54+
await anyio.sleep(30)
55+
except Exception:
56+
return
57+
58+
1859
def _run_shell_with_lease(lease, exporter_logs, config, command):
1960
"""Run shell with lease context managers."""
61+
2062
def launch_remote_shell(path: str) -> int:
2163
return launch_shell(
22-
path, lease.exporter_name, config.drivers.allow, config.drivers.unsafe,
23-
config.shell.use_profiles, command=command, lease=lease
64+
path,
65+
lease.exporter_name,
66+
config.drivers.allow,
67+
config.drivers.unsafe,
68+
config.shell.use_profiles,
69+
command=command,
70+
lease=lease,
2471
)
2572

2673
with lease.serve_unix() as path:
@@ -39,13 +86,17 @@ async def _shell_with_signal_handling(
3986
"""Handle lease acquisition and shell execution with signal handling."""
4087
exit_code = 0
4188
cancelled_exc_class = get_cancelled_exc_class()
89+
lease_used = None
4290

4391
async with create_task_group() as tg:
4492
tg.start_soon(signal_handler, tg.cancel_scope)
93+
tg.start_soon(_monitor_token_expiry, config, tg.cancel_scope)
94+
4595
try:
4696
try:
4797
async with anyio.from_thread.BlockingPortal() as portal:
4898
async with config.lease_async(selector, lease_name, duration, portal, acquisition_timeout) as lease:
99+
lease_used = lease
49100
exit_code = await anyio.to_thread.run_sync(
50101
_run_shell_with_lease, lease, exporter_logs, config, command
51102
)
@@ -55,6 +106,13 @@ async def _shell_with_signal_handling(
55106
raise exc from None
56107
raise
57108
except cancelled_exc_class:
109+
# Check if cancellation was due to token expiry
110+
token = getattr(config, "token", None)
111+
if lease_used and token:
112+
remaining = get_token_remaining_seconds(token)
113+
if remaining is not None and remaining <= 0:
114+
_warn_about_expired_token(lease_used.name, selector)
115+
return 3 # Exit code for token expiry
58116
exit_code = 2
59117
finally:
60118
if not tg.cancel_scope.cancel_called:

0 commit comments

Comments
 (0)