Browse Source

1.3.0: Add new Tor flavors, simplify

tags/1.3.0
Teran McKinney 5 months ago
parent
commit
8301f7b144
8 changed files with 153 additions and 136 deletions
  1. +2
    -2
      setup.py
  2. +2
    -1
      sporestackv2/__init__.py
  3. +9
    -18
      sporestackv2/api_client.py
  4. +14
    -70
      sporestackv2/client.py
  5. +75
    -0
      sporestackv2/flavors.py
  6. +22
    -44
      sporestackv2/validate.py
  7. +28
    -0
      sporestackv2/validate_test.py
  8. +1
    -1
      sporestackv2/version.py

+ 2
- 2
setup.py View File

@@ -8,7 +8,7 @@ VERSION = None
with open("sporestackv2/version.py") as f:
for line in f:
if line.startswith("__version__"):
VERSION = line.replace("\"", "").split("=")[1].strip()
VERSION = line.replace('"', "").split("=")[1].strip()
break
if VERSION is None:
raise ValueError("__version__ not found in __init__.py")
@@ -27,7 +27,7 @@ KEYWORDS = [
]

setup(
python_requires=">=3.3",
python_requires=">=3.6",
name="sporestack",
version=VERSION,
author="SporeStack",


+ 2
- 1
sporestackv2/__init__.py View File

@@ -1,9 +1,10 @@
from . import api_client
from . import client
from . import flavors
from . import utilities
from . import validate
from . import version

__all__ = ["api_client", "client", "utilities", "validate"]
__all__ = ["api_client", "client", "flavors", "utilities", "validate"]

__version__ = version.__version__

+ 9
- 18
sporestackv2/api_client.py View File

@@ -152,23 +152,19 @@ def get_url(api_endpoint, host, target):
def launch(
machine_id,
days,
disk,
memory,
ipv4,
ipv6,
bandwidth,
currency,
flavor=None,
disk=None,
memory=None,
ipv4=None,
ipv6=None,
region=None,
ipxescript=None,
operating_system=None,
ssh_key=None,
organization=None,
cores=1,
managed=False,
override_code=None,
settlement_token=None,
qemuopts=None,
hostaccess=False,
api_endpoint=None,
host=None,
want_topup=False,
@@ -178,15 +174,16 @@ def launch(
):
"""
Only ipxescript or operating_system + ssh_key can be None.

flavor overrides cores, memory, etc settings.
"""
ipv4 = normalize_argument(ipv4)
ipv6 = normalize_argument(ipv6)
bandwidth = normalize_argument(bandwidth)

validate.currency(currency)
validate.flavor(flavor)
validate.ipv4(ipv4)
validate.ipv6(ipv6)
validate.bandwidth(bandwidth)
validate.cores(cores)
validate.disk(disk)
validate.memory(memory)
@@ -200,20 +197,16 @@ def launch(
json_params = {
"machine_id": machine_id,
"days": days,
"flavor": flavor,
"disk": disk,
"memory": memory,
"cores": cores,
"managed": managed,
"currency": currency,
"region": region,
"organization": organization,
"bandwidth": bandwidth,
"ipv4": ipv4,
"ipv6": ipv6,
"override_code": override_code,
"settlement_token": settlement_token,
"qemuopts": qemuopts,
"hostaccess": hostaccess,
"ipxescript": ipxescript,
"operating_system": operating_system,
"ssh_key": ssh_key,
@@ -232,7 +225,6 @@ def topup(
days,
currency,
settlement_token=None,
override_code=None,
api_endpoint=None,
host=None,
retry=False,
@@ -249,7 +241,6 @@ def topup(
"settlement_token": settlement_token,
"currency": currency,
"host": host,
"override_code": override_code,
"affiliate_amount": affiliate_amount,
"affiliate_token": affiliate_token,
}


+ 14
- 70
sporestackv2/client.py View File

@@ -7,11 +7,10 @@ Cleaner interface into api_client, for the most part.
import json
import sys

# FUTURE PYTHON 3.6: import secrets
import secrets
import os
import logging
import time
from hashlib import sha256

import aaargh
import pyqrcode
@@ -35,25 +34,13 @@ API_ENDPOINT = CLEARNET_ENDPOINT
WAITING_PAYMENT_TO_PROCESS = "Waiting for payment to process..."


def i_am_root():
if os.getuid() == 0:
return True
else:
return False


def random_machine_id():
"""
Makes a random Machine ID.

These can also be deterministic, but then it wouldn't be called "random".
"""
# We could also use secrets.token_hex(32),
# this may be more secure.
# FUTURE PYTHON 3.6: random = secrets.token_bytes(64)
random = os.urandom(64)
machine_id = sha256(random).hexdigest()
return machine_id
return secrets.token_hex(32)


def make_payment(currency, uri, usd=None, walkingliberty_wallet=None):
@@ -80,21 +67,17 @@ Press ctrl+c to abort."""
@cli.cmd
@cli.cmd_arg("vm_hostname")
@cli.cmd_arg("--host", type=str, default=None)
@cli.cmd_arg("--hostaccess", type=bool, default=False)
@cli.cmd_arg("--save", type=bool, default=True)
@cli.cmd_arg("--override_code", type=str, default=None)
@cli.cmd_arg("--region", type=str, default=None)
@cli.cmd_arg("--managed", type=bool, default=False)
@cli.cmd_arg("--currency", type=str, default=None)
@cli.cmd_arg("--settlement_token", type=str, default=None)
@cli.cmd_arg("--flavor", type=str, default=None)
@cli.cmd_arg("--cores", type=int, default=1)
@cli.cmd_arg("--memory", type=int, default=1)
@cli.cmd_arg("--bandwidth", type=int, default=1)
@cli.cmd_arg("--ipv4", default="/32")
@cli.cmd_arg("--ipv6", default="/128")
@cli.cmd_arg("--disk", type=int, default=5)
@cli.cmd_arg("--days", type=int, required=True)
@cli.cmd_arg("--qemuopts", type=str, default=None)
@cli.cmd_arg("--walkingliberty_wallet", type=str, default=None)
@cli.cmd_arg("--api_endpoint", type=str, default=API_ENDPOINT)
@cli.cmd_arg("--want_topup", type=bool, default=False)
@@ -110,22 +93,18 @@ Press ctrl+c to abort."""
def launch(
vm_hostname,
days,
disk,
memory,
ipv4,
ipv6,
bandwidth,
flavor=None,
disk=None,
memory=None,
ipv4=None,
ipv6=None,
host=None,
api_endpoint=API_ENDPOINT,
cores=1,
currency="bch",
currency=None,
region=None,
managed=False,
organization=None,
override_code=None,
settlement_token=None,
qemuopts=None,
hostaccess=False,
ipxescript=None,
ipxescript_stdin=False,
ipxescript_file=None,
@@ -140,10 +119,11 @@ def launch(
):
"""
Attempts to launch a server.

Flavor overrides cores, memory, etc settings.
"""
ipv4 = api_client.normalize_argument(ipv4)
ipv6 = api_client.normalize_argument(ipv6)
bandwidth = api_client.normalize_argument(bandwidth)
want_topup = api_client.normalize_argument(want_topup)
ipxescript_stdin = api_client.normalize_argument(ipxescript_stdin)

@@ -177,12 +157,6 @@ def launch(
with open(ipxescript_file) as fp:
ipxescript = fp.read()

# FIXME: Hacky.
if host == "127.0.0.1":
if walkingliberty_wallet is None and settlement_token is None:
if override_code is None:
override_code = get_override_code()

machine_id = random_machine_id()

def create_vm(host):
@@ -191,6 +165,7 @@ def launch(
host=host,
machine_id=machine_id,
days=days,
flavor=flavor,
disk=disk,
memory=memory,
ipxescript=ipxescript,
@@ -199,15 +174,10 @@ def launch(
cores=cores,
ipv4=ipv4,
ipv6=ipv6,
bandwidth=bandwidth,
currency=currency,
region=region,
organization=organization,
managed=managed,
override_code=override_code,
settlement_token=settlement_token,
qemuopts=qemuopts,
hostaccess=hostaccess,
api_endpoint=api_endpoint,
want_topup=want_topup,
affiliate_amount=affiliate_amount,
@@ -277,7 +247,6 @@ def launch(

@cli.cmd
@cli.cmd_arg("vm_hostname")
@cli.cmd_arg("--override_code", type=str, default=None)
@cli.cmd_arg("--currency", type=str, default=None)
@cli.cmd_arg("--settlement_token", type=str, default=None)
@cli.cmd_arg("--days", type=int, required=True)
@@ -289,7 +258,6 @@ def topup(
vm_hostname,
days,
currency,
override_code=None,
settlement_token=None,
walkingliberty_wallet=None,
api_endpoint=None,
@@ -313,20 +281,12 @@ def topup(
if api_endpoint is None:
api_endpoint = machine_info["api_endpoint"]

if api_endpoint is None:
# FIXME: Hacky.
if host == "127.0.0.1":
if walkingliberty_wallet is None and settlement_token is None:
if override_code is None:
override_code = get_override_code()

def topup_vm():
return api_client.topup(
host=host,
machine_id=machine_id,
days=days,
currency=currency,
override_code=override_code,
api_endpoint=api_endpoint,
settlement_token=settlement_token,
affiliate_amount=affiliate_amount,
@@ -369,10 +329,7 @@ def topup(


def machine_info_directory():
if i_am_root():
directory = "/etc/sporestackv2"
else:
directory = os.path.join(os.getenv("HOME"), ".sporestackv2")
directory = os.path.join(os.getenv("HOME"), ".sporestackv2")
return directory


@@ -488,7 +445,7 @@ def remove_cli(vm_hostname):

def machine_exists(vm_hostname):
"""
Check if the VM exists locally in /etc/sporestackv2 or ~/.sporestackv2
Check if the VM's JSON exists locally.
"""
directory = machine_info_directory()
file_path = os.path.join(directory, "{}.json".format(vm_hostname))
@@ -509,19 +466,6 @@ def get_attribute(vm_hostname, attribute):
return machine_info[attribute]


def get_override_code():
"""
Attempts to procure the override code for
localhost spawns.
"""
try:
with open("/etc/vmmanagement.override_code") as fp:
override_code = fp.read().strip("\n")
except Exception:
override_code = None
return override_code


@cli.cmd
@cli.cmd_arg("vm_hostname")
@cli.cmd_arg("--api_endpoint", type=str, default=None)


+ 75
- 0
sporestackv2/flavors.py View File

@@ -0,0 +1,75 @@
from typing import NamedTuple


class Flavor(NamedTuple):
# Unique string to identify the flavor that's sort of human readable.
slug: str
# Number of vCPU cores the server is given.
cores: int
# Memory in Megabytes
memory: int
# Disk in Gigabytes
disk: int
# USD cents per day
price: int
# IPv4 connectivity: "/32" or "tor". If "tor", needs to match IPv6 setting.
ipv4: str
# IPv6 connectivity: "/128" (may act as a /64, may not) or tor. If "tor", needs to match IPv4 setting.
ipv6: str
# Gigabytes of bandwidth per day
bandwidth: int


# This one is too small to work reliably with iPXE.
# "tor-512": Flavor(
# slug="tor-512",
# cores=1,
# memory=512,
# disk=4,
# price=14,
# ipv4="tor",
# ipv6="tor",
# bandwidth=10,
# ),
all_sporestack_flavors = {
"tor-1024": Flavor(
slug="tor-1024",
cores=1,
memory=1024,
disk=8,
price=28,
ipv4="tor",
ipv6="tor",
bandwidth=20,
),
"tor-2048": Flavor(
slug="tor-2048",
cores=1,
memory=2048,
disk=16,
price=56,
ipv4="tor",
ipv6="tor",
bandwidth=40,
),
"tor-3072": Flavor(
slug="tor-3072",
cores=1,
memory=3072,
disk=24,
price=84,
ipv4="tor",
ipv6="tor",
bandwidth=60,
),
"tor-4096": Flavor(
slug="tor-4096",
cores=1,
memory=4096,
disk=32,
price=112,
ipv4="tor",
ipv6="tor",
bandwidth=80,
),
}

+ 22
- 44
sporestackv2/validate.py View File

@@ -53,6 +53,26 @@ def operating_system(operating_system):
return True


def flavor(flavor):
"""
Validates an flavor argument.
"""
if flavor is None:
return True
if not isinstance(flavor, str):
raise TypeError("flavor must be null or a string.")
if len(flavor) < 1:
raise ValueError("flavor must have at least one letter.")
if len(flavor) > 16:
raise ValueError("flavor must have 16 letters or less.")
for character in flavor:
if character not in string.ascii_lowercase + string.digits + "-":
msg = "flavor must only contain a-z, digits, -"
raise ValueError(msg)

return True


def ssh_key(ssh_key):
"""
Validates an ssh_key argument.
@@ -75,8 +95,8 @@ def days(days, zero_allowed=False):
"""
Makes sure our argument is valid.
0-28
Keep in mind that 0 days implies no expiration and is
only allowed if override_code is set.
Keep in mind that 0 days implies no expiration,
may be used in the future with settlement tokens.

0 is invalid unless zero_allowed is True.
"""
@@ -160,35 +180,6 @@ def cores(cores):
return True


def qemuopts(qemuopts):
"""
Makes sure qemuopts is valid.
"""
if qemuopts is None:
return True
if not isinstance(qemuopts, str):
raise TypeError("qemuopts must be none or str.")
return True


def managed(managed):
"""
Makes sure managed is valid.
"""
if not isinstance(managed, bool):
raise TypeError("managed must be a boolean.")
return True


def hostaccess(hostaccess):
"""
Makes sure hostaccess is valid.
"""
if not isinstance(hostaccess, bool):
raise TypeError("hostaccess must be a boolean.")
return True


def currency(currency):
"""
Makes sure currency is valid.
@@ -200,19 +191,6 @@ def currency(currency):
return True


def refund_address(refund_address):
"""
Makes sure refund_address is valid.

Currently not implemented.
"""
if refund_address is None:
return True
if not isinstance(refund_address, str):
raise TypeError("refund_address must be none or str.")
return True


def bandwidth(bandwidth):
"""
Bandwidth is in gigabytes per day.


+ 28
- 0
sporestackv2/validate_test.py View File

@@ -182,6 +182,34 @@ def test_operating_system():
validate.operating_system("_")


def test_flavor():
assert validate.flavor("debian-9") is True
assert validate.flavor("debian-10") is True
assert validate.flavor("ubuntu-16-04") is True
assert validate.flavor("something-else") is True
assert validate.flavor(None) is True

# Shortest valid.
assert validate.flavor("a") is True
# Longest valid
valid = validate.flavor(string.ascii_lowercase[:16])
assert valid is True

with pytest.raises(TypeError):
validate.flavor(1)
with pytest.raises(TypeError):
validate.flavor(0)
# Too short.
with pytest.raises(ValueError):
validate.flavor("")
# One too long.
with pytest.raises(ValueError):
validate.flavor(string.ascii_lowercase[:17])
# Bad character.
with pytest.raises(ValueError):
validate.flavor("_")


valid_ssh_key = (
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLuFASoTgo5r/bBGcawcN"
"7B/NTyjmEi3cdgl8r3ldZXVXl6N/7vfF/O6ggkU1iJCHUgxOqGHzJMyJ3ZL"


+ 1
- 1
sporestackv2/version.py View File

@@ -1 +1 @@
__version__ = "1.2.3"
__version__ = "1.3.0"

Loading…
Cancel
Save