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"]
__version__ = "7.3.0"
__version__ = "8.0.0"

View File

@ -2,42 +2,15 @@
SporeStack CLI: `sporestack`
"""
import importlib.util
import json
import logging
import os
import sys
import time
from pathlib import Path
from types import ModuleType
from typing import TYPE_CHECKING, Any, Dict, Optional
from typing import Any, Dict, Optional
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 = """
SporeStack Python CLI
@ -81,9 +54,11 @@ WAITING_PAYMENT_TO_PROCESS = "Waiting for payment to process..."
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:
api_endpoint = api_client.TOR_ENDPOINT
api_endpoint = TOR_ENDPOINT
return api_endpoint
@ -119,12 +94,14 @@ def launch(
"""
Launch a server on SporeStack.
"""
from . import utils
typer.echo(f"Launching server with token {token}...", err=True)
_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}...")
if not ssh_key_file.exists():
msg = f"{ssh_key_file} does not exist. "
@ -137,7 +114,7 @@ def launch(
machine_id = utils.random_machine_id()
if quote:
response = api_client.launch(
response = api_client.server_launch(
machine_id=machine_id,
days=days,
flavor=flavor,
@ -145,8 +122,6 @@ def launch(
ssh_key=ssh_key,
region=region,
token=_token,
api_endpoint=get_api_endpoint(),
retry=True,
quote=True,
hostname=hostname,
autorenew=autorenew,
@ -168,7 +143,7 @@ def launch(
tries = 360
while tries > 0:
response = api_client.launch(
response = api_client.server_launch(
machine_id=machine_id,
days=days,
flavor=flavor,
@ -178,8 +153,6 @@ def launch(
token=_token,
hostname=hostname,
autorenew=autorenew,
api_endpoint=get_api_endpoint(),
retry=True,
)
if response.created is True:
break
@ -193,11 +166,7 @@ def launch(
raise typer.Exit(code=1)
typer.echo(
pretty_machine_info(
api_client.info(
machine_id=machine_id, api_endpoint=get_api_endpoint()
).dict()
)
pretty_machine_info(api_client.server_info(machine_id=machine_id).dict())
)
@ -212,19 +181,21 @@ def topup(
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)
_token = load_token(token)
response = api_client.topup(
api_client.server_topup(
machine_id=machine_id,
days=days,
api_endpoint=get_api_endpoint(),
token=_token,
retry=True,
)
typer.echo(response.expiration)
typer.echo(f"Server topped up for {days} day(s)")
def server_info_path() -> Path:
@ -310,13 +281,14 @@ def server_list(
"""
List all locally known servers and all servers under the given token.
"""
from .api_client import APIClient
from .exceptions import SporeStackUserError
api_client = APIClient(api_endpoint=get_api_endpoint())
_token = load_token(token)
server_infos = api_client.servers_launched_from_token(
token=_token, api_endpoint=get_api_endpoint()
).servers
server_infos = api_client.servers_launched_from_token(token=_token).servers
machine_id_hostnames = {}
if local:
@ -341,20 +313,21 @@ def server_list(
typer.echo(f"Machine ID (keep this secret!): {info.machine_id}")
typer.echo(f"IPv6: {info.network_interfaces[0].ipv6}")
typer.echo(f"IPv4: {info.network_interfaces[0].ipv4}")
typer.echo(f"Running: {info.running}")
typer.echo(f"Region: {info.region}")
typer.echo(f"Flavor: {info.flavor.slug}")
typer.echo(f"Token: {info.token}")
typer.echo(f"Autorenew: {info.autorenew}")
human_expiration = time.strftime(
"%Y-%m-%d %H:%M:%S %z", time.localtime(info.expiration)
)
typer.echo(f"Expiration: {info.expiration} ({human_expiration})")
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"Token: {info.token}")
if info.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)
@ -367,9 +340,8 @@ def server_list(
continue
try:
upstream_vm_info = api_client.info(
machine_id=saved_vm_info["machine_id"],
api_endpoint=get_api_endpoint(),
upstream_vm_info = api_client.server_info(
machine_id=saved_vm_info["machine_id"]
)
saved_vm_info["expiration"] = upstream_vm_info.expiration
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)
for server in api_client.servers_launched_from_token(
token=_token, api_endpoint=get_api_endpoint()
).servers:
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
for server in api_client.servers_launched_from_token(token=_token).servers:
if server.hostname == hostname:
return server.machine_id
@ -429,18 +403,38 @@ def info(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -
Info on the VM
"""
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.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()
def start(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -> None:
"""
Boots the VM.
"""
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.")
@ -450,7 +444,10 @@ def stop(hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN) -
Immediately shuts down the VM.
"""
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.")
@ -462,7 +459,10 @@ def autorenew_enable(
Enable autorenew on a server.
"""
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.")
@ -474,19 +474,25 @@ def autorenew_disable(
Disable autorenew on a server.
"""
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.")
@server_cli.command()
def destroy(
def delete(
hostname: str = "", machine_id: str = "", token: str = DEFAULT_TOKEN
) -> 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)
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
server_info_path().joinpath(f"{hostname}.json").unlink(missing_ok=True)
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.
"""
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.")
@ -514,7 +523,10 @@ def rebuild(
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)
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.")
@ -523,7 +535,10 @@ def flavors() -> None:
"""
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:
typer.echo(f"{flavor}: {flavors[flavor]}")
@ -533,9 +548,10 @@ def operating_systems() -> None:
"""
Returns available operating systems.
"""
os_list = api_client.operating_systems(
api_endpoint=get_api_endpoint()
).operating_systems
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
os_list = api_client.operating_systems().operating_systems
for operating_system in os_list:
typer.echo(operating_system)
@ -590,11 +606,14 @@ def token_create(
typer.echo("Token already created! Did you mean to `topup`?", err=True)
raise typer.Exit(1)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
response = api_client.token_add(
token=_token,
dollars=dollars,
currency=currency,
api_endpoint=get_api_endpoint(),
retry=True,
)
@ -615,7 +634,6 @@ def token_create(
token=_token,
dollars=dollars,
currency=currency,
api_endpoint=get_api_endpoint(),
retry=True,
)
if response.payment.paid is True:
@ -649,11 +667,14 @@ def token_topup(
"""
token = load_token(token)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
response = api_client.token_add(
token,
dollars,
currency=currency,
api_endpoint=get_api_endpoint(),
retry=True,
)
@ -672,7 +693,6 @@ def token_topup(
token,
dollars,
currency=currency,
api_endpoint=get_api_endpoint(),
retry=True,
)
# Waiting for payment to set in.
@ -690,9 +710,11 @@ def balance(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
"""
_token = load_token(token)
typer.echo(
api_client.token_balance(token=_token, api_endpoint=get_api_endpoint()).usd
)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo(api_client.token_balance(token=_token).usd)
@token_cli.command()
@ -702,11 +724,11 @@ def servers(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
"""
_token = load_token(token)
typer.echo(
api_client.servers_launched_from_token(
token=_token, api_endpoint=get_api_endpoint()
)
)
from .api_client import APIClient
api_client = APIClient(api_endpoint=get_api_endpoint())
typer.echo(api_client.servers_launched_from_token(token=_token))
@token_cli.command(name="list")
@ -728,6 +750,8 @@ def version() -> None:
"""
Returns the installed version.
"""
from . import __version__
typer.echo(__version__)
@ -737,6 +761,8 @@ def api_endpoint() -> None:
Prints the selected API endpoint: Env var: SPORESTACK_ENDPOINT,
or, SPORESTACK_USE_TOR=1
"""
from . import api_client
endpoint = get_api_endpoint()
if ".onion" in endpoint:
typer.echo(f"{endpoint} using {api_client._get_tor_proxy()}")