From ac7eb3a18635c8010515433362ce5b59fbfd6e96 Mon Sep 17 00:00:00 2001 From: SporeStack Date: Tue, 31 Oct 2023 21:14:57 +0000 Subject: [PATCH] 10.7.0: Add suspended server support --- CHANGELOG.md | 8 +++++ src/sporestack/__init__.py | 4 +-- src/sporestack/api.py | 3 +- src/sporestack/cli.py | 62 ++++++++++++++++++++++++-------------- src/sporestack/models.py | 5 +-- tests/test_api_client.py | 3 ++ 6 files changed, 57 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed36642..602642d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Nothing yet. +## [10.7.0 - 2023-10-31] + +## Added + +- Added `suspended_at` to server info response object. +- Added `autorenew_servers` to token info response object. +- Added `suspended_servers` to token info response object. + ## [10.6.3 - 2023-09-18] ## Changed diff --git a/src/sporestack/__init__.py b/src/sporestack/__init__.py index e2aafd0..3003b57 100644 --- a/src/sporestack/__init__.py +++ b/src/sporestack/__init__.py @@ -1,5 +1,5 @@ """SporeStack API library and CLI for launching servers with Monero or Bitcoin""" -__all__ = ["api", "api_client", "exceptions"] +__all__ = ["api", "api_client", "client", "exceptions"] -__version__ = "10.6.3" +__version__ = "10.7.0" diff --git a/src/sporestack/api.py b/src/sporestack/api.py index c537538..0860d68 100644 --- a/src/sporestack/api.py +++ b/src/sporestack/api.py @@ -117,10 +117,11 @@ class ServerInfo: ipv6: str region: str flavor: Flavor - deleted: bool + deleted: Annotated[bool, Field(deprecated=True)] deleted_at: int deleted_by: Union[ServerDeletedBy, None] forgotten_at: Union[datetime, None] + suspended_at: Union[datetime, None] operating_system: str hostname: str autorenew: bool diff --git a/src/sporestack/cli.py b/src/sporestack/cli.py index ec46151..9b25e5e 100644 --- a/src/sporestack/cli.py +++ b/src/sporestack/cli.py @@ -303,38 +303,49 @@ def epoch_to_human(epoch: int) -> str: def print_machine_info(info: "api.ServerInfo.Response") -> None: - if info.hostname != "": - typer.echo(f"Hostname: {info.hostname}") - else: - typer.echo("Hostname: (none) (No hostname set)") + from rich.console import Console - typer.echo(f"Machine ID (keep this secret!): {info.machine_id}") + console = Console(width=None if sys.stdout.isatty() else 10**9) + + output = "" + + if info.hostname != "": + output += f"Hostname: {info.hostname}\n" + else: + output += "Hostname: (none) (No hostname set)\n" + + output += f"Machine ID (keep this secret!): {info.machine_id}\n" if info.ipv6 != "": - typer.echo(f"IPv6: {info.ipv6}") + output += f"IPv6: {info.ipv6}\n" else: - typer.echo("IPv6: (Not yet assigned)") + output += "IPv6: (Not yet assigned)\n" if info.ipv4 != "": - typer.echo(f"IPv4: {info.ipv4}") + output += f"IPv4: {info.ipv4}\n" else: - typer.echo("IPv4: (Not yet assigned)") - typer.echo(f"Region: {info.region}") - typer.echo(f"Flavor: {info.flavor.slug}") - typer.echo(f"Expiration: {epoch_to_human(info.expiration)}") - typer.echo(f"Token (keep this secret!): {info.token}") - if info.deleted_at != 0 or info.deleted: - typer.echo("Server was deleted!") - if info.deleted_at != 0: - typer.echo(f"Server deleted at: {epoch_to_human(info.deleted_at)}") + output += "IPv4: (Not yet assigned)\n" + output += f"Region: {info.region}\n" + output += f"Flavor: {info.flavor.slug}\n" + output += f"Token (keep this secret!): {info.token}\n" + if info.deleted_at != 0: + output += f"Server deleted at: {epoch_to_human(info.deleted_at)}\n" if info.deleted_by is not None: - typer.echo(f"Server deleted by: {info.deleted_by.value}") + output += f"Server deleted by: {info.deleted_by.value}\n" if info.forgotten_at is not None: - typer.echo(f"Server forgotten at: {info.forgotten_at}") + output += f"Server forgotten at: {info.forgotten_at}\n" else: - typer.echo(f"Running: {info.running}") + msg = f"Running: {info.running}\n" + if info.suspended_at is not None: + msg = ( + "Running: Server is powered off because it is [bold]suspended[/bold].\n" + ) + output += msg time_to_live = info.expiration - int(time.time()) hours = time_to_live // 3600 - typer.echo(f"Server will be deleted in {hours} hours.") - typer.echo(f"Autorenew: {info.autorenew}") + output += f"Server will be deleted in {hours} hours.\n" + output += f"Expiration: {epoch_to_human(info.expiration)}\n" + output += f"Autorenew: {info.autorenew}" + + console.print(output) @server_cli.command(name="list") @@ -414,6 +425,9 @@ def server_list( str(info.autorenew), ) + if info.suspended_at != 0: + typer.echo(f"Warning: {info.machine_id} is suspended!", err=True) + printed_machine_ids.append(info.machine_id) console.print(table) @@ -887,7 +901,9 @@ def token_info(token: Annotated[str, typer.Argument()] = DEFAULT_TOKEN) -> None: info = client.token.info() print(f"[bold]Token Information for {token} ({_token})[/bold]") print(f"Balance: [green]{info.balance_usd}") - print(f"Total Servers: {info.servers}") + print(f"Total Servers (not deleted): {info.servers}") + print(f"Servers set to autorenew: {info.autorenew_servers}") + print(f"Suspended servers: {info.suspended_servers}") print( f"Burn Rate: [red]{info.burn_rate_usd}[/red] " "(per day of servers set to autorenew)" diff --git a/src/sporestack/models.py b/src/sporestack/models.py index 39ca7fc..ba3ca56 100644 --- a/src/sporestack/models.py +++ b/src/sporestack/models.py @@ -62,12 +62,13 @@ class OperatingSystem(BaseModel): class TokenInfo(BaseModel): balance_cents: int balance_usd: str - burn_rate: int - """Deprecated.""" + burn_rate: Annotated[int, Field(deprecated=True)] burn_rate_cents: int burn_rate_usd: str days_remaining: int servers: int + autorenew_servers: int + suspended_servers: int class Region(BaseModel): diff --git a/tests/test_api_client.py b/tests/test_api_client.py index 6081e9a..80262f8 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -81,6 +81,8 @@ def test_token_info(respx_mock: respx.MockRouter) -> None: "balance_cents": 0, "balance_usd": "$0.00", "servers": 0, + "autorenew_servers": 0, + "suspended_servers": 0, "burn_rate": 0, "burn_rate_usd": "$0.00", "burn_rate_cents": 0, @@ -133,6 +135,7 @@ def test_server_info(respx_mock: respx.MockRouter) -> None: "deleted_at": 0, "deleted_by": None, "forgotten_at": None, + "suspended_at": None, "operating_system": "debian-11", } route_response = httpx.Response(200, json=response_json)