2020-05-11 16:57:39 +00:00
|
|
|
import re
|
2020-05-11 21:24:37 +00:00
|
|
|
import sys
|
2020-05-12 01:34:12 +00:00
|
|
|
from datetime import datetime
|
2020-05-11 02:43:06 +00:00
|
|
|
from flask import Blueprint
|
|
|
|
from flask import flash
|
|
|
|
from flask import current_app
|
|
|
|
from flask import g
|
|
|
|
from flask import redirect
|
|
|
|
from flask import url_for
|
|
|
|
from flask import request
|
|
|
|
from flask import session
|
|
|
|
from flask import render_template
|
|
|
|
from flask_mail import Message
|
|
|
|
from werkzeug.exceptions import abort
|
2020-05-11 06:47:14 +00:00
|
|
|
from nanoid import generate
|
|
|
|
|
|
|
|
from capsulflask.auth import account_required
|
2020-05-11 02:43:06 +00:00
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
from capsulflask.db import get_model, my_exec_info_message
|
2020-05-11 02:43:06 +00:00
|
|
|
|
|
|
|
bp = Blueprint("console", __name__, url_prefix="/console")
|
|
|
|
|
2020-05-11 06:47:14 +00:00
|
|
|
def makeCapsulId():
|
|
|
|
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
|
|
|
|
return f"capsul-{lettersAndNumbers}"
|
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
def double_check_capsul_address(id, ipv4):
|
2020-05-11 21:24:37 +00:00
|
|
|
try:
|
|
|
|
result = current_app.config["VIRTUALIZATION_MODEL"].get(id)
|
|
|
|
if result.ipv4 != ipv4:
|
|
|
|
ipv4 = result.ipv4
|
2020-05-12 01:34:12 +00:00
|
|
|
get_model().updateVm(email=session["account"], id=id, ipv4=result.ipv4)
|
2020-05-11 21:24:37 +00:00
|
|
|
except:
|
|
|
|
print(f"""
|
2020-05-12 01:34:12 +00:00
|
|
|
the virtualization model threw an error in double_check_capsul_address of {id}:
|
|
|
|
{my_exec_info_message(sys.exc_info())}"""
|
2020-05-11 21:24:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return ipv4
|
|
|
|
|
2020-05-11 06:47:14 +00:00
|
|
|
@bp.route("/")
|
|
|
|
@account_required
|
|
|
|
def index():
|
2020-05-12 01:34:12 +00:00
|
|
|
vms = get_vms()
|
2020-05-11 21:24:37 +00:00
|
|
|
|
|
|
|
# for now we are going to check the IP according to the virt model
|
|
|
|
# on every request. this could be done by a background job and cached later on...
|
|
|
|
for vm in vms:
|
2020-05-12 01:34:12 +00:00
|
|
|
vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"])
|
2020-05-11 21:24:37 +00:00
|
|
|
|
2020-05-11 20:13:20 +00:00
|
|
|
vms = list(map(
|
|
|
|
lambda x: dict(
|
|
|
|
id=x['id'],
|
|
|
|
size=x['size'],
|
|
|
|
ipv4=(x['ipv4'] if x['ipv4'] else "..booting.."),
|
|
|
|
ipv4_status=("ok" if x['ipv4'] else "waiting-pulse"),
|
|
|
|
os=x['os'],
|
|
|
|
created=x['created'].strftime("%b %d %Y %H:%M")
|
|
|
|
),
|
2020-05-11 21:24:37 +00:00
|
|
|
vms
|
2020-05-11 20:13:20 +00:00
|
|
|
))
|
2020-05-11 21:24:37 +00:00
|
|
|
|
2020-05-11 20:13:20 +00:00
|
|
|
return render_template("capsuls.html", vms=vms, has_vms=len(vms) > 0)
|
2020-05-11 16:57:39 +00:00
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
@bp.route("/<string:id>")
|
2020-05-11 16:57:39 +00:00
|
|
|
@account_required
|
2020-05-11 21:24:37 +00:00
|
|
|
def detail(id):
|
2020-05-12 01:34:12 +00:00
|
|
|
vm = get_model().get_vm_detail(email=session["account"], id=id)
|
2020-05-11 16:57:39 +00:00
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
if vm is None:
|
|
|
|
return abort(404, f"{id} doesn't exist.")
|
2020-05-11 16:57:39 +00:00
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"])
|
2020-05-11 21:24:37 +00:00
|
|
|
vm["created"] = vm['created'].strftime("%b %d %Y %H:%M")
|
|
|
|
vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "<deleted>"
|
2020-05-11 16:57:39 +00:00
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
return render_template("capsul-detail.html", vm=vm)
|
2020-05-11 16:57:39 +00:00
|
|
|
|
2020-05-11 06:47:14 +00:00
|
|
|
|
|
|
|
@bp.route("/create", methods=("GET", "POST"))
|
|
|
|
@account_required
|
|
|
|
def create():
|
2020-05-12 01:34:12 +00:00
|
|
|
vm_sizes = get_model().vm_sizes_dict()
|
|
|
|
operating_systems = get_model().operating_systems_dict()
|
|
|
|
ssh_public_keys = get_model().list_ssh_public_keys_for_account(session["account"])
|
2020-05-11 20:13:20 +00:00
|
|
|
errors = list()
|
|
|
|
created_os = None
|
2020-05-11 06:47:14 +00:00
|
|
|
|
|
|
|
if request.method == "POST":
|
2020-05-11 20:13:20 +00:00
|
|
|
|
2020-05-11 06:47:14 +00:00
|
|
|
size = request.form["size"]
|
|
|
|
os = request.form["os"]
|
|
|
|
if not size:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("Size is required")
|
2020-05-11 06:47:14 +00:00
|
|
|
elif size not in vm_sizes:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append(f"Invalid size {size}")
|
2020-05-11 06:47:14 +00:00
|
|
|
|
|
|
|
if not os:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("OS is required")
|
2020-05-11 06:47:14 +00:00
|
|
|
elif os not in operating_systems:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append(f"Invalid os {os}")
|
|
|
|
|
|
|
|
posted_keys_count = int(request.form["ssh_public_key_count"])
|
2020-05-11 21:24:37 +00:00
|
|
|
posted_keys = list()
|
2020-05-11 20:13:20 +00:00
|
|
|
|
|
|
|
if posted_keys_count > 1000:
|
|
|
|
errors.append("something went wrong with ssh keys")
|
|
|
|
else:
|
|
|
|
for i in range(0, posted_keys_count):
|
|
|
|
if f"ssh_key_{i}" in request.form:
|
|
|
|
posted_name = request.form[f"ssh_key_{i}"]
|
2020-05-11 21:24:37 +00:00
|
|
|
key = None
|
|
|
|
for x in ssh_public_keys:
|
|
|
|
if x['name'] == posted_name:
|
|
|
|
key = x
|
|
|
|
if key:
|
|
|
|
posted_keys.append(key)
|
2020-05-11 20:13:20 +00:00
|
|
|
else:
|
|
|
|
errors.append(f"SSH Key \"{posted_name}\" doesn't exist")
|
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
if len(posted_keys) == 0:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("At least one SSH Public Key is required")
|
|
|
|
|
|
|
|
if len(errors) == 0:
|
2020-05-11 06:47:14 +00:00
|
|
|
id = makeCapsulId()
|
2020-05-12 01:34:12 +00:00
|
|
|
get_model().create_vm(
|
2020-05-11 06:47:14 +00:00
|
|
|
email=session["account"],
|
|
|
|
id=id,
|
|
|
|
size=size,
|
2020-05-11 21:24:37 +00:00
|
|
|
os=os,
|
|
|
|
ssh_public_keys=list(map(lambda x: x["name"], posted_keys))
|
2020-05-11 06:47:14 +00:00
|
|
|
)
|
|
|
|
current_app.config["VIRTUALIZATION_MODEL"].create(
|
|
|
|
email = session["account"],
|
|
|
|
id=id,
|
2020-05-11 20:13:20 +00:00
|
|
|
template_image_file_name=operating_systems[os]['template_image_file_name'],
|
|
|
|
vcpus=vm_sizes[size]['vcpus'],
|
|
|
|
memory_mb=vm_sizes[size]['memory_mb'],
|
2020-05-11 21:24:37 +00:00
|
|
|
ssh_public_keys=list(map(lambda x: x["content"], posted_keys))
|
2020-05-11 06:47:14 +00:00
|
|
|
)
|
2020-05-11 20:13:20 +00:00
|
|
|
created_os = os
|
|
|
|
|
|
|
|
for error in errors:
|
2020-05-11 16:57:39 +00:00
|
|
|
flash(error)
|
|
|
|
|
2020-05-11 06:47:14 +00:00
|
|
|
return render_template(
|
2020-05-11 16:57:39 +00:00
|
|
|
"create-capsul.html",
|
2020-05-11 20:13:20 +00:00
|
|
|
created_os=created_os,
|
2020-05-11 06:47:14 +00:00
|
|
|
ssh_public_keys=ssh_public_keys,
|
2020-05-11 20:13:20 +00:00
|
|
|
ssh_public_key_count=len(ssh_public_keys),
|
|
|
|
has_ssh_public_keys=len(ssh_public_keys) > 0,
|
2020-05-11 06:47:14 +00:00
|
|
|
operating_systems=operating_systems,
|
|
|
|
vm_sizes=vm_sizes
|
|
|
|
)
|
|
|
|
|
2020-05-11 21:24:37 +00:00
|
|
|
@bp.route("/ssh", methods=("GET", "POST"))
|
|
|
|
@account_required
|
|
|
|
def ssh_public_keys():
|
|
|
|
errors = list()
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
method = request.form["method"]
|
|
|
|
|
|
|
|
name = request.form["name"]
|
|
|
|
if not name or len(name.strip()) < 1:
|
|
|
|
errors.append("Name is required")
|
|
|
|
elif not re.match(r"^[0-9A-Za-z_ -]+$", name):
|
|
|
|
errors.append("Name must match \"^[0-9A-Za-z_ -]+$\"")
|
|
|
|
|
|
|
|
if method == "POST":
|
|
|
|
content = request.form["content"]
|
|
|
|
if not content or len(content.strip()) < 1:
|
|
|
|
errors.append("Content is required")
|
|
|
|
else:
|
|
|
|
content = content.replace("\r", "").replace("\n", "")
|
|
|
|
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", content):
|
|
|
|
errors.append("Content must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"")
|
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
if get_model().ssh_public_key_name_exists(session["account"], name):
|
2020-05-11 21:24:37 +00:00
|
|
|
errors.append("A key with that name already exists")
|
|
|
|
|
|
|
|
if len(errors) == 0:
|
2020-05-12 01:34:12 +00:00
|
|
|
get_model().create_ssh_public_key(session["account"], name, content)
|
2020-05-11 21:24:37 +00:00
|
|
|
|
|
|
|
elif method == "DELETE":
|
|
|
|
|
|
|
|
if len(errors) == 0:
|
2020-05-12 01:34:12 +00:00
|
|
|
get_model().delete_ssh_public_key(session["account"], name)
|
2020-05-11 21:24:37 +00:00
|
|
|
|
|
|
|
for error in errors:
|
|
|
|
flash(error)
|
|
|
|
|
|
|
|
keys_list=list(map(
|
|
|
|
lambda x: dict(name=x['name'], content=f"{x['content'][:20]}...{x['content'][len(x['content'])-20:]}"),
|
2020-05-12 01:34:12 +00:00
|
|
|
get_model().list_ssh_public_keys_for_account(session["account"])
|
2020-05-11 21:24:37 +00:00
|
|
|
))
|
|
|
|
|
|
|
|
return render_template("ssh-public-keys.html", ssh_public_keys=keys_list, has_ssh_public_keys=len(keys_list) > 0)
|
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
def get_vms():
|
|
|
|
if 'user_vms' not in g:
|
|
|
|
g.user_vms = get_model().list_vms_for_account(session["account"])
|
|
|
|
return g.user_vms
|
2020-05-11 21:24:37 +00:00
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
def get_payments():
|
|
|
|
if 'user_payments' not in g:
|
|
|
|
g.user_payments = get_model().list_payments_for_account(session["account"])
|
|
|
|
return g.user_payments
|
|
|
|
|
|
|
|
def get_account_balance():
|
|
|
|
|
|
|
|
|
|
|
|
average_number_of_days_in_a_month = 30.44
|
|
|
|
|
|
|
|
vm_cost_dollars = 0.0
|
|
|
|
for vm in get_vms():
|
|
|
|
end_datetime = vm["deleted"] if vm["deleted"] else datetime.now()
|
|
|
|
vm_months = ( end_datetime - vm["created"] ).days / average_number_of_days_in_a_month
|
|
|
|
vm_cost_dollars += vm_months * vm["dollars_per_month"]
|
|
|
|
|
|
|
|
payment_total = sum(map(lambda x: x["dollars"], get_payments()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/account-balance")
|
2020-05-11 06:47:14 +00:00
|
|
|
@account_required
|
2020-05-12 01:34:12 +00:00
|
|
|
def account_balance():
|
|
|
|
errors = list()
|
|
|
|
payments = get_payments()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return render_template("account-balance.html")
|