starting to build console controller and views

This commit is contained in:
forest 2020-05-11 01:47:14 -05:00
parent 7932db90d5
commit 18e6a1b141
18 changed files with 345 additions and 89 deletions

View File

@ -1,8 +1,10 @@
import os
from flask import Flask from flask import Flask
from flask_mail import Mail from flask_mail import Mail
from flask import render_template from flask import render_template
import os
from capsulflask import virt_model
def create_app(): def create_app():
app = Flask(__name__) app = Flask(__name__)
@ -21,6 +23,7 @@ def create_app():
) )
app.config['FLASK_MAIL_INSTANCE'] = Mail(app) app.config['FLASK_MAIL_INSTANCE'] = Mail(app)
app.config['VIRTUALIZATION_MODEL'] = virt_model.MockVirtualization()
from capsulflask import db from capsulflask import db
@ -29,8 +32,8 @@ def create_app():
from capsulflask import auth, landing, console from capsulflask import auth, landing, console
app.register_blueprint(landing.bp) app.register_blueprint(landing.bp)
app.register_blueprint(auth.bp) app.register_blueprint(auth.bp)
app.register_blueprint(console.bp)
app.add_url_rule("/", endpoint="index") app.add_url_rule("/", endpoint="index")

View File

@ -71,11 +71,11 @@ def login():
@bp.route("/magic/<string:token>", methods=("GET", )) @bp.route("/magic/<string:token>", methods=("GET", ))
def magiclink(token): def magiclink(token):
email = get_model().consumeToken(token) email = get_model().consume_token(token)
if email is not None: if email is not None:
session.clear() session.clear()
session["account"] = email session["account"] = email
return redirect(url_for("index")) return redirect(url_for("console.index"))
else: else:
abort(404, f"Token {token} doesn't exist or has already been used.") abort(404, f"Token {token} doesn't exist or has already been used.")

View File

@ -9,8 +9,69 @@ from flask import session
from flask import render_template from flask import render_template
from flask_mail import Message from flask_mail import Message
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from nanoid import generate
from capsulflask.auth import account_required
from capsulflask.db import get_model from capsulflask.db import get_model
bp = Blueprint("console", __name__, url_prefix="/console") bp = Blueprint("console", __name__, url_prefix="/console")
def makeCapsulId():
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
return f"capsul-{lettersAndNumbers}"
@bp.route("/")
@account_required
def index():
return render_template("console.html", vms=get_model().list_vms_for_account(session["account"]))
@bp.route("/create", methods=("GET", "POST"))
@account_required
def create():
db_model = get_model()
vm_sizes = db_model.vm_sizes_dict()
operating_systems = db_model.operating_systems_dict()
ssh_public_keys = db_model.list_ssh_public_keys_for_account(session["account"])
error = None
if request.method == "POST":
size = request.form["size"]
os = request.form["os"]
if not size:
error = "Size is required"
elif size not in vm_sizes:
error = f"Invalid size {size}"
if not os:
error = "OS is required"
elif os not in operating_systems:
error = f"Invalid os {os}"
if error is None:
id = makeCapsulId()
db_model.create_vm(
email=session["account"],
id=id,
size=size,
os=os
)
current_app.config["VIRTUALIZATION_MODEL"].create(
email = session["account"],
id=id,
template_image_file_name=operating_systems[os].template_image_file_name,
vcpus=vm_sizes[size].vcpus,
memory=vm_sizes[size].memory
)
return render_template(
"create.html",
ssh_public_keys=ssh_public_keys,
operating_systems=operating_systems,
vm_sizes=vm_sizes
)
@bp.route("/billing")
@account_required
def faq():
return render_template("billing.html")

View File

