diff --git a/capsulflask/console.py b/capsulflask/console.py index 43f24e3..987e2d5 100644 --- a/capsulflask/console.py +++ b/capsulflask/console.py @@ -1,4 +1,5 @@ import re +import sys from flask import Blueprint from flask import flash from flask import current_app @@ -14,7 +15,7 @@ from nanoid import generate from capsulflask.auth import account_required -from capsulflask.db import get_model +from capsulflask.db import get_model, my_exec_info_message bp = Blueprint("console", __name__, url_prefix="/console") @@ -22,9 +23,31 @@ def makeCapsulId(): lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) return f"capsul-{lettersAndNumbers}" +def double_check_capsul_address(db_model, email, id, ipv4): + try: + result = current_app.config["VIRTUALIZATION_MODEL"].get(id) + if result.ipv4 != ipv4: + ipv4 = result.ipv4 + db_model.updateVm(email=email, id=id, ipv4=result.ipv4) + except: + print(f""" + error occurred in list capsuls endpoint while trying to grab ip address of {vm['id']} + via the virtualization model: {my_exec_info_message(sys.exc_info())}""" + ) + + return ipv4 + @bp.route("/") @account_required def index(): + db_model = get_model() + vms = db_model.list_vms_for_account(email=session["account"]) + + # 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: + vm["ipv4"] = double_check_capsul_address(db_model, session["account"], vm["id"], vm["ipv4"]) + vms = list(map( lambda x: dict( id=x['id'], @@ -34,54 +57,26 @@ def index(): os=x['os'], created=x['created'].strftime("%b %d %Y %H:%M") ), - get_model().list_vms_for_account(session["account"]) + vms )) + return render_template("capsuls.html", vms=vms, has_vms=len(vms) > 0) -@bp.route("/ssh", methods=("GET", "POST")) +@bp.route("/") @account_required -def ssh_public_keys(): +def detail(id): db_model = get_model() - errors = list() - - if request.method == "POST": - method = request.form["method"] + vm = db_model.get_vm_detail(email=session["account"], id=id) - 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 vm is None: + return abort(404, f"{id} doesn't exist.") - 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+/_=@ -]+$\"") + vm["ipv4"] = double_check_capsul_address(db_model, session["account"], vm["id"], vm["ipv4"]) + 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 "" - if db_model.ssh_public_key_name_exists(session["account"], name): - errors.append("A key with that name already exists") + return render_template("capsul-detail.html", vm=vm) - if len(errors) == 0: - db_model.create_ssh_public_key(session["account"], name, content) - - elif method == "DELETE": - - if len(errors) == 0: - db_model.delete_ssh_public_key(session["account"], name) - - 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:]}"), - db_model.list_ssh_public_keys_for_account(session["account"]) - )) - - return render_template("ssh-public-keys.html", ssh_public_keys=keys_list, has_ssh_public_keys=len(keys_list) > 0) @bp.route("/create", methods=("GET", "POST")) @account_required @@ -108,7 +103,7 @@ def create(): errors.append(f"Invalid os {os}") posted_keys_count = int(request.form["ssh_public_key_count"]) - posted_keys_contents = list() + posted_keys = list() if posted_keys_count > 1000: errors.append("something went wrong with ssh keys") @@ -116,16 +111,16 @@ def create(): for i in range(0, posted_keys_count): if f"ssh_key_{i}" in request.form: posted_name = request.form[f"ssh_key_{i}"] - key_content = None - for key in ssh_public_keys: - if key['name'] == posted_name: - key_content = key['content'] - if key_content: - posted_keys_contents.append(key_content) + key = None + for x in ssh_public_keys: + if x['name'] == posted_name: + key = x + if key: + posted_keys.append(key) else: errors.append(f"SSH Key \"{posted_name}\" doesn't exist") - if len(posted_keys_contents) == 0: + if len(posted_keys) == 0: errors.append("At least one SSH Public Key is required") if len(errors) == 0: @@ -134,7 +129,8 @@ def create(): email=session["account"], id=id, size=size, - os=os + os=os, + ssh_public_keys=list(map(lambda x: x["name"], posted_keys)) ) current_app.config["VIRTUALIZATION_MODEL"].create( email = session["account"], @@ -142,7 +138,7 @@ def create(): template_image_file_name=operating_systems[os]['template_image_file_name'], vcpus=vm_sizes[size]['vcpus'], memory_mb=vm_sizes[size]['memory_mb'], - ssh_public_keys=posted_keys_contents + ssh_public_keys=list(map(lambda x: x["content"], posted_keys)) ) created_os = os @@ -159,6 +155,52 @@ def create(): vm_sizes=vm_sizes ) +@bp.route("/ssh", methods=("GET", "POST")) +@account_required +def ssh_public_keys(): + db_model = get_model() + 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+/_=@. -]+$\"") + + if db_model.ssh_public_key_name_exists(session["account"], name): + errors.append("A key with that name already exists") + + if len(errors) == 0: + db_model.create_ssh_public_key(session["account"], name, content) + + elif method == "DELETE": + + if len(errors) == 0: + db_model.delete_ssh_public_key(session["account"], name) + + 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:]}"), + db_model.list_ssh_public_keys_for_account(session["account"]) + )) + + return render_template("ssh-public-keys.html", ssh_public_keys=keys_list, has_ssh_public_keys=len(keys_list) > 0) + + @bp.route("/billing") @account_required def faq(): diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 875e478..fd796d0 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -89,11 +89,52 @@ class DBModel: self.cursor.fetchall() )) - def create_vm(self, email, id, size, os): + def updateVm(self, email, id, ipv4): + self.cursor.execute("UPDATE vms SET last_seen_ipv4 = %s WHERE email = %s AND id = %s", (ipv4, email, id)) + self.connection.commit() + + def create_vm(self, email, id, size, os, ssh_public_keys): self.cursor.execute(""" INSERT INTO vms (email, id, size, os) VALUES (%s, %s, %s, %s) """, (email, id, size, os) ) - self.connection.commit() \ No newline at end of file + + for ssh_public_key in ssh_public_keys: + self.cursor.execute(""" + INSERT INTO vm_ssh_public_key (email, vm_id, ssh_public_key_name) + VALUES (%s, %s, %s) + """, + (email, id, ssh_public_key) + ) + self.connection.commit() + + def get_vm_detail(self, email, id): + self.cursor.execute(""" + SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, os_images.description, vms.created, vms.deleted, + vm_sizes.id, vm_sizes.dollars_per_month, vm_sizes.vcpus, vm_sizes.memory_mb, vm_sizes.bandwidth_gb_per_month + FROM vms + JOIN os_images on vms.os = os_images.id + JOIN vm_sizes on vms.size = vm_sizes.id + WHERE vms.email = %s AND vms.id = %s""", + (email, id) + ) + rows = self.cursor.fetchall() + if len(rows) == 0: + return None + + x = rows[0] + vm = dict( + id=x[0], ipv4=x[1], ipv6=x[2], os_description=x[3], created=x[4], deleted=x[5], + size=x[6], dollars_per_month=x[7], vcpus=x[8], memory_mb=x[9], bandwidth_gb_per_month=x[10] + ) + + self.cursor.execute(""" + SELECT ssh_public_key_name FROM vm_ssh_public_key + WHERE vm_ssh_public_key.email = %s AND vm_ssh_public_key.vm_id = %s""", + (email, id) + ) + vm["ssh_public_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() )) + + return vm diff --git a/capsulflask/shell_scripts/create.sh b/capsulflask/shell_scripts/create.sh index f280a55..f320376 100644 --- a/capsulflask/shell_scripts/create.sh +++ b/capsulflask/shell_scripts/create.sh @@ -32,8 +32,8 @@ if echo "$memory" | grep -vqE "^[0-9]+$"; then fi echo "$pubkeys" | while IFS= read -r line; do - if echo "$line" | grep -vqE "^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$"; then - echo "pubkey \"$line\" must match "'"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$"' + if echo "$line" | grep -vqE "^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$"; then + echo "pubkey \"$line\" must match "'"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$"' exit 1 fi done diff --git a/capsulflask/templates/capsul-detail.html b/capsulflask/templates/capsul-detail.html new file mode 100644 index 0000000..7416d2f --- /dev/null +++ b/capsulflask/templates/capsul-detail.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} + +{% block title %}Create{% endblock %} + +{% block content %} +
+

