6.0.0a1: Token-centric release
See the changelog for more information!
This commit is contained in:
parent
48e15d876a
commit
2af37624f7
|
@ -23,6 +23,7 @@ pipeline:
|
|||
image: python:3.9
|
||||
commands:
|
||||
- pip install pipenv==2022.1.8 pre-commit==2.17.0
|
||||
- pre-commit run --all-files
|
||||
- pipenv install --dev --deploy
|
||||
- pipenv run almake test
|
||||
- pipenv run almake build-dist
|
||||
|
@ -33,6 +34,7 @@ pipeline:
|
|||
image: python:3.10
|
||||
commands:
|
||||
- pip install pipenv==2022.1.8 pre-commit==2.17.0
|
||||
- pre-commit run --all-files
|
||||
- pipenv install --dev --deploy
|
||||
- pipenv run almake test
|
||||
- pipenv run almake build-dist
|
||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -15,6 +15,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Nothing yet.
|
||||
|
||||
## [6.0.0a1 - 2022-03-31]
|
||||
|
||||
Remember to backup your ~/.sporestack folder as any tokens you generate will be stored there!
|
||||
|
||||
### Changed
|
||||
|
||||
- Now token-centric. You can only use `sporestack` to launch or topup servers from a token.
|
||||
- `sporestack launch/info/topup`, etc, moved to `sporestack server launch/info/topup`, etc.
|
||||
- `--token` argument takes the name of the token, and not the key. Defaults to `primary`.
|
||||
- `--ssh-key-file` now defaults to `~/.ssh/id_rsa.pub`.
|
||||
- Import generated tokens from the key with: `sporestack token import (token reference name, default is primary) --key (the token key in hex format)`
|
||||
|
||||
### Added
|
||||
|
||||
- New token commands: `sporestack token create/list`
|
||||
|
||||
## [5.2.3 - 2022-03-30]
|
||||
|
||||
### Added
|
||||
|
|
1
Makefile
1
Makefile
|
@ -1,5 +1,4 @@
|
|||
test:
|
||||
pre-commit run --all-files
|
||||
python -m pflake8 .
|
||||
python -m mypy --strict .
|
||||
$(MAKE) test-pytest
|
||||
|
|
38
README.md
38
README.md
|
@ -7,45 +7,37 @@
|
|||
## Installation
|
||||
|
||||
* `pip install sporestack`
|
||||
* Recommended: Create a virtual environment, first. Can use `pipenv`, as well.
|
||||
* Recommended: Create a virtual environment, first, and use it inside there.
|
||||
|
||||
## Running without installing (preferred)
|
||||
## Running without installing
|
||||
|
||||
* Make sure `pipx` is installed.
|
||||
* `pipx run sporestack`
|
||||
* Make sure you're on the latest version with `sporestack version`.
|
||||
|
||||
## Screenshot
|
||||
|
||||
![sporestack CLI screenshot](https://sporestack.com/static/sporestackv2-screenshot.png)
|
||||
* Make sure you're on the latest stable version comparing `sporestack version` with git tags in this repository, or releases on [PyPI](https://pypi.org/project/sporestack/).
|
||||
|
||||
## Usage
|
||||
|
||||
* `sporestack launch SomeHostname --flavor vps-1vcpu-1gb --days 7 --ssh-key ~/.ssh/id_rsa.pub --operating-system debian-10 --currency btc`
|
||||
* `sporestack topup SomeHostname --days 3 --currency xmr`
|
||||
* `sporestack launch SomeOtherHostname --flavor vps-1vcpu-2gb --days 7 --ssh-key ~/.ssh/id_rsa.pub --operating-system debian-11 --currency btc`
|
||||
* `sporestack stop SomeHostname`
|
||||
* `sporestack start SomeHostname`
|
||||
* `sporestack list`
|
||||
* `sporestack remove SomeHostname # If expired`
|
||||
* `sporestack settlement-token-generate`
|
||||
* `sporestack settlement-token-enable (token) --dollars 10 --currency xmr`
|
||||
* `sporestack settlement-token-add (token) --dollars 25 --currency btc`
|
||||
* `sporestack settlement-token-balance (token)`
|
||||
|
||||
More examples on the [website](https://sporestack.com).
|
||||
* `sporestack token create --dollars 20 --currency xmr # Can use btc as well.`
|
||||
* `sporestack token list`
|
||||
* `sporestack token balance`
|
||||
* `sporestack server launch SomeHostname --operating-system debian-11 --days 1 # Will use ~/.ssh/id_rsa.pub as your SSH key, by default`
|
||||
(You may also want to consider passing `--region` to have a non-random region. This will use the "primary" token by default, which is the default when you run `sporestack token create`.)
|
||||
* `sporestack server stop SomeHostname`
|
||||
* `sporestack server start SomeHostname`
|
||||
* `sporestack server list`
|
||||
* `sporestack server remove SomeHostname # If expired`
|
||||
|
||||
## Notes
|
||||
|
||||
* You can use `--settlement-token` if you don't want to pay with QR codes all the time.
|
||||
* If using a .onion API endpoint, will try to use a local Tor proxy if connecting to a .onion URL. (127.0.0.1:9050)
|
||||
* If you want to communicate with SporeStack APIs using Tor, set this environment variable: `SPORESTACK_USE_TOR_ENDPOINT=1`
|
||||
|
||||
## Developing
|
||||
|
||||
* `pip install pipenv pre-commit`
|
||||
* `pre-commit install`
|
||||
* `pipenv install --deploy --dev`
|
||||
* `pipenv run make test` (If you don't have `make`, use `almake`)
|
||||
* Hint: `pre-commit run` is a faster way to run some of the tests/autofixers.
|
||||
* `pre-commit run --all-files` (To format code, or wait for `git commit`)
|
||||
|
||||
## Licence
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = sporestack
|
||||
version = 5.2.3
|
||||
version = 6.0.0a1
|
||||
description = SporeStack.com library and client. Launch servers with Monero or Bitcoin.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
|
|
@ -137,7 +137,7 @@ def launch(
|
|||
ssh_key: str,
|
||||
api_endpoint: str = API_ENDPOINT,
|
||||
region: Optional[str] = None,
|
||||
settlement_token: Optional[str] = None,
|
||||
token: Optional[str] = None,
|
||||
retry: bool = False,
|
||||
affiliate_amount: Optional[int] = None,
|
||||
affiliate_token: Optional[str] = None,
|
||||
|
@ -146,7 +146,7 @@ def launch(
|
|||
machine_id=machine_id,
|
||||
days=days,
|
||||
currency=currency,
|
||||
settlement_token=settlement_token,
|
||||
settlement_token=token,
|
||||
affiliate_amount=affiliate_amount,
|
||||
affiliate_token=affiliate_token,
|
||||
flavor=flavor,
|
||||
|
@ -166,7 +166,7 @@ def topup(
|
|||
days: int,
|
||||
currency: str,
|
||||
api_endpoint: str = API_ENDPOINT,
|
||||
settlement_token: Optional[str] = None,
|
||||
token: Optional[str] = None,
|
||||
retry: bool = False,
|
||||
affiliate_amount: Optional[int] = None,
|
||||
affiliate_token: Optional[str] = None,
|
||||
|
@ -178,7 +178,7 @@ def topup(
|
|||
machine_id=machine_id,
|
||||
days=days,
|
||||
currency=currency,
|
||||
settlement_token=settlement_token,
|
||||
settlement_token=token,
|
||||
affiliate_amount=affiliate_amount,
|
||||
affiliate_token=affiliate_token,
|
||||
)
|
||||
|
|
|
@ -53,11 +53,29 @@ SPORESTACK_USE_TOR_ENDPOINT
|
|||
TOR_PROXY (defaults to socks5h://127.0.0.1:9050 which is fine for most)
|
||||
"""
|
||||
|
||||
_home = os.getenv("HOME", None)
|
||||
assert _home is not None, "Unable to detect $HOME environment variable?"
|
||||
HOME = Path(_home)
|
||||
|
||||
SPORESTACK_DIR = HOME / ".sporestack"
|
||||
|
||||
cli = typer.Typer(help=HELP)
|
||||
|
||||
token_cli = typer.Typer()
|
||||
HOME = Path(_home)
|
||||
cli.add_typer(token_cli, name="token")
|
||||
server_cli = typer.Typer()
|
||||
cli.add_typer(server_cli, name="server")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
DEFAULT_TOKEN = "primary"
|
||||
DEFAULT_FLAVOR = "vps-1vcpu-1gb"
|
||||
# Users may have a different key file, but this is the most common.
|
||||
DEFAULT_SSH_KEY_FILE = HOME / ".ssh" / "id_rsa.pub"
|
||||
|
||||
# On disk format
|
||||
TOKEN_VERSION = 1
|
||||
|
||||
WAITING_PAYMENT_TO_PROCESS = "Waiting for payment to process..."
|
||||
|
||||
|
@ -86,15 +104,14 @@ Press ctrl+c to abort."""
|
|||
input("[Press enter once you have made payment.]")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def launch(
|
||||
hostname: str,
|
||||
days: int = typer.Option(...),
|
||||
ssh_key_file: Path = typer.Option(...),
|
||||
operating_system: str = typer.Option(...),
|
||||
ssh_key_file: Path = DEFAULT_SSH_KEY_FILE,
|
||||
flavor: str = DEFAULT_FLAVOR,
|
||||
currency: Optional[str] = None,
|
||||
settlement_token: Optional[str] = None,
|
||||
token: str = DEFAULT_TOKEN,
|
||||
region: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -103,70 +120,37 @@ def launch(
|
|||
|
||||
from . import utils
|
||||
|
||||
if settlement_token is not None:
|
||||
if currency is None or currency == "settlement":
|
||||
currency = "settlement"
|
||||
else:
|
||||
msg = "Cannot use non-settlement --currency with --settlement-token"
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=2)
|
||||
if currency is None:
|
||||
typer.echo("--currency must be set.", err=True)
|
||||
raise typer.Exit(code=2)
|
||||
typer.echo(f"Launching server with token {token}...", err=True)
|
||||
_token = load_token(token)
|
||||
|
||||
if machine_exists(hostname):
|
||||
typer.echo(f"{hostname} already created.")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
typer.echo(f"Loading SSH key from {ssh_key_file}...")
|
||||
if not ssh_key_file.exists():
|
||||
msg = f"{ssh_key_file} does not exist. "
|
||||
msg += "You can try generating a key file with `ssh-keygen`"
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
ssh_key = ssh_key_file.read_text()
|
||||
|
||||
machine_id = utils.random_machine_id()
|
||||
|
||||
assert currency is not None
|
||||
response = api_client.launch(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
currency="settlement",
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
token=_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
|
||||
# This will be false at least the first time if paying with BTC or BCH.
|
||||
if response.payment.paid is False:
|
||||
assert response.payment.uri is not None
|
||||
make_payment(
|
||||
currency=currency,
|
||||
uri=response.payment.uri,
|
||||
usd=response.payment.usd,
|
||||
)
|
||||
|
||||
tries = 360
|
||||
while tries > 0:
|
||||
tries = tries - 1
|
||||
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
||||
# FIXME: Wait one hour in a smarter way.
|
||||
# Waiting for payment to set in.
|
||||
time.sleep(10)
|
||||
response = api_client.launch(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
if response.payment.paid is True:
|
||||
break
|
||||
|
||||
if response.created is False:
|
||||
tries = 360
|
||||
while tries > 0:
|
||||
|
@ -180,9 +164,9 @@ def launch(
|
|||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
currency="settlement",
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
token=_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
|
@ -200,103 +184,69 @@ def launch(
|
|||
typer.echo(json.dumps(created_dict, indent=4))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def topup(
|
||||
hostname: str,
|
||||
days: int = typer.Option(...),
|
||||
currency: Optional[str] = None,
|
||||
settlement_token: Optional[str] = None,
|
||||
token: str = DEFAULT_TOKEN,
|
||||
) -> None:
|
||||
"""
|
||||
tops up an existing vm.
|
||||
"""
|
||||
if settlement_token is not None:
|
||||
if currency is None or currency == "settlement":
|
||||
currency = "settlement"
|
||||
else:
|
||||
msg = "Cannot use non-settlement --currency with --settlement-token"
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=2)
|
||||
|
||||
if currency is None:
|
||||
typer.echo("--currency must be set.", err=True)
|
||||
raise typer.Exit(code=2)
|
||||
|
||||
if not machine_exists(hostname):
|
||||
typer.echo(f"{hostname} does not exist.")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
_token = load_token(token)
|
||||
|
||||
machine_info = get_machine_info(hostname)
|
||||
machine_id = machine_info["machine_id"]
|
||||
|
||||
assert currency is not None
|
||||
response = api_client.topup(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
currency=currency,
|
||||
currency="settlement",
|
||||
api_endpoint=get_api_endpoint(),
|
||||
settlement_token=settlement_token,
|
||||
token=_token,
|
||||
retry=True,
|
||||
)
|
||||
|
||||
# This will be false at least the first time if paying with anything
|
||||
# but settlement.
|
||||
if response.payment.paid is False:
|
||||
assert response.payment.uri is not None
|
||||
make_payment(
|
||||
currency=currency,
|
||||
uri=response.payment.uri,
|
||||
usd=response.payment.usd,
|
||||
)
|
||||
|
||||
tries = 360
|
||||
while tries > 0:
|
||||
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
||||
tries = tries - 1
|
||||
# FIXME: Wait one hour in a smarter way.
|
||||
# Waiting for payment to set in.
|
||||
time.sleep(10)
|
||||
response = api_client.topup(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
currency=currency,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
settlement_token=settlement_token,
|
||||
retry=True,
|
||||
)
|
||||
if response.payment.paid is True:
|
||||
break
|
||||
|
||||
machine_info["expiration"] = response.expiration
|
||||
save_machine_info(machine_info, overwrite=True)
|
||||
typer.echo(machine_info["expiration"])
|
||||
|
||||
|
||||
def server_info_path() -> Path:
|
||||
home = os.getenv("HOME")
|
||||
assert home is not None, "Unable to detect $HOME environment variable?"
|
||||
sporestack_dir = Path(home, ".sporestack")
|
||||
|
||||
# Put servers in a subdirectory
|
||||
servers_dir = sporestack_dir.joinpath("servers")
|
||||
servers_dir = SPORESTACK_DIR / "servers"
|
||||
|
||||
# Migrate existing server.json files into servers subdirectory
|
||||
if sporestack_dir.exists() and not servers_dir.exists():
|
||||
if SPORESTACK_DIR.exists() and not servers_dir.exists():
|
||||
typer.echo(
|
||||
f"Migrating server profiles found in {sporestack_dir} to {servers_dir}.",
|
||||
f"Migrating server profiles found in {SPORESTACK_DIR} to {servers_dir}.",
|
||||
err=True,
|
||||
)
|
||||
servers_dir.mkdir()
|
||||
for json_file in sporestack_dir.glob("*.json"):
|
||||
json_file.rename(servers_dir.joinpath(json_file.name))
|
||||
for json_file in SPORESTACK_DIR.glob("*.json"):
|
||||
json_file.rename(servers_dir / json_file.name)
|
||||
|
||||
# Make it, if it doesn't exist already.
|
||||
sporestack_dir.mkdir(exist_ok=True)
|
||||
SPORESTACK_DIR.mkdir(exist_ok=True)
|
||||
servers_dir.mkdir(exist_ok=True)
|
||||
|
||||
return servers_dir
|
||||
|
||||
|
||||
def token_path() -> Path:
|
||||
token_dir = SPORESTACK_DIR / "tokens"
|
||||
|
||||
# Make it, if it doesn't exist already.
|
||||
token_dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
return token_dir
|
||||
|
||||
|
||||
def save_machine_info(machine_info: Dict[str, Any], overwrite: bool = False) -> None:
|
||||
"""
|
||||
Save info to disk.
|
||||
|
@ -304,7 +254,7 @@ def save_machine_info(machine_info: Dict[str, Any], overwrite: bool = False) ->
|
|||
os.umask(0o0077)
|
||||
directory = server_info_path()
|
||||
hostname = machine_info["vm_hostname"]
|
||||
json_file = directory.joinpath(f"{hostname}.json")
|
||||
json_file = directory / f"{hostname}.json"
|
||||
if overwrite is False:
|
||||
assert json_file.exists() is False, f"{json_file} already exists."
|
||||
json_file.write_text(json.dumps(machine_info))
|
||||
|
@ -315,7 +265,7 @@ def get_machine_info(hostname: str) -> Dict[str, Any]:
|
|||
Get info from disk.
|
||||
"""
|
||||
directory = server_info_path()
|
||||
json_file = directory.joinpath(f"{hostname}.json")
|
||||
json_file = directory / f"{hostname}.json"
|
||||
if not json_file.exists():
|
||||
raise ValueError(f"{hostname} does not exist in {directory} as {json_file}")
|
||||
machine_info = json.loads(json_file.read_bytes())
|
||||
|
@ -343,8 +293,8 @@ def pretty_machine_info(info: Dict[str, Any]) -> str:
|
|||
return msg
|
||||
|
||||
|
||||
@cli.command()
|
||||
def list() -> None:
|
||||
@server_cli.command(name="list")
|
||||
def server_list() -> None:
|
||||
"""
|
||||
List all locally known servers.
|
||||
"""
|
||||
|
@ -386,7 +336,7 @@ def machine_exists(hostname: str) -> bool:
|
|||
return server_info_path().joinpath(f"{hostname}.json").exists()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def get_attribute(hostname: str, attribute: str) -> None:
|
||||
"""
|
||||
Returns an attribute about the VM.
|
||||
|
@ -395,7 +345,7 @@ def get_attribute(hostname: str, attribute: str) -> None:
|
|||
typer.echo(machine_info[attribute])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def info(hostname: str) -> None:
|
||||
"""
|
||||
Info on the VM
|
||||
|
@ -407,7 +357,7 @@ def info(hostname: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def start(hostname: str) -> None:
|
||||
"""
|
||||
Boots the VM.
|
||||
|
@ -418,7 +368,7 @@ def start(hostname: str) -> None:
|
|||
typer.echo(f"{hostname} started.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def stop(hostname: str) -> None:
|
||||
"""
|
||||
Immediately kills the VM.
|
||||
|
@ -429,7 +379,7 @@ def stop(hostname: str) -> None:
|
|||
typer.echo(f"{hostname} stopped.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def delete(hostname: str) -> None:
|
||||
"""
|
||||
Deletes the VM before expiration (no refunds/credits)
|
||||
|
@ -442,7 +392,7 @@ def delete(hostname: str) -> None:
|
|||
typer.echo(f"{hostname} was deleted.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@server_cli.command()
|
||||
def rebuild(hostname: str) -> None:
|
||||
"""
|
||||
Rebuilds the VM with the operating system and SSH key given at launch time.
|
||||
|
@ -455,9 +405,38 @@ def rebuild(hostname: str) -> None:
|
|||
typer.echo(f"{hostname} rebuilding.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def settlement_token_enable(
|
||||
token: str,
|
||||
def load_token(token: str) -> str:
|
||||
token_file = token_path().joinpath(f"{token}.json")
|
||||
if not token_file.exists():
|
||||
msg = f"Token '{token}' ({token_file}) does not exist. Create it with:\n"
|
||||
msg += f"sporestack token create {token} --dollars 20 --currency xmr\n"
|
||||
msg += "(Can do more than $20, or a different currency, like btc.)\n"
|
||||
msg += (
|
||||
"With the token credited, you can launch servers, renew existing ones, etc."
|
||||
)
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
token_data = json.loads(token_file.read_text())
|
||||
assert token_data["version"] == 1
|
||||
assert isinstance(token_data["key"], str)
|
||||
return token_data["key"]
|
||||
|
||||
|
||||
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!"
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
token_data = {"version": TOKEN_VERSION, "name": token, "key": key}
|
||||
token_file.write_text(json.dumps(token_data))
|
||||
|
||||
|
||||
@token_cli.command(name="create")
|
||||
def token_create(
|
||||
token: str = typer.Argument(DEFAULT_TOKEN),
|
||||
dollars: int = typer.Option(...),
|
||||
currency: str = typer.Option(...),
|
||||
) -> None:
|
||||
|
@ -466,9 +445,18 @@ def settlement_token_enable(
|
|||
|
||||
Dollars is starting balance.
|
||||
"""
|
||||
from . import utils
|
||||
|
||||
_token = utils.random_token()
|
||||
|
||||
typer.echo(f"Generated key {_token} for use with token {token}", err=True)
|
||||
|
||||
if Path(SPORESTACK_DIR / "tokens" / f"{token}.json").exists():
|
||||
typer.echo("Token already created! Did you mean to `topup`?", err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
response = api_client.token_enable(
|
||||
token=token,
|
||||
token=_token,
|
||||
dollars=dollars,
|
||||
currency=currency,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
|
@ -489,29 +477,42 @@ def settlement_token_enable(
|
|||
# Waiting for payment to set in.
|
||||
time.sleep(10)
|
||||
response = api_client.token_enable(
|
||||
token=token,
|
||||
token=_token,
|
||||
dollars=dollars,
|
||||
currency=currency,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
if response.payment.paid is True:
|
||||
typer.echo(
|
||||
f"{token} has been enabled with ${dollars}. Save it and don't lose it!"
|
||||
)
|
||||
typer.echo(f"{token} has been enabled with ${dollars}.")
|
||||
typer.echo(f"{token}'s key is {_token}.")
|
||||
typer.echo("Save it, don't share it, and don't lose it!")
|
||||
save_token(token, _token)
|
||||
return
|
||||
raise ValueError(f"{token} did not get enabled in time.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def settlement_token_add(
|
||||
token: str,
|
||||
@token_cli.command(name="import")
|
||||
def token_import(
|
||||
name: str = typer.Argument(DEFAULT_TOKEN),
|
||||
key: str = typer.Option(...),
|
||||
) -> None:
|
||||
"""
|
||||
Imports a token from a key.
|
||||
"""
|
||||
save_token(name, key)
|
||||
|
||||
|
||||
@token_cli.command(name="topup")
|
||||
def token_topup(
|
||||
token: str = typer.Argument(DEFAULT_TOKEN),
|
||||
dollars: int = typer.Option(...),
|
||||
currency: str = typer.Option(...),
|
||||
) -> None:
|
||||
"""
|
||||
Adds balance to an existing settlement token.
|
||||
"""
|
||||
token = load_token(token)
|
||||
|
||||
response = api_client.token_add(
|
||||
token,
|
||||
|
@ -547,25 +548,30 @@ def settlement_token_add(
|
|||
raise ValueError(f"{token} did not get enabled in time.")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def settlement_token_balance(token: str) -> None:
|
||||
@token_cli.command()
|
||||
def balance(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
|
||||
"""
|
||||
Gets balance for a settlement token.
|
||||
"""
|
||||
_token = load_token(token)
|
||||
|
||||
typer.echo(
|
||||
api_client.token_balance(token=token, api_endpoint=get_api_endpoint()).usd
|
||||
api_client.token_balance(token=_token, api_endpoint=get_api_endpoint()).usd
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def settlement_token_generate() -> None:
|
||||
@token_cli.command(name="list")
|
||||
def token_list() -> None:
|
||||
"""
|
||||
Generates a settlement token that can be enabled.
|
||||
Gets balance for a settlement token.
|
||||
"""
|
||||
from . import utils
|
||||
|
||||
typer.echo(utils.random_token())
|
||||
token_dir = token_path()
|
||||
typer.echo(f"SporeStack tokens present in {token_dir}:", err=True)
|
||||
typer.echo("(Name): (Key)", err=True)
|
||||
for token_file in token_dir.glob("*.json"):
|
||||
token = token_file.stem
|
||||
key = load_token(token)
|
||||
typer.echo(f"{token}: {key}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
|
Loading…
Reference in New Issue