@ -22,7 +22,7 @@ class DBModel:
return token return token
def consumeToken(self, token): def consume_token(self, token):
self.cursor.execute("SELECT email FROM login_tokens WHERE token = %s", (token, )) self.cursor.execute("SELECT email FROM login_tokens WHERE token = %s", (token, ))
rows = self.cursor.fetchall() rows = self.cursor.fetchall()
if len(rows) > 0: if len(rows) > 0:
@ -32,7 +32,52 @@ class DBModel:
return email return email
return None return None
def allVmIds(self,): def all_vm_ids(self,):
self.cursor.execute("SELECT id FROM vms") self.cursor.execute("SELECT id FROM vms")
return map(lambda x: x[0], self.cursor.fetchall()) return map(lambda x: x[0], self.cursor.fetchall())
def operating_systems_dict(self,):
self.cursor.execute("SELECT id, template_image_file_name, description FROM os_images")
operatingSystems = dict()
for row in self.cursor.fetchall():
operatingSystems[row[0]] = dict(template_image_file_name=row[1], description=row[2])
return operatingSystems
def vm_sizes_dict(self,):
self.cursor.execute("SELECT id, dollars_per_month, vcpus, memory_mb, bandwidth_gb_per_month FROM vm_sizes")
vmSizes = dict()
for row in self.cursor.fetchall():
vmSizes[row[0]] = dict(dollars_per_month=row[1], vcpus=row[2], memory_mb=row[3], bandwidth_gb_per_month=row[4])
return vmSizes
def list_ssh_public_keys_for_account(self, email):
self.cursor.execute("SELECT name, content FROM ssh_public_keys WHERE email = %s", (email, ))
return map(
lambda x: dict(name=x[0], content=x[1]),
self.cursor.fetchall()
)
def list_vms_for_account(self, email):
self.cursor.execute("""
SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, os_images.description, vms.created, vms.deleted
FROM vms JOIN os_images on os_images.id = vms.os
WHERE vms.email = %s""",
(email, )
)
return map(
lambda x: dict(id=x[0], ipv4=x[1], ipv6=x[2], size=x[3], os=x[4], created=x[5], deleted=x[6]),
self.cursor.fetchall()
)
def create_vm(self, email, id, size, os):
self.cursor.execute("""
INSERT INTO vms (email, id, size, os)
VALUES (%s, %s, %s, %s)
""",
(email, id, size, os)
)
self.connection.commit()

View File

