Rewrite finished, for now

This commit is contained in:
Administrator 2023-02-07 19:30:24 +00:00
parent a947d83669
commit 6981ff00c7
2 changed files with 116 additions and 90 deletions

View File

@ -2,4 +2,4 @@
__all__ = ["api", "api_client", "exceptions"] __all__ = ["api", "api_client", "exceptions"]
__version__ = "7.3.0" __version__ = "8.0.0"

View File

@ -2,42 +2,15 @@
SporeStack CLI: `sporestack` SporeStack CLI: `sporestack`
""" """
import importlib.util
import json import json
import logging import logging
import os import os
import sys
import time import time
from pathlib import Path from pathlib import Path
from types import ModuleType from typing import Any, Dict, Optional
from typing import TYPE_CHECKING, Any, Dict, Optional
import typer import typer
from . import __version__
def lazy_import(name: str) -> ModuleType:
"""
Lazily import a module. Helps speed up CLI performance.
"""
spec = importlib.util.find_spec(name)
assert spec is not None
assert spec.loader is not None
loader = importlib.util.LazyLoader(spec.loader)
spec.loader = loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
loader.exec_module(module)
return module
# For mypy
if TYPE_CHECKING:
from . import api_client
else:
api_client = lazy_import("sporestack.api_client")
HELP = """ HELP = """
SporeStack Python CLI SporeStack Python CLI
@ -81,9 +54,11 @@ WAITING_PAYMENT_TO_PROCESS = "Waiting for payment to process..."
def get_api_endpoint() -> str: def get_api_endpoint() -> str:
api_endpoint = os.getenv("SPORESTACK_ENDPOINT", api_client.CLEARNET_ENDPOINT) from .api_client import CLEARNET_ENDPOINT, TOR_ENDPOINT
api_endpoint = os.getenv("SPORESTACK_ENDPOINT", CLEARNET_ENDPOINT)
if os.getenv("SPORESTACK_USE_TOR_ENDPOINT", None) is not None: if os.getenv("SPORESTACK_USE_TOR_ENDPOINT", None) is not None:
api_endpoint = api_client.TOR_ENDPOINT api_endpoint = TOR_ENDPOINT
return api_endpoint return api_endpoint
@ -119,12 +94,14 @@ def launch(
""" """
Launch a server on SporeStack. Launch a server on SporeStack.
""" """
from . import utils
typer.echo(f"Launching server with token {token}...", err=True) typer.echo(f"Launching server with token {token}...", err=True)
_token = load_token(token) _token = load_token(token)
from . import utils
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo(f"Loading SSH key from {ssh_key_file}...") typer.echo(f"Loading SSH key from {ssh_key_file}...")
if not ssh_key_file.exists(): if not ssh_key_file.exists():
msg = f"{ssh_key_file} does not exist. " msg = f"{ssh_key_file} does not exist. "
@ -137,7 +114,7 @@ def launch(
machine_id = utils.random_machine_id() machine_id = utils.random_machine_id()
if quote: if quote:
response = api_client.launch( response = api_client.server_launch(
machine_id=machine_id, machine_id=machine_id,
days=days, days=days,
flavor=flavor, flavor=flavor,
@ -145,8 +122,6 @@ def launch(
ssh_key=ssh_key, ssh_key=ssh_key,
region=region, region=region,
token=_token, token=_token,
api_endpoint=get_api_endpoint(),
retry=True,
quote=True, quote=True,
hostname=hostname, hostname=hostname,
autorenew=autorenew, autorenew=autorenew,
@ -168,7 +143,7 @@ def launch(
tries = 360 tries = 360
while tries > 0: while tries > 0:
response = api_client.launch( response = api_client.server_launch(
machine_id=machine_id, machine_id=machine_id,
days=days, days=days,
flavor=flavor, flavor=flavor,
@ -178,8 +153,6 @@ def launch(
token=_token, token=_token,
hostname=hostname, hostname=hostname,
autorenew=autorenew, autorenew=autorenew,
api_endpoint=get_api_endpoint(),
retry=True,
) )
if response.created is True: if response.created is True:
break break
@ -193,11 +166,7 @@ def launch(
raise typer.Exit(code=1) raise typer.Exit(code=1)
typer.echo( typer.echo(
pretty_machine_info( pretty_machine_info(api_client.server_info(machine_id=machine_id).dict())
api_client.info(
machine_id=machine_id, api_endpoint=get_api_endpoint()
).dict()
)
) )
@ -212,19 +181,21 @@ def topup(
Extend an existing SporeStack server's lifetime. Extend an existing SporeStack server's lifetime.
""" """
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
_token = load_token(token) _token = load_token(token)
response = api_client.topup( api_client.server_topup(
machine_id=machine_id, machine_id=machine_id,
days=days, days=days,
api_endpoint=get_api_endpoint(),
token=_token, token=_token,
retry=True,
) )
typer.echo(response.expiration) typer.echo(f"Server topped up for {days} day(s)")
def server_info_path() -> Path: def server_info_path() -> Path:
@ -310,13 +281,14 @@ def server_list(
""" """
List all locally known servers and all servers under the given token. List all locally known servers and all servers under the given token.
""" """
from .api_client import APIClient
from .exceptions import SporeStackUserError from .exceptions import SporeStackUserError
api_client = APIClient(api_endpoint=get_api_endpoint())
_token = load_token(token) _token = load_token(token)
server_infos = api_client.servers_launched_from_token( server_infos = api_client.servers_launched_from_token(token=_token).servers
token=_token, api_endpoint=get_api_endpoint()
).servers
machine_id_hostnames = {} machine_id_hostnames = {}
if local: if local:
@ -341,20 +313,21 @@ def server_list(
typer.echo(f"Machine ID (keep this secret!): {info.machine_id}") typer.echo(f"Machine ID (keep this secret!): {info.machine_id}")
typer.echo(f"IPv6: {info.network_interfaces[0].ipv6}") typer.echo(f"IPv6: {info.network_interfaces[0].ipv6}")
typer.echo(f"IPv4: {info.network_interfaces[0].ipv4}") typer.echo(f"IPv4: {info.network_interfaces[0].ipv4}")
typer.echo(f"Running: {info.running}")
typer.echo(f"Region: {info.region}") typer.echo(f"Region: {info.region}")
typer.echo(f"Flavor: {info.flavor.slug}") typer.echo(f"Flavor: {info.flavor.slug}")
typer.echo(f"Token: {info.token}")
typer.echo(f"Autorenew: {info.autorenew}")
human_expiration = time.strftime( human_expiration = time.strftime(
"%Y-%m-%d %H:%M:%S %z", time.localtime(info.expiration) "%Y-%m-%d %H:%M:%S %z", time.localtime(info.expiration)
) )
typer.echo(f"Expiration: {info.expiration} ({human_expiration})") typer.echo(f"Expiration: {info.expiration} ({human_expiration})")
time_to_live = info.expiration - int(time.time()) typer.echo(f"Token: {info.token}")
hours = time_to_live // 3600
typer.echo(f"Server will be deleted in {hours} hours.")
if info.deleted: if info.deleted:
typer.echo("Server was deleted!") typer.echo("Server was deleted!")
else:
typer.echo(f"Running: {info.running}")
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}")
printed_machine_ids.append(info.machine_id) printed_machine_ids.append(info.machine_id)
@ -367,9 +340,8 @@ def server_list(
continue continue
try: try:
upstream_vm_info = api_client.info( upstream_vm_info = api_client.server_info(
machine_id=saved_vm_info["machine_id"], machine_id=saved_vm_info["machine_id"]
api_endpoint=get_api_endpoint(),
) )
saved_vm_info["expiration"] = upstream_vm_info.expiration saved_vm_info["expiration"] = upstream_vm_info.expiration
saved_vm_info["running"] = upstream_vm_info.running saved_vm_info["running"] = upstream_vm_info.running
@ -411,9 +383,11 @@ def _get_machine_id(machine_id: str, hostname: str, token: str) -> str:
_token = load_token(token) _token = load_token(token)
for server in api_client.servers_launched_from_token( from .api_client import APIClient
token=_token, api_endpoint=get_api_endpoint()
).servers: api_client = APIClient(api_endpoint=get_api_endpoint())
for server in api_client.servers_launched_from_token(token=_token).servers:
if server.hostname == hostname: if server.hostname == hostname:
return server.machine_id return server.machine_id
@ -429,18 +403,38 @@ def info(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -
Info on the VM Info on the VM
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo( typer.echo(
api_client.info(machine_id=machine_id, api_endpoint=get_api_endpoint()).json() pretty_machine_info(api_client.server_info(machine_id=machine_id).dict())
) )
@server_cli.command(name="json")
def server_info_json(
hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN
) -> None:
"""
Info on the VM, in JSON format
"""
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo(api_client.server_info(machine_id=machine_id).json())
@server_cli.command() @server_cli.command()
def start(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -> None: def start(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -> None:
""" """
Boots the VM. Boots the VM.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.start(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.server_start(machine_id=machine_id)
typer.echo(f"{hostname} started.") typer.echo(f"{hostname} started.")
@ -450,7 +444,10 @@ def stop(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -
Immediately shuts down the VM. Immediately shuts down the VM.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.stop(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.server_stop(machine_id=machine_id)
typer.echo(f"{hostname} stopped.") typer.echo(f"{hostname} stopped.")
@ -462,7 +459,10 @@ def autorenew_enable(
Enable autorenew on a server. Enable autorenew on a server.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.autorenew_enable(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.autorenew_enable(machine_id=machine_id)
typer.echo("Autorenew enabled.") typer.echo("Autorenew enabled.")
@ -474,19 +474,25 @@ def autorenew_disable(
Disable autorenew on a server. Disable autorenew on a server.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.autorenew_disable(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.autorenew_disable(machine_id=machine_id)
typer.echo("Autorenew disabled.") typer.echo("Autorenew disabled.")
@server_cli.command() @server_cli.command()
def destroy( def delete(
hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN
) -> None: ) -> None:
""" """
Deletes/destroys the VM before expiration (no refunds/credits) Delete the VM before its expiration
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.destroy(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.server_delete(machine_id=machine_id)
# Also remove the .json file # Also remove the .json file
server_info_path().joinpath(f"{hostname}.json").unlink(missing_ok=True) server_info_path().joinpath(f"{hostname}.json").unlink(missing_ok=True)
typer.echo(f"{machine_id} was destroyed.") typer.echo(f"{machine_id} was destroyed.")
@ -500,7 +506,10 @@ def forget(
Forget about a deleted server so that it doesn't show up in server list. Forget about a deleted server so that it doesn't show up in server list.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.forget(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.server_forget(machine_id=machine_id)
typer.echo(f"{machine_id} was forgotten.") typer.echo(f"{machine_id} was forgotten.")
@ -514,7 +523,10 @@ def rebuild(
Will take a couple minutes to complete after the request is made. Will take a couple minutes to complete after the request is made.
""" """
machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token) machine_id = _get_machine_id(machine_id=machine_id, hostname=hostname, token=token)
api_client.rebuild(machine_id=machine_id, api_endpoint=get_api_endpoint()) from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
api_client.server_rebuild(machine_id=machine_id)
typer.echo(f"{hostname} rebuilding.") typer.echo(f"{hostname} rebuilding.")
@ -523,7 +535,10 @@ def flavors() -> None:
""" """
Returns available flavors. Returns available flavors.
""" """
flavors = api_client.flavors(api_endpoint=get_api_endpoint()).flavors from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
flavors = api_client.flavors().flavors
for flavor in flavors: for flavor in flavors:
typer.echo(f"{flavor}: {flavors[flavor]}") typer.echo(f"{flavor}: {flavors[flavor]}")
@ -533,9 +548,10 @@ def operating_systems() -> None:
""" """
Returns available operating systems. Returns available operating systems.
""" """
os_list = api_client.operating_systems( from .api_client import APIClient
api_endpoint=get_api_endpoint()
).operating_systems api_client = APIClient(api_endpoint=get_api_endpoint())
os_list = api_client.operating_systems().operating_systems
for operating_system in os_list: for operating_system in os_list:
typer.echo(operating_system) typer.echo(operating_system)
@ -590,11 +606,14 @@ def token_create(
typer.echo("Token already created! Did you mean to `topup`?", err=True) typer.echo("Token already created! Did you mean to `topup`?", err=True)
raise typer.Exit(1) raise typer.Exit(1)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
response = api_client.token_add( response = api_client.token_add(
token=_token, token=_token,
dollars=dollars, dollars=dollars,
currency=currency, currency=currency,
api_endpoint=get_api_endpoint(),
retry=True, retry=True,
) )
@ -615,7 +634,6 @@ def token_create(
token=_token, token=_token,
dollars=dollars, dollars=dollars,
currency=currency, currency=currency,
api_endpoint=get_api_endpoint(),
retry=True, retry=True,
) )
if response.payment.paid is True: if response.payment.paid is True:
@ -649,11 +667,14 @@ def token_topup(
""" """
token = load_token(token) token = load_token(token)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
response = api_client.token_add( response = api_client.token_add(
token, token,
dollars, dollars,
currency=currency, currency=currency,
api_endpoint=get_api_endpoint(),
retry=True, retry=True,
) )
@ -672,7 +693,6 @@ def token_topup(
token, token,
dollars, dollars,
currency=currency, currency=currency,
api_endpoint=get_api_endpoint(),
retry=True, retry=True,
) )
# Waiting for payment to set in. # Waiting for payment to set in.
@ -690,9 +710,11 @@ def balance(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
""" """
_token = load_token(token) _token = load_token(token)
typer.echo( from .api_client import APIClient
api_client.token_balance(token=_token, api_endpoint=get_api_endpoint()).usd
) api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo(api_client.token_balance(token=_token).usd)
@token_cli.command() @token_cli.command()
@ -702,11 +724,11 @@ def servers(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
""" """
_token = load_token(token) _token = load_token(token)
typer.echo( from .api_client import APIClient
api_client.servers_launched_from_token(
token=_token, api_endpoint=get_api_endpoint() api_client = APIClient(api_endpoint=get_api_endpoint())
)
) typer.echo(api_client.servers_launched_from_token(token=_token))
@token_cli.command(name="list") @token_cli.command(name="list")
@ -728,6 +750,8 @@ def version() -> None:
""" """
Returns the installed version. Returns the installed version.
""" """
from . import __version__
typer.echo(__version__) typer.echo(__version__)
@ -737,6 +761,8 @@ def api_endpoint() -> None:
Prints the selected API endpoint: Env var: SPORESTACK_ENDPOINT, Prints the selected API endpoint: Env var: SPORESTACK_ENDPOINT,
or, SPORESTACK_USE_TOR=1 or, SPORESTACK_USE_TOR=1
""" """
from . import api_client
endpoint = get_api_endpoint() endpoint = get_api_endpoint()
if ".onion" in endpoint: if ".onion" in endpoint:
typer.echo(f"{endpoint} using {api_client._get_tor_proxy()}") typer.echo(f"{endpoint} using {api_client._get_tor_proxy()}")