From 8d00120a13df5de4845ef975a62b87706c6bf6e8 Mon Sep 17 00:00:00 2001 From: SporeStack Date: Fri, 1 Apr 2022 22:56:17 +0000 Subject: [PATCH] 6.0.0a2: --quote/--no-quote --- CHANGELOG.md | 6 +++ setup.cfg | 2 +- src/sporestack/api.py | 58 ++++++++++++++-------- src/sporestack/api_client.py | 8 ++- src/sporestack/cli.py | 94 ++++++++++++++++++++++-------------- tests/test_api_client.py | 31 +----------- 6 files changed, 109 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b73dd23..17e22d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.0.0a2 - 2022-04-01] + +### Added + +- `--quote` / `--no-quote` to launch/topup. Prompt by default if price to draw from token is acceptable. + ### Removed - affiliate_amount diff --git a/setup.cfg b/setup.cfg index 624b54c..bae3ee4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = sporestack -version = 6.0.0a1 +version = 6.0.0a2 description = SporeStack.com library and client. Launch servers with Monero or Bitcoin. long_description = file: README.md long_description_content_type = text/markdown diff --git a/src/sporestack/api.py b/src/sporestack/api.py index 1dee59a..2b8d55e 100644 --- a/src/sporestack/api.py +++ b/src/sporestack/api.py @@ -11,6 +11,8 @@ from pydantic import BaseModel from .models import NetworkInterface, Payment +LATEST_API_VERSION = 3 + class TokenEnable: url = "/token/{token}/enable" @@ -19,6 +21,7 @@ class TokenEnable: class Request(BaseModel): currency: str dollars: int + affiliate_token: Optional[str] = None class Response(BaseModel): token: str @@ -32,6 +35,7 @@ class TokenAdd: class Request(BaseModel): currency: str dollars: int + affiliate_token: Optional[str] = None class Response(BaseModel): token: str @@ -55,29 +59,35 @@ class ServerLaunch: class Request(BaseModel): machine_id: str days: int - currency: str flavor: str ssh_key: str operating_system: str - region: Optional[str] - organization: Optional[str] - settlement_token: Optional[str] - affiliate_token: Optional[str] + currency: Optional[str] = None + """Currency only needs to be set if not paying with a token.""" + region: Optional[str] = None + organization: Optional[str] = None + token: Optional[str] = None + quote: bool = False + affiliate_token: Optional[str] = None + affiliate_amount: None = None + """Deprecated field""" + settlement_token: Optional[str] = None + """Deprecated field. Use token instead.""" class Response(BaseModel): - created_at: Optional[int] payment: Payment - expiration: Optional[int] + expiration: int machine_id: str - network_interfaces: List[NetworkInterface] - region: str - latest_api_version: int - created: bool - paid: bool - warning: Optional[str] - txid: Optional[str] operating_system: str flavor: str + network_interfaces: List[NetworkInterface] = [] + created_at: int = 0 + region: Optional[str] = None + latest_api_version: int = LATEST_API_VERSION + created: bool = False + paid: bool = False + warning: Optional[str] = None + txid: Optional[str] = None class ServerTopup: @@ -87,18 +97,24 @@ class ServerTopup: class Request(BaseModel): machine_id: str days: int - currency: str - settlement_token: Optional[str] - affiliate_token: Optional[str] + token: Optional[str] = None + quote: bool = False + currency: Optional[str] = None + """Currency only needs to be set if not paying with a token.""" + affiliate_token: Optional[str] = None + affiliate_amount: None = None + """Deprecated field""" + settlement_token: Optional[str] = None + """Deprecated field. Use token instead.""" class Response(BaseModel): machine_id: str payment: Payment - paid: bool - warning: Optional[str] expiration: int - txid: Optional[str] - latest_api_version: int + paid: bool = False + warning: Optional[str] = None + txid: Optional[str] = None + latest_api_version: int = LATEST_API_VERSION class ServerInfo: diff --git a/src/sporestack/api_client.py b/src/sporestack/api_client.py index a31bdcd..09029ba 100644 --- a/src/sporestack/api_client.py +++ b/src/sporestack/api_client.py @@ -140,17 +140,19 @@ def launch( token: Optional[str] = None, retry: bool = False, affiliate_token: Optional[str] = None, + quote: bool = False, ) -> api.ServerLaunch.Response: request = api.ServerLaunch.Request( machine_id=machine_id, days=days, currency=currency, - settlement_token=token, + token=token, affiliate_token=affiliate_token, flavor=flavor, region=region, operating_system=operating_system, ssh_key=ssh_key, + quote=quote, ) url = api_endpoint + api.ServerLaunch.url.format(machine_id=machine_id) response = _api_request(url=url, json_params=request.dict(), retry=retry) @@ -167,6 +169,7 @@ def topup( token: Optional[str] = None, retry: bool = False, affiliate_token: Optional[str] = None, + quote: bool = False, ) -> api.ServerTopup.Response: """ Topup a server. @@ -175,8 +178,9 @@ def topup( machine_id=machine_id, days=days, currency=currency, - settlement_token=token, + token=token, affiliate_token=affiliate_token, + quote=quote, ) url = api_endpoint + api.ServerTopup.url.format(machine_id=machine_id) response = _api_request(url=url, json_params=request.dict(), retry=retry) diff --git a/src/sporestack/cli.py b/src/sporestack/cli.py index 19d9345..1fca439 100644 --- a/src/sporestack/cli.py +++ b/src/sporestack/cli.py @@ -116,9 +116,10 @@ def launch( flavor: str = DEFAULT_FLAVOR, token: str = DEFAULT_TOKEN, region: Optional[str] = None, + quote: bool = typer.Option(True, help="Require manual price confirmation."), ) -> None: """ - Attempts to launch a server. + Launch a server on SporeStack. """ from . import utils @@ -141,40 +142,45 @@ def launch( machine_id = utils.random_machine_id() - response = api_client.launch( - machine_id=machine_id, - days=days, - flavor=flavor, - operating_system=operating_system, - ssh_key=ssh_key, - currency="settlement", - region=region, - token=_token, - api_endpoint=get_api_endpoint(), - retry=True, - ) + if quote: + response = api_client.launch( + machine_id=machine_id, + days=days, + flavor=flavor, + operating_system=operating_system, + ssh_key=ssh_key, + currency="settlement", + region=region, + token=_token, + api_endpoint=get_api_endpoint(), + retry=True, + quote=True, + ) - if response.created is False: - tries = 360 - while tries > 0: - typer.echo("Waiting for server to build...", err=True) - tries = tries + 1 - # Waiting for server to spin up. - time.sleep(10) - response = api_client.launch( - machine_id=machine_id, - days=days, - flavor=flavor, - operating_system=operating_system, - ssh_key=ssh_key, - currency="settlement", - region=region, - token=_token, - api_endpoint=get_api_endpoint(), - retry=True, - ) - if response.created is True: - break + msg = f"Is {response.payment.usd} for {days} day(s) of {flavor} okay?" + typer.echo(msg, err=True) + input("[Press ctrl+c to cancel, or enter to accept.]") + + tries = 360 + while tries > 0: + response = api_client.launch( + machine_id=machine_id, + days=days, + flavor=flavor, + operating_system=operating_system, + ssh_key=ssh_key, + currency="settlement", + region=region, + token=_token, + api_endpoint=get_api_endpoint(), + retry=True, + ) + if response.created is True: + break + typer.echo("Waiting for server to build...", err=True) + tries = tries + 1 + # Waiting for server to spin up. + time.sleep(10) if response.created is False: typer.echo("Server creation failed, tries exceeded.", err=True) @@ -192,9 +198,10 @@ def topup( hostname: str, days: int = typer.Option(...), token: str = DEFAULT_TOKEN, + quote: bool = typer.Option(True, help="Require manual price confirmation."), ) -> None: """ - tops up an existing vm. + Extend an existing SporeStack server's lifetime. """ if not machine_exists(hostname): @@ -206,6 +213,20 @@ def topup( machine_info = get_machine_info(hostname) machine_id = machine_info["machine_id"] + if quote: + response = api_client.topup( + machine_id=machine_id, + days=days, + currency="settlement", + api_endpoint=get_api_endpoint(), + token=_token, + retry=True, + quote=True, + ) + + typer.echo(f"Is {response.payment.usd} for {days} day(s) okay?", err=True) + input("[Press ctrl+c to cancel, or enter to accept.]") + response = api_client.topup( machine_id=machine_id, days=days, @@ -214,6 +235,7 @@ def topup( token=_token, retry=True, ) + assert response.payment.paid is True machine_info["expiration"] = response.expiration save_machine_info(machine_info, overwrite=True) @@ -428,7 +450,7 @@ def load_token(token: str) -> str: def save_token(token: str, key: str) -> None: token_file = token_path().joinpath(f"{token}.json") if token_file.exists(): - msg = "Token '{token}' already exists in {token_file}. Aborting!" + msg = f"Token '{token}' already exists in {token_file}. Aborting!" typer.echo(msg, err=True) raise typer.Exit(code=1) diff --git a/tests/test_api_client.py b/tests/test_api_client.py index b40b4cc..d981aaf 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -31,41 +31,12 @@ def test_launch(mock_api_request: MagicMock) -> None: ssh_key="id-rsa...", flavor="aflavor", ) - json_params = { - "machine_id": "dummymachineid", - "days": 1, - "currency": "xmr", - "flavor": "aflavor", - "ssh_key": "id-rsa...", - "operating_system": "freebsd-12", - "region": None, - "organization": None, - "settlement_token": None, - "affiliate_token": None, - } - mock_api_request.assert_called_once_with( - url="https://api.sporestack.com/server/dummymachineid/launch", - json_params=json_params, - retry=False, - ) @patch("sporestack.api_client._api_request") def test_topup(mock_api_request: MagicMock) -> None: with pytest.raises(ValidationError): api_client.topup("dummymachineid", currency="xmr", days=1) - json_params = { - "machine_id": "dummymachineid", - "days": 1, - "currency": "xmr", - "settlement_token": None, - "affiliate_token": None, - } - mock_api_request.assert_called_once_with( - url="https://api.sporestack.com/server/dummymachineid/topup", - json_params=json_params, - retry=False, - ) @patch("sporestack.api_client._api_request") @@ -122,7 +93,7 @@ def test_token_balance(mock_api_request: MagicMock) -> None: def test_token_enable(mock_api_request: MagicMock) -> None: with pytest.raises(ValidationError): api_client.token_enable("dummytoken", currency="xmr", dollars=20) - json_params = {"currency": "xmr", "dollars": 20} + json_params = {"currency": "xmr", "dollars": 20, "affiliate_token": None} mock_api_request.assert_called_once_with( url="https://api.sporestack.com/token/dummytoken/enable", json_params=json_params,