@ -20,11 +20,10 @@ CREATE TABLE vm_sizes (
); );
CREATE TABLE ssh_public_keys ( CREATE TABLE ssh_public_keys (
id SERIAL PRIMARY KEY,
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT, email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
name TEXT NOT NULL, name TEXT NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
UNIQUE (id, email) PRIMARY KEY (email, name)
); );
CREATE TABLE vms ( CREATE TABLE vms (
@ -40,12 +39,12 @@ CREATE TABLE vms (
); );
CREATE TABLE vm_ssh_public_key ( CREATE TABLE vm_ssh_public_key (
ssh_public_key_id INTEGER NOT NULL, ssh_public_key_name TEXT NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
vm_id TEXT NOT NULL, vm_id TEXT NOT NULL,
FOREIGN KEY (email, ssh_public_key_id) REFERENCES ssh_public_keys(email, id) ON DELETE RESTRICT, FOREIGN KEY (email, ssh_public_key_name) REFERENCES ssh_public_keys(email, name) ON DELETE RESTRICT,
FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE RESTRICT, FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE RESTRICT,
PRIMARY KEY (email, vm_id, ssh_public_key_id) PRIMARY KEY (email, vm_id, ssh_public_key_name)
); );
CREATE TABLE payments ( CREATE TABLE payments (
@ -63,11 +62,11 @@ CREATE TABLE login_tokens (
); );
INSERT INTO os_images (id, template_image_file_name, description) INSERT INTO os_images (id, template_image_file_name, description)
VALUES ('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'), VALUES ('alpine311', 'alpine-cloud-2020-04-18.qcow2', 'Alpine Linux 3.11'),
('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic Beaver)'),
('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'),
('centos7', 'CentOS-7-x86_64-GenericCloud.qcow2', 'CentOS 7'), ('centos7', 'CentOS-7-x86_64-GenericCloud.qcow2', 'CentOS 7'),
('centos8', 'CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2', 'CentOS 8'), ('centos8', 'CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2', 'CentOS 8'),
('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic Beaver)'),
('alpine311', 'alpine-cloud-2020-04-18.qcow2', 'Alpine Linux 3.11'),
('openbsd66', 'openbsd-cloud-2020-05.qcow2', 'OpenBSD 6.6'), ('openbsd66', 'openbsd-cloud-2020-05.qcow2', 'OpenBSD 6.6'),
('guix110', 'guixsystem-cloud-2020-05.qcow2', 'Guix System 1.1.0'); ('guix110', 'guixsystem-cloud-2020-05.qcow2', 'Guix System 1.1.0');

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

View File

@ -26,15 +26,14 @@ a:hover, a:active, a:visited {
color: #b5bd68; color: #b5bd68;
} }
nav .nav-row { .nav-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 2rem 0;
} }
nav .nav-row:last-child { .nav-row:last-child {
justify-content: center; justify-content: center;
} }
nav .nav-row:last-child a, nav .nav-row:last-child div { .nav-row:last-child a, .nav-row:last-child div {
margin: 0 1em; margin: 0 1em;
} }
@ -52,37 +51,77 @@ main {
border: 1px dashed #bdc7b8; border: 1px dashed #bdc7b8;
padding: 1rem; padding: 1rem;
margin-bottom: 2em; margin-bottom: 2em;
}
.hero {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.single-content { .full-margin {
margin: 2rem 0; margin: 3rem 0;
}
.half-margin {
margin: 1.5rem 0;
}
.third-margin {
margin: 1rem 0;
}
.row {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
}
.row.wrap {
flex-wrap: wrap;
}
.row.justify-start {
justify-content: flex-start;
}
.row.justify-end {
justify-content: flex-end;
} }
form { form {
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
align-items: center; align-items: flex-start;
justify-content: space-around;
} }
input, label { label.align {
min-width: 10em;
}
input, select, label {
margin: 0.5em; margin: 0.5em;
} }
input { input, select {
background: none;
outline: 0; outline: 0;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border-radius: 0.5em; border-radius: 0.5em;
color: #bdc7b8; color: #bdc7b8;
} }
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/*
re-generate the following line from the source image with:
echo "background-image: url(data:image/png;base64,$(cat capsulflask/static/dropdown-handle.png | base64 -w 0));"
*/
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA9hAAAPYQGoP6dpAAACfElEQVRYw+2WSWgVQRCGp3tmHmLEuKEEL3owguJBjBcD8eJJiCABT4IrOQiKC6jnoJ6CYBDiQkTxiQiC4nKIntR4E70JUQQ9KahIUAg6m1/FntBvXpYZE6PCFPxUTc90/dW19HuOU0op/4K8GnzcOMN8s8BCsbVZO8hCO1AzQN6EugJa7QCWguvgMB/4f5B8DeqO73vb0JEdwBetVYPnud3Yl0yU003egep3XbclCEInG8C3OE6cMIwc3/e383yXDWuniViDI5J2rXVTFEXpq9gO4Gu6GgSB43neOsyHbNwFpkK+AHWeU3dD3hDHsf06sQO4DZ6lUYVh6CilpEvPgTNpxxYgVmA15i3KuldObZGL8QQ8Hw2geWXbW9RWMECkv8JLEgmiQvQHeLyGw+YCMWwC98hkm5Q1Fdcd8d0POuD8LA8qE/kic+otYHQafM9zgjB8jXkIPGBzMN58o/aAExxkXiblP8ANsJ/9Q+mitr/gxSeUNOHVNBMjfUFJOM0KzJviACJvDPI5QgzOZsnJpKiLYLdNXpcBy1kF1WVOXKnZgDPKU8U8Ct6b5WWgh3q32yk38h2cAichr3upJmmmYyaQmiC4SJiW8AVmJ5Bs9DG+q2SCMjIMjkPcMx6HytHRUtPTYK69TnM6dPcHKSPNtTiK6kZsyNS7OpF/lXOsZEL6qO18u7Zpn2TXeJZe2gn5/cl8qwKzvRF12dR7InkDdkD+NI9fnTcAHD4yd8Wg9EBWzNpL+SYveaEMWJlYjqoyDBuSpGYyBmSEIX9XxJ/6zTt+CeoC2GwaTmrdCfnHor7UFH5oZqN6zd2+D/Lhv/FXbj1oKf/UllLKfy0/ATtM/c/kKrmhAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: bottom 0.65em right 0.8em;
background-size: 0.5em;
}
input {
background: none;
}
input[type=text] { input[type=text] {
font: calc(0.40rem + 1vmin) monospace; font: calc(0.40rem + 1vmin) monospace;
border: 0; border: 0;
@ -91,14 +130,14 @@ input[type=text] {
outline: 0; outline: 0;
} }
input[type=submit] { input[type=submit], select {
font: calc(0.40rem + 1vmin) monospace; font: calc(0.40rem + 1vmin) monospace;
cursor: pointer; cursor: pointer;
border: 1px solid #777e73; border: 1px solid #777e73;
background: #bdc7b810; background-color: #bdc7b810;
} }
h1, h2, h3, h4, h5 { h1, h2, h3, h4, h5 {
font-size:calc(0.40rem + 1vmin); font-size:calc(0.40rem + 1vmin);
margin: initial; margin: initial;

View File

@ -9,7 +9,7 @@
</head> </head>
<body> <body>
<nav> <nav>
<div class="nav-row"> <div class="nav-row half-margin">
<div> <div>
<a href="/"><b>Capsul</b></a>💊 <a href="/"><b>Capsul</b></a>💊
</div> </div>
@ -21,9 +21,15 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="nav-row"> <div class="nav-row half-margin">
<a href="/faq">FAQ</a> <a href="/faq">FAQ</a>
<a href="/changelog">Changelog</a> <a href="/changelog">Changelog</a>
{% if session["account"] %}
<a href="/console/">Console</a>
<a href="/console/billing">Billing</a>
{% endif %}
<a href="/support">Support</a> <a href="/support">Support</a>
</div> </div>
</nav> </nav>

View File

@ -3,9 +3,7 @@
{% block title %}Changelog{% endblock %} {% block title %}Changelog{% endblock %}
{% block content %} {% block content %}
<div class="hero"> <div class="full-margin"><h1>CHANGELOG</h1></div>
<div class="single-content"><h1>CHANGELOG</h1></div>
</div>
{% endblock %} {% endblock %}
{% block subcontent %} {% block subcontent %}
<p> <p>

View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block title %}Console{% endblock %}
{% block content %}
<div class="third-margin">
<h1>CONSOLE</h1>
</div>
<div class="third-margin">
<div class="nav-row">
<a href="/console">Capsuls</a>
<a href="/console/ssh">SSH Public Keys</a>
<a href="/console/billing">Billing</a>
</div>
</div>
<div class="third-margin">
{% if vms[0] is defined %}
<table>
<thead>
<tr>
<th>id</th>
<th>size</th>
<th>ipv4</th>
<th>os</th>
<th>created</th>
</tr>
</thead>
<tbody>
{% for vm in vms %}
<a href="/console/{{ vm['id'] }}">
<tr>
<td>{{ vm["id"] }}</td>
<td>{{ vm["size"] }}</td>
<td>{{ vm["ipv4"] }}</td>
<td>{{ vm["os"] }}</td>
<td>{{ vm["created"] }}</td>
</tr>
</a>
{% endfor %}
</tbody>
</table>
{% else %}
You don't have any Capsuls running. <a href="/console/create">Create one</a> today!
{% endif %}
</div>
{% endblock %}
{% block pagesource %}/templates/console.html{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends 'base.html' %}
{% block title %}Create{% endblock %}
{% block content %}
<div class="half-margin">
<h1>CONSOLE - CREATE CAPSUL</h1>
</div>
<div class="half-margin">
{% if ssh_public_keys[0] is defined %}
<pre>
CAPSUL SIZES
============
type monthly cpus mem ssd net*
----- ------- ---- --- --- ---
f1-s $5.33 1 512M 25G .5TB
f1-m $7.16 1 1024M 25G 1TB
f1-l $8.92 1 2048M 25G 2TB
f1-x $16.16 2 4096M 25G 4TB
f1-xx $29.66 4 8096M 25G 8TB
f1-xxx $57.58 8 16G 25G 16TB
* net is calculated as a per-month average
* all VMs come standard with one public IPv4 addr</pre>
</div>
<form method="post">
<div class="row justify-start">
<label class="align" for="size">Capsul Size</label>
<select id="size" name="size">
{% for size in vm_sizes.keys() %}<option value="{{ size }}">{{ size }}</option>{% endfor %}
</select>
</div>
<div class="row justify-start">
<label class="align" for="os">Operating System</label>
<select id="os" name="os">
{% for os_id, os in operating_systems.items() %}
<option value="{{ os_id }}">{{ os.description }}</option>
{% endfor %}
</select>
</div>
<div class="row justify-end">
<input type="submit" value="Create">
</div>
</form>
{% else %}
<p>You don't have any ssh public keys yet.</p>
<p>You must <a href="/console/ssh/upload">upload one</a> before you can create a Capsul.</p>
{% endif %}
{% endblock %}
{% block subcontent %}
{% if ssh_public_keys[0] is defined %}
<p>
Using our services implies that you agree to our terms of service & privacy policy.
</p>
<ul>
<li>
<a href="https://git.cyberia.club/congress/terms-of-service/plain/README">git.cyberia.club/congress/terms-of-service</a>
</li>
<li>
<a href="https://git.cyberia.club/congress/privacy-policy/plain/README">git.cyberia.club/congress/privacy-policy</a>
</li>
</ul>
{% endif %}
{% endblock %}
{% block pagesource %}/templates/console.html{% endblock %}

View File

@ -3,9 +3,7 @@
{% block title %}FAQ{% endblock %} {% block title %}FAQ{% endblock %}
{% block content %} {% block content %}
<div class="hero"> <div class="full-margin"><h1>Frequently Asked Questions</h1></div>
<div class="single-content"><h1>Frequently Asked Questions</h1></div>
</div>
{% endblock %} {% endblock %}
{% block subcontent %} {% block subcontent %}

View File

@ -2,7 +2,6 @@
{% block content %} {% block content %}
<div class="hero">
<h1>CAPSUL</h1> <h1>CAPSUL</h1>
<pre> <pre>
.-. .-.
@ -13,20 +12,19 @@
\ / \ /
`"` `"`
</pre> </pre>
<span>Simple, fast, private compute by <a href="https://cyberia.club">https://cyberia.club</a></span> <span>Simple, fast, private compute by <a href="https://cyberia.club">cyberia.club</a></span>
</div>
{% endblock %} {% endblock %}
{% block subcontent %} {% block subcontent %}
<p> <p>
<ul> <ul>
<li>Simply log in with your email address </li> <li>Low friction: simply log in with your email address and fund your account with Credit/Debit or Cryptocurrency</li>
<li>All root disks are backed up at no charge</li> <li>All root disks are backed up at no charge</li>
<li>All storage is fast, local, and solid-state</li> <li>All storage is fast, local, and solid-state</li>
<li>All network connections are low latency</li> <li>All network connections are low latency</li>
<li>Supported by amazing volunteers from Cyberia</li> <li>Supported by amazing volunteers from Cyberia</li>
<li>Upfront prices, no confusing billing</li> <li>Upfront prices, no confusing billing</li>
<li>A Minnesota non-profit organization that will never exploit you</li> <li>Operated by a Minnesota non-profit organization that will never exploit you</li>
<li>We donate a portion of our proceeds to likeminded hacker groups around the globe</li> <li>We donate a portion of our proceeds to likeminded hacker groups around the globe</li>
</ul> </ul>
</p> </p>

View File

@ -3,9 +3,7 @@
{% block title %}check your email{% endblock %} {% block title %}check your email{% endblock %}
{% block content %} {% block content %}
<div class="hero"> <div class="full-margin">Check your email. A login link has been sent to {{ email }}</div>
<div class="single-content">check your email. a login link has been sent to {{ email }}</div>
</div>
{% endblock %} {% endblock %}
{% block pagesource %}/templates/login-landing.html{% endblock %} {% block pagesource %}/templates/login-landing.html{% endblock %}

View File

@ -3,14 +3,16 @@
{% block title %}login{% endblock %} {% block title %}login{% endblock %}
{% block content %} {% block content %}
<div class="hero"> <div class="half-margin">
<form method="post" class="single-content"> <h1>LOGIN</h1>
<label for="email">Email Address</label> </div>
<input type="text" name="email" id="email" required> <form method="post" class="half-margin">
<input type="submit" value="Log In"> <div class="row wrap">
<label for="email">Email Address</label>
<input type="text" name="email" id="email" required>
<input type="submit" value="Log In">
</div>
</form> </form>
</div>
{% endblock %} {% endblock %}
{% block pagesource %}/templates/login.html{% endblock %} {% block pagesource %}/templates/login.html{% endblock %}

View File

@ -3,15 +3,12 @@
{% block title %}Support{% endblock %} {% block title %}Support{% endblock %}
{% block content %} {% block content %}
<div class="hero"> <div class="half-margin">
<div class="single-content">
<h1>SUPPORT</h1> <h1>SUPPORT</h1>
</div> </div>
<div class="half-margin">
<div class="single-content"> <a href="mailto:support@cyberia.club?subject=Please%20halp!">mailto:support@cyberia.club</a>
<a href="mailto:support@cyberia.club?subject=Please%20halp!">Click Here to Email Support</a> (support@cyberia.club)
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block subcontent %} {% block subcontent %}

View File

@ -5,15 +5,10 @@ from flask import current_app
from time import sleep from time import sleep
from os.path import join from os.path import join
from subprocess import run from subprocess import run
from nanoid import generate
from capsulflask.db import get_model from capsulflask.db import get_model
def makeCapsulId(): def validate_capsul_id(id):
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
return f"capsul-{lettersAndNumbers}"
def validateCapsulId(id):
if not re.match(r"^capsul-[a-z0-9]{10}$", id): if not re.match(r"^capsul-[a-z0-9]{10}$", id):
raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"") raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"")
@ -27,10 +22,10 @@ class VirtualizationInterface:
def get(self, id: str) -> VirtualMachine: def get(self, id: str) -> VirtualMachine:
pass pass
def listIds(self) -> list: def list_ids(self) -> list:
pass pass
def create(self, email: str, template_file_name: str, vcpus: int, memory: int, ssh_public_keys: list) -> VirtualMachine: def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory: int, ssh_public_keys: list) -> VirtualMachine:
pass pass
def destroy(self, email: str, id: str): def destroy(self, email: str, id: str):
@ -38,14 +33,14 @@ class VirtualizationInterface:
class MockVirtualization(VirtualizationInterface): class MockVirtualization(VirtualizationInterface):
def get(self, id): def get(self, id):
validateCapsulId(id) validate_capsul_id(id)
return VirtualMachine(id, ipv4="1.1.1.1") return VirtualMachine(id, ipv4="1.1.1.1")
def listIds(self) -> list: def list_ids(self) -> list:
return get_model().allVmIds() return get_model().all_vm_ids()
def create(self, email: str, template_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list): def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
id = makeCapsulId() validate_capsul_id(id)
print(f"mock create: {id} for {email}") print(f"mock create: {id} for {email}")
sleep(5) sleep(5)
return VirtualMachine(id, ipv4="1.1.1.1") return VirtualMachine(id, ipv4="1.1.1.1")
@ -56,7 +51,7 @@ class MockVirtualization(VirtualizationInterface):
class ShellScriptVirtualization(VirtualizationInterface): class ShellScriptVirtualization(VirtualizationInterface):
def validateCompletedProcess(self, completedProcess, email=None): def validate_completed_process(self, completedProcess, email=None):
emailPart = "" emailPart = ""
if email != None: if email != None:
emailPart = f"for {email}" emailPart = f"for {email}"
@ -70,9 +65,9 @@ class ShellScriptVirtualization(VirtualizationInterface):
""") """)
def get(self, id): def get(self, id):
validateCapsulId(id) validate_capsul_id(id)
completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True) completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
self.validateCompletedProcess(completedProcess) self.validate_completed_process(completedProcess)
lines = completedProcess.stdout.splitlines() lines = completedProcess.stdout.splitlines()
if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", lines[0]): if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", lines[0]):
@ -80,16 +75,16 @@ class ShellScriptVirtualization(VirtualizationInterface):
return VirtualMachine(id, ipv4=lines[0]) return VirtualMachine(id, ipv4=lines[0])
def listIds(self) -> list: def list_ids(self) -> list:
completedProcess = run([join(current_app.root_path, 'shell_scripts/listIds.sh')], capture_output=True) completedProcess = run([join(current_app.root_path, 'shell_scripts/list_ids.sh')], capture_output=True)
self.validateCompletedProcess(completedProcess) self.validate_completed_process(completedProcess)
return completedProcess.stdout.splitlines() return completedProcess.stdout.splitlines()
def create(self, email: str, template_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list): def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
id = makeCapsulId() validate_capsul_id(id)
if not re.match(r"^[a-zA-Z0-9_.-]$", template_file_name): if not re.match(r"^[a-zA-Z0-9_.-]$", template_image_file_name):
raise ValueError(f"template_file_name \"{template_file_name}\" must match \"^[a-zA-Z0-9_.-]$\"") raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9_.-]$\"")
for ssh_public_key in ssh_public_keys: for ssh_public_key in ssh_public_keys:
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$", ssh_public_key): if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$", ssh_public_key):
@ -106,18 +101,18 @@ class ShellScriptVirtualization(VirtualizationInterface):
completedProcess = run([ completedProcess = run([
join(current_app.root_path, 'shell_scripts/create.sh'), join(current_app.root_path, 'shell_scripts/create.sh'),
id, id,
template_file_name, template_image_file_name,
str(vcpus), str(vcpus),
str(memory_mb), str(memory_mb),
ssh_keys_string ssh_keys_string
], capture_output=True) ], capture_output=True)
self.validateCompletedProcess(completedProcess, email) self.validate_completed_process(completedProcess, email)
lines = completedProcess.stdout.splitlines() lines = completedProcess.stdout.splitlines()
vmSettings = f""" vmSettings = f"""
id={id} id={id}
template_file_name={template_file_name} template_image_file_name={template_image_file_name}
vcpus={str(vcpus)} vcpus={str(vcpus)}
memory={str(memory_mb)} memory={str(memory_mb)}
ssh_public_keys={ssh_keys_string} ssh_public_keys={ssh_keys_string}
@ -149,9 +144,9 @@ class ShellScriptVirtualization(VirtualizationInterface):
""") """)
def destroy(self, email: str, id: str): def destroy(self, email: str, id: str):
validateCapsulId(id) validate_capsul_id(id)
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True) completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
self.validateCompletedProcess(completedProcess, email) self.validate_completed_process(completedProcess, email)
lines = completedProcess.stdout.splitlines() lines = completedProcess.stdout.splitlines()
if not lines[len(lines)-1] == "success": if not lines[len(lines)-1] == "success":