{{ vm['id'] }}

+
+
+
+ + {{ vm['created'] }} +
+
+ + {{ vm['size'] }} +
+
+ + ${{ vm['dollars_per_month'] }} +
+
+ + {{ vm['ipv4'] }} +
+
+ + {{ vm['os_description'] }} +
+
+ + {{ vm['vcpus'] }} +
+
+ + {{ vm['memory_mb'] }}MB +
+
+ + {{ vm['bandwidth_gb_per_month'] }}GB/month +
+
+ + cyberian +
+
+ + {{ vm['ssh_public_keys'] }} +
+ +
+{% endblock %} + +{% block pagesource %}/templates/create-capsul.html{% endblock %} diff --git a/capsulflask/virt_model.py b/capsulflask/virt_model.py index 5fdafdc..03b5b64 100644 --- a/capsulflask/virt_model.py +++ b/capsulflask/virt_model.py @@ -86,8 +86,8 @@ class ShellScriptVirtualization(VirtualizationInterface): 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: - if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$", ssh_public_key): - raise ValueError(f"ssh_public_key \"{ssh_public_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$\"") + if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", ssh_public_key): + raise ValueError(f"ssh_public_key \"{ssh_public_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"") if vcpus < 1 or vcpus > 8: raise ValueError(f"vcpus \"{vcpus}\" must match 1 <= vcpus <= 8")