first try at creating VirtualizationInterface
This commit is contained in:
		@ -49,17 +49,17 @@ def login():
 | 
			
		||||
 | 
			
		||||
                current_app.config["FLASK_MAIL_INSTANCE"].send(
 | 
			
		||||
                    Message(
 | 
			
		||||
                    "Click This Link to Login to Capsul",
 | 
			
		||||
                    body=f"""
 | 
			
		||||
                        Navigate to {link} to log into capsul.
 | 
			
		||||
                        "Click This Link to Login to Capsul",
 | 
			
		||||
                        body=f"""
 | 
			
		||||
                            Navigate to {link} to log into capsul.
 | 
			
		||||
 | 
			
		||||
                        If you didn't request this, ignore this message.
 | 
			
		||||
                    """,
 | 
			
		||||
                    html=f"""
 | 
			
		||||
                        <p>Navigate to <a href="{link}">{link}</a> to log into capsul.</p>
 | 
			
		||||
                        <p>If you didn't request this, ignore this message.</p>
 | 
			
		||||
                    """,
 | 
			
		||||
                    recipients=[email]
 | 
			
		||||
                            If you didn't request this, ignore this message.
 | 
			
		||||
                        """,
 | 
			
		||||
                        html=f"""
 | 
			
		||||
                            <p>Navigate to <a href="{link}">{link}</a> to log into capsul.</p>
 | 
			
		||||
                            <p>If you didn't request this, ignore this message.</p>
 | 
			
		||||
                        """,
 | 
			
		||||
                        recipients=[email]
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ from psycopg2 import pool
 | 
			
		||||
from flask import current_app
 | 
			
		||||
from flask import g
 | 
			
		||||
 | 
			
		||||
from capsulflask.model import Model
 | 
			
		||||
from capsulflask.db_model import DBModel
 | 
			
		||||
 | 
			
		||||
def init_app(app):
 | 
			
		||||
  databaseUrl = urlparse(app.config['DATABASE_URL'])
 | 
			
		||||
@ -111,8 +111,8 @@ def get_model():
 | 
			
		||||
    if 'model' not in g:
 | 
			
		||||
        connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn()
 | 
			
		||||
        cursor = connection.cursor()
 | 
			
		||||
        g.model = Model(connection, cursor)
 | 
			
		||||
    return g.model
 | 
			
		||||
        g.db_model = DBModel(connection, cursor)
 | 
			
		||||
    return g.db_model
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def close_db(e=None):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								capsulflask/db_model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								capsulflask/db_model.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
 | 
			
		||||
from nanoid import generate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DBModel:
 | 
			
		||||
  def __init__(self, connection, cursor):
 | 
			
		||||
    self.connection = connection
 | 
			
		||||
    self.cursor = cursor
 | 
			
		||||
 | 
			
		||||
  def login(self, email):
 | 
			
		||||
    self.cursor.execute("SELECT * FROM accounts WHERE email = %s", (email, ))
 | 
			
		||||
    if len(self.cursor.fetchall()) == 0:
 | 
			
		||||
      self.cursor.execute("INSERT INTO accounts (email) VALUES (%s)", (email, ))
 | 
			
		||||
 | 
			
		||||
    self.cursor.execute("SELECT token FROM login_tokens WHERE email = %s", (email, ))
 | 
			
		||||
    if len(self.cursor.fetchall()) > 2:
 | 
			
		||||
      return None
 | 
			
		||||
 | 
			
		||||
    token = generate()
 | 
			
		||||
    self.cursor.execute("INSERT INTO login_tokens (email, token) VALUES (%s, %s)", (email, token))
 | 
			
		||||
    self.connection.commit()
 | 
			
		||||
 | 
			
		||||
    return token
 | 
			
		||||
    
 | 
			
		||||
  def consumeToken(self, token):
 | 
			
		||||
    self.cursor.execute("SELECT email FROM login_tokens WHERE token = %s", (token, ))
 | 
			
		||||
    rows = self.cursor.fetchall()
 | 
			
		||||
    if len(rows) > 0:
 | 
			
		||||
      email = rows[0][0]
 | 
			
		||||
      self.cursor.execute("DELETE FROM login_tokens WHERE email = %s", (email, ))
 | 
			
		||||
      self.connection.commit()
 | 
			
		||||
      return email
 | 
			
		||||
    return None
 | 
			
		||||
@ -1,9 +1,17 @@
 | 
			
		||||
DROP TABLE payments;
 | 
			
		||||
 | 
			
		||||
DROP TABLE logintokens;
 | 
			
		||||
DROP TABLE login_tokens;
 | 
			
		||||
 | 
			
		||||
DROP TABLE vm_ssh_public_key;
 | 
			
		||||
 | 
			
		||||
DROP TABLE vms;
 | 
			
		||||
 | 
			
		||||
DROP TABLE vm_sizes;
 | 
			
		||||
 | 
			
		||||
DROP TABLE os_images;
 | 
			
		||||
 | 
			
		||||
DROP TABLE ssh_public_keys;
 | 
			
		||||
 | 
			
		||||
DROP TABLE accounts;
 | 
			
		||||
 | 
			
		||||
UPDATE schemaversion SET version = 1;
 | 
			
		||||
@ -1,15 +1,48 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CREATE TABLE accounts (
 | 
			
		||||
   email TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
   created  TIMESTAMP NOT NULL DEFAULT NOW()
 | 
			
		||||
  email TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
  created  TIMESTAMP NOT NULL DEFAULT NOW()
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE os_images (
 | 
			
		||||
  id    TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
  template_image_file_name TEXT NOT NULL,
 | 
			
		||||
  description TEXT NOT NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE vm_sizes (
 | 
			
		||||
  id    TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
  dollars_per_month NUMERIC(8, 2) NOT NULL,
 | 
			
		||||
  memory_megabytes INTEGER NOT NULL,
 | 
			
		||||
  vcpus INTEGER NOT NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE ssh_public_keys (
 | 
			
		||||
  id    SERIAL PRIMARY KEY,
 | 
			
		||||
  email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
 | 
			
		||||
  name TEXT NOT NULL,
 | 
			
		||||
  content TEXT NOT NULL,
 | 
			
		||||
  UNIQUE (id, email)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE vms (
 | 
			
		||||
   id    TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
   email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
 | 
			
		||||
   created  TIMESTAMP NOT NULL DEFAULT NOW(),
 | 
			
		||||
   deleted  TIMESTAMP NOT NULL
 | 
			
		||||
  id    TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
  email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
 | 
			
		||||
  os    TEXT REFERENCES os_images(id) ON DELETE RESTRICT,
 | 
			
		||||
  size  TEXT REFERENCES vm_sizes(id) ON DELETE RESTRICT,
 | 
			
		||||
  created  TIMESTAMP NOT NULL DEFAULT NOW(),
 | 
			
		||||
  deleted  TIMESTAMP,
 | 
			
		||||
  UNIQUE (id, email)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE vm_ssh_public_key (
 | 
			
		||||
  ssh_public_key_id  INTEGER NOT NULL,
 | 
			
		||||
  email              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, vm_id)             REFERENCES vms(email, id) ON DELETE RESTRICT,
 | 
			
		||||
  PRIMARY KEY (email, vm_id, ssh_public_key_id)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE payments (
 | 
			
		||||
@ -19,11 +52,28 @@ CREATE TABLE payments (
 | 
			
		||||
  PRIMARY KEY (email, created)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE logintokens (
 | 
			
		||||
CREATE TABLE login_tokens (
 | 
			
		||||
  email      TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
 | 
			
		||||
  created    TIMESTAMP NOT NULL DEFAULT NOW(),
 | 
			
		||||
  token      TEXT NOT NULL,
 | 
			
		||||
  PRIMARY KEY (email, created)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
INSERT INTO os_images (id, template_image_file_name, description)
 | 
			
		||||
VALUES  ('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'),
 | 
			
		||||
        ('centos7', 'CentOS-7-x86_64-GenericCloud.qcow2', 'CentOS 7'),
 | 
			
		||||
        ('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'),
 | 
			
		||||
        ('guix110', 'guixsystem-cloud-2020-05.qcow2', 'Guix System 1.1.0');
 | 
			
		||||
 | 
			
		||||
INSERT INTO vm_sizes (id, dollars_per_month, memory_megabytes, vcpus)
 | 
			
		||||
VALUES  ('f1-s',    5.33,   512, 1),
 | 
			
		||||
        ('f1-m',    7.16,  1024, 1),
 | 
			
		||||
        ('f1-l',    8.92,  2048, 1),
 | 
			
		||||
        ('f1-x',   16.16,  4096, 2),
 | 
			
		||||
        ('f1-xx',  29.66,  8192, 4),
 | 
			
		||||
        ('f1-xxx', 57.58, 16384, 8);
 | 
			
		||||
 | 
			
		||||
UPDATE schemaversion SET version = 2;
 | 
			
		||||
							
								
								
									
										79
									
								
								capsulflask/shell_scripts/create.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								capsulflask/shell_scripts/create.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
#!/bin/sh -e
 | 
			
		||||
#
 | 
			
		||||
# create VMs for the capsul service
 | 
			
		||||
# developed by Cyberia Heavy Industries
 | 
			
		||||
# POSIX or die
 | 
			
		||||
 | 
			
		||||
vmname="$1"
 | 
			
		||||
template_file="/tank/img/$2"
 | 
			
		||||
vcpus="$3"
 | 
			
		||||
memory="$4"
 | 
			
		||||
pubkeys="$5"
 | 
			
		||||
root_voume_size="25G"
 | 
			
		||||
 | 
			
		||||
if echo "$vmname" | grep -vqE '^capsul-[a-z0-9]{10}$'; then
 | 
			
		||||
  echo "vmname $vmname must match "'"^capsul-[a-z0-9]{10}$"'
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ ! -f "$template_file" ]; then 
 | 
			
		||||
  echo "template $template_file not found"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if echo "$vcpus" | grep -vqE "^[0-9]+$"; then 
 | 
			
		||||
  echo "vcpus \"$vcpus\" must be an integer"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if echo "$memory" | grep -vqE "^[0-9]+$"; then 
 | 
			
		||||
  echo "memory \"$memory\" must be an integer"
 | 
			
		||||
  exit 1
 | 
			
		||||
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+/_=@ -]+$"'
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
disk="/tank/vm/$vmname.qcow2"
 | 
			
		||||
cdrom="/tank/vm/$vmname.iso"
 | 
			
		||||
xml="/tank/vm/$vmname.xml"
 | 
			
		||||
 | 
			
		||||
if [ -f /tank/vm/$vmname.qcow2 ]; then
 | 
			
		||||
    echo "Randomly generated name matched an existing VM! Odds are like one in a billion. Buy a lotto ticket."
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
qemu-img create -f qcow2 -b "$template_file" "$disk"
 | 
			
		||||
cp /tank/config/cyberia-cloudinit.yml /tmp/cloudinit.yml
 | 
			
		||||
echo "$pubkeys" | while IFS= read -r line; do
 | 
			
		||||
  echo "      - $line" >> /tmp/cloudinit.yml
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
cloud-localds "$cdrom" /tmp/cloudinit.yml
 | 
			
		||||
 | 
			
		||||
qemu-img resize "$disk" "$root_voume_size"
 | 
			
		||||
virt-install \
 | 
			
		||||
    --memory "$memory" \
 | 
			
		||||
    --vcpus "$vcpus" \
 | 
			
		||||
    --name "$vmname" \
 | 
			
		||||
    --disk "$disk",bus=virtio \
 | 
			
		||||
    --disk "$cdrom",device=cdrom \
 | 
			
		||||
    --os-type Linux \
 | 
			
		||||
    --os-variant generic \
 | 
			
		||||
    --virt-type kvm \
 | 
			
		||||
    --graphics vnc,listen=127.0.0.1 \
 | 
			
		||||
    --network network=public1,filterref=clean-traffic,model=virtio \
 | 
			
		||||
    --import \
 | 
			
		||||
    --print-xml > "$xml"
 | 
			
		||||
 | 
			
		||||
# --network network=public6,filterref=clean-traffic,model=virtio
 | 
			
		||||
chmod 0600 "$xml" "$disk" "$cdrom"
 | 
			
		||||
virsh define "$xml"
 | 
			
		||||
virsh start "$vmname"
 | 
			
		||||
 | 
			
		||||
echo "success"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								capsulflask/shell_scripts/destroy.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								capsulflask/shell_scripts/destroy.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
three_dots() {
 | 
			
		||||
	printf '.'
 | 
			
		||||
	sleep 0.01
 | 
			
		||||
	printf '.'
 | 
			
		||||
	sleep 0.01
 | 
			
		||||
	printf '.\n'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vmname="$1"
 | 
			
		||||
 | 
			
		||||
if echo "$vmname" | grep -vqE '^capsul-[a-z0-9]{10}$'; then
 | 
			
		||||
  echo "vmname $vmname must match "'"^capsul-[a-z0-9]{10}$"'
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo "deleting $vmname:"
 | 
			
		||||
count=$(ls /tank/vm/$vmname* 2> /dev/null | wc -l)
 | 
			
		||||
if [ "$count" -gt 3 ]; then
 | 
			
		||||
	echo "too many files found, exiting cowardly:"
 | 
			
		||||
	echo "$(ls /tank/vm/$vmname*)"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
virsh list --name --all | grep -q -E "^$vmname$"
 | 
			
		||||
if [ "$?" -eq 1 ]; then
 | 
			
		||||
	echo "Error: $vmname not found"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
virsh list --name --state-running | grep -q -E "^$vmname$"
 | 
			
		||||
if [ "$?" -eq 0 ]; then
 | 
			
		||||
	printf '  stopping vm'; three_dots
 | 
			
		||||
	virsh destroy "$vmname" > /dev/null
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
printf '  undefining xml.'; three_dots
 | 
			
		||||
if ! [ -f "/tank/vm/$vmname.qcow2" ]; then
 | 
			
		||||
	echo "/tank/vm/$vmname.qcow2 is not a file, exiting cowardly"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
virsh undefine "$vmname" > /dev/null
 | 
			
		||||
printf '  deleting disks.' ; three_dots
 | 
			
		||||
rm /tank/vm/$vmname*
 | 
			
		||||
 | 
			
		||||
echo "success"
 | 
			
		||||
							
								
								
									
										16
									
								
								capsulflask/shell_scripts/get.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								capsulflask/shell_scripts/get.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
vmname="$1"
 | 
			
		||||
 | 
			
		||||
if echo "$vmname" | grep -vqE '^capsul-[a-z0-9]{10}$'; then
 | 
			
		||||
  echo "vmname $vmname must match "'"^capsul-[a-z0-9]{10}$"'
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if virsh list --name --all | grep -vqE "^$vmname$" ; then
 | 
			
		||||
	echo "Error: $vmname not found"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# this gets the ipv4
 | 
			
		||||
virsh domifaddr "$vmname" | awk '/vnet/ {print $4}' | cut -d'/' -f1
 | 
			
		||||
							
								
								
									
										3
									
								
								capsulflask/shell_scripts/listIds.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								capsulflask/shell_scripts/listIds.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
virsh list --all | grep running | grep -v ' Id' | grep -v -- '----' | awk '{print $2}' | sort
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
<head>
 | 
			
		||||
<meta charset="utf-8">
 | 
			
		||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
<title>{% block title %}{% endblock %}{% if self.title() %} - {% endif %}capsul</title>
 | 
			
		||||
<title>{% block title %}{% endblock %}{% if self.title() %} - {% endif %}capsul💊</title>
 | 
			
		||||
<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
<link rel="shortcut icon" href="/favicon.ico" />
 | 
			
		||||
<link rel="apple-touch-icon" href="/icon.png" />
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										134
									
								
								capsulflask/virt_model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								capsulflask/virt_model.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from flask import current_app
 | 
			
		||||
from time import sleep
 | 
			
		||||
from os.path import join
 | 
			
		||||
from subprocess import run
 | 
			
		||||
 | 
			
		||||
class VirtualMachine:
 | 
			
		||||
  def __init__(self, id, ipv4=None, ipv6=None):
 | 
			
		||||
    self.id = id
 | 
			
		||||
    self.ipv4 = ipv4
 | 
			
		||||
    self.ipv6 = ipv6
 | 
			
		||||
 | 
			
		||||
class VirtualizationInterface:
 | 
			
		||||
  def get(self, id: str) -> VirtualMachine:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
  def listIds(self) -> list:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
  def create(self, id: str, template_file_name: str, memory: int, vcpus: int, ssh_public_keys: list) -> VirtualMachine:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
  def destroy(self, id: str):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class ShellScriptVirtualization(VirtualizationInterface):
 | 
			
		||||
 | 
			
		||||
  def validateId(self, 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}}$\"")
 | 
			
		||||
 | 
			
		||||
  def validateCompletedProcess(self, completedProcess):
 | 
			
		||||
    if completedProcess.returncode != 0:
 | 
			
		||||
      raise RuntimeError(f"""{" ".join(completedProcess.args)} failed with exit code {completedProcess.returncode}
 | 
			
		||||
        stdout:
 | 
			
		||||
        {completedProcess.stdout}
 | 
			
		||||
        stderr:
 | 
			
		||||
        {completedProcess.stderr}
 | 
			
		||||
      """)
 | 
			
		||||
 | 
			
		||||
  def get(self, id):
 | 
			
		||||
    self.validateId(id)
 | 
			
		||||
    completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
 | 
			
		||||
    self.validateCompletedProcess(completedProcess)
 | 
			
		||||
    lines = completedProcess.stdout.splitlines()
 | 
			
		||||
 | 
			
		||||
    if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", lines[0]):
 | 
			
		||||
      return None
 | 
			
		||||
 | 
			
		||||
    return VirtualMachine(id, ipv4=lines[0])
 | 
			
		||||
 | 
			
		||||
  def listIds(self) -> list:
 | 
			
		||||
    completedProcess = run([join(current_app.root_path, 'shell_scripts/listIds.sh')], capture_output=True)
 | 
			
		||||
    self.validateCompletedProcess(completedProcess)
 | 
			
		||||
    return completedProcess.stdout.splitlines()
 | 
			
		||||
 | 
			
		||||
  def create(self, id: str, template_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
 | 
			
		||||
    self.validateId(id)
 | 
			
		||||
 | 
			
		||||
    if not re.match(r"^[a-zA-Z0-9_.-]$", template_file_name):
 | 
			
		||||
      raise ValueError(f"template_file_name \"{template_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 vcpus < 1 or vcpus > 8:
 | 
			
		||||
      raise ValueError(f"vcpus \"{vcpus}\" must match 1 <= vcpus <= 8")
 | 
			
		||||
 | 
			
		||||
    if memory_mb < 512 or memory_mb > 16384:
 | 
			
		||||
      raise ValueError(f"memory_mb \"{memory_mb}\" must match 512 <= memory_mb <= 16384")
 | 
			
		||||
 | 
			
		||||
    ssh_keys_string = "\n".join(ssh_public_keys)
 | 
			
		||||
 | 
			
		||||
    completedProcess = run([
 | 
			
		||||
      join(current_app.root_path, 'shell_scripts/create.sh'),
 | 
			
		||||
      id,
 | 
			
		||||
      template_file_name,
 | 
			
		||||
      str(vcpus),
 | 
			
		||||
      str(memory_mb),
 | 
			
		||||
      ssh_keys_string
 | 
			
		||||
    ], capture_output=True)
 | 
			
		||||
 | 
			
		||||
    self.validateCompletedProcess(completedProcess)
 | 
			
		||||
    lines = completedProcess.stdout.splitlines()
 | 
			
		||||
 | 
			
		||||
    vmSettings = f"""
 | 
			
		||||
      id={id}
 | 
			
		||||
      template_file_name={template_file_name}
 | 
			
		||||
      vcpus={str(vcpus)}
 | 
			
		||||
      memory={str(memory_mb)}
 | 
			
		||||
      ssh_public_keys={ssh_keys_string}
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if not lines[len(lines)-1] == "success":
 | 
			
		||||
      raise ValueError(f"""failed to create vm with:
 | 
			
		||||
        {vmSettings}
 | 
			
		||||
        stdout:
 | 
			
		||||
        {completedProcess.stdout}
 | 
			
		||||
        stderr:
 | 
			
		||||
        {completedProcess.stderr}
 | 
			
		||||
      """)
 | 
			
		||||
    
 | 
			
		||||
    for _ in range(0, 10):
 | 
			
		||||
      sleep(6)
 | 
			
		||||
      result = self.get(id)
 | 
			
		||||
      if result != None:
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    for _ in range(0, 10):
 | 
			
		||||
      sleep(60)
 | 
			
		||||
      result = self.get(id)
 | 
			
		||||
      if result != None:
 | 
			
		||||
        return result
 | 
			
		||||
    
 | 
			
		||||
    raise TimeoutError(f"""timed out waiting for vm to obtain an IP address:
 | 
			
		||||
        {vmSettings}
 | 
			
		||||
    """)
 | 
			
		||||
 | 
			
		||||
  def destroy(self, id: str):
 | 
			
		||||
    self.validateId(id)
 | 
			
		||||
    completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
 | 
			
		||||
    self.validateCompletedProcess(completedProcess)
 | 
			
		||||
    lines = completedProcess.stdout.splitlines()
 | 
			
		||||
 | 
			
		||||
    if not lines[len(lines)-1] == "success":
 | 
			
		||||
      raise ValueError(f"""failed to destroy vm "{id}":
 | 
			
		||||
        stdout:
 | 
			
		||||
        {completedProcess.stdout}
 | 
			
		||||
        stderr:
 | 
			
		||||
        {completedProcess.stderr}
 | 
			
		||||
      """)
 | 
			
		||||
		Reference in New Issue
	
	Block a user