diff --git a/CHANGELOG.md b/CHANGELOG.md index 415b550..4e866a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Deprecated features that will be removed in the next major version. - `burn_rate` from `TokenInfo` is deprecated. Use `burn_rate_cents` or `burn_rate_usd` instead. +- `--no-local` will become the default for `sporestack server list`. ## [Unreleased] - Nothing yet. -## [10.1.2] +## [10.2.0 - 2023-05-03] + +## Changed + +- Updated client to support new `forgotten_at` field and `deleted_by`. + +## [10.1.2 - 2023-04-14] ## Fixed diff --git a/src/sporestack/__init__.py b/src/sporestack/__init__.py index a1ecb15..366c391 100644 --- a/src/sporestack/__init__.py +++ b/src/sporestack/__init__.py @@ -2,4 +2,4 @@ __all__ = ["api", "api_client", "exceptions"] -__version__ = "10.1.2" +__version__ = "10.2.0" diff --git a/src/sporestack/api.py b/src/sporestack/api.py index 9b201aa..3e2dc09 100644 --- a/src/sporestack/api.py +++ b/src/sporestack/api.py @@ -1,8 +1,4 @@ -""" - -SporeStack API request/response models - -""" +"""SporeStack API request/response models""" from datetime import datetime @@ -21,7 +17,7 @@ class TokenAdd: class Request(BaseModel): currency: str dollars: int - affiliate_token: Optional[str] = None + affiliate_token: Union[str, None] = None class Response(BaseModel): token: str @@ -88,6 +84,15 @@ class ServerTopup: token: Union[str, None] = None +class ServerDeletedBy(str, Enum): + EXPIRATION = "expiration" + """The server was deleted automatically for being expired.""" + MANUAL = "manual" + """The server was deleted before its expiration via the API.""" + SPORESTACK = "sporestack" + """The server was deleted by SporeStack, likely due to an AUP violation.""" + + class ServerInfo: url = "/server/{machine_id}/info" method = "GET" @@ -104,6 +109,8 @@ class ServerInfo: flavor: Flavor deleted: bool deleted_at: int + deleted_by: Union[ServerDeletedBy, None] + forgotten_at: Union[datetime, None] operating_system: str hostname: str autorenew: bool @@ -120,13 +127,8 @@ class ServerStop: class ServerDelete: - url = "/server/{machine_id}/delete" - method = "POST" - - -class ServerDestroy: - url = "/server/{machine_id}/destroy" - method = "POST" + url = "/server/{machine_id}" + method = "DELETE" class ServerForget: diff --git a/src/sporestack/api_client.py b/src/sporestack/api_client.py index 1ba1c96..f6574fc 100644 --- a/src/sporestack/api_client.py +++ b/src/sporestack/api_client.py @@ -185,13 +185,11 @@ class APIClient: def server_delete(self, machine_id: str) -> None: """Delete a server.""" url = self.api_endpoint + api.ServerDelete.url.format(machine_id=machine_id) - response = self._httpx_client.post(url) + response = self._httpx_client.delete(url) _handle_response(response) def server_forget(self, machine_id: str) -> None: - """ - Forget about a destroyed/deleted server. - """ + """Forget about a deleted server to hide it from view.""" url = self.api_endpoint + api.ServerForget.url.format(machine_id=machine_id) response = self._httpx_client.post(url) _handle_response(response) diff --git a/src/sporestack/cli.py b/src/sporestack/cli.py index db4e998..383d1e9 100644 --- a/src/sporestack/cli.py +++ b/src/sporestack/cli.py @@ -279,6 +279,10 @@ def pretty_machine_info(info: Dict[str, Any]) -> str: return msg +def epoch_to_human(epoch: int) -> str: + return time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(epoch)) + + def print_machine_info(info: "api.ServerInfo.Response") -> None: if info.hostname != "": typer.echo(f"Hostname: {info.hostname}") @@ -296,13 +300,16 @@ def print_machine_info(info: "api.ServerInfo.Response") -> None: typer.echo("IPv4: (Not yet assigned)") typer.echo(f"Region: {info.region}") typer.echo(f"Flavor: {info.flavor.slug}") - human_expiration = time.strftime( - "%Y-%m-%d %H:%M:%S %z", time.localtime(info.expiration) - ) - typer.echo(f"Expiration: {info.expiration} ({human_expiration})") + typer.echo(f"Expiration: {epoch_to_human(info.expiration)}") typer.echo(f"Token: {info.token}") - if info.deleted: + 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)}") + if info.deleted_by is not None: + typer.echo(f"Server deleted by: {info.deleted_by.value}") + if info.forgotten_at is not None: + typer.echo(f"Server forgotten at: {info.forgotten_at}") else: typer.echo(f"Running: {info.running}") time_to_live = info.expiration - int(time.time()) @@ -317,6 +324,9 @@ def server_list( local: bool = typer.Option( True, help="List older servers not associated to token." ), + show_forgotten: bool = typer.Option( + False, help="Show deleted and forgotten servers." + ), ) -> None: """List all locally known servers and all servers under the given token.""" from .api_client import APIClient @@ -339,6 +349,9 @@ def server_list( printed_machine_ids = [] for info in server_infos: + if not show_forgotten and info.forgotten_at is not None: + continue + typer.echo() hostname = info.hostname @@ -408,6 +421,8 @@ def _get_machine_id(machine_id: str, hostname: str, token: str) -> str: api_client = APIClient(api_endpoint=get_api_endpoint()) for server in api_client.servers_launched_from_token(token=_token).servers: + if server.forgotten_at is not None: + continue if server.hostname == hostname: return server.machine_id diff --git a/src/sporestack/client.py b/src/sporestack/client.py index 4733e55..6a16588 100644 --- a/src/sporestack/client.py +++ b/src/sporestack/client.py @@ -82,9 +82,11 @@ class Token: """Returns support messages for/from the token.""" self.api_client.token_send_message(token=self.token, message=message) - def servers(self) -> List[Server]: + def servers(self, show_forgotten: bool = False) -> List[Server]: server_classes: List[Server] = [] for server in self.api_client.servers_launched_from_token(self.token).servers: + if not show_forgotten and server.forgotten_at is not None: + continue server_classes.append( Server( machine_id=server.machine_id,