This repository has been archived on 2020-09-13. You can view files and clone it, but cannot push or open issues or pull requests.
magic-app/second.py

186 lines
5.5 KiB
Python
Raw Normal View History

2020-07-01 18:46:29 +00:00
"""Less flashy version. More hard-coding."""
2020-07-01 18:02:19 +00:00
2020-07-02 11:41:31 +00:00
from hashlib import md5
2020-07-01 19:19:09 +00:00
from json import dumps, loads
from os import environ, mkdir
2020-07-01 19:19:09 +00:00
from os.path import exists
2020-07-01 18:02:19 +00:00
from pathlib import Path
2020-07-01 18:42:47 +00:00
from shlex import split
from subprocess import run
from typing import Dict
2020-07-01 18:02:19 +00:00
2020-07-01 19:19:09 +00:00
from flask import Flask, render_template, request
2020-07-01 18:42:47 +00:00
from flask_wtf import FlaskForm
2020-07-01 18:02:19 +00:00
from ruamel.yaml import YAML
2020-07-01 18:42:47 +00:00
from wtforms import PasswordField, StringField
from wtforms.validators import URL, DataRequired, Length
2020-07-01 18:02:19 +00:00
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
yaml = YAML()
2020-07-02 11:41:31 +00:00
APPS_SPEC = {
"gitea": {
"url": "https://git.autonomic.zone/compose-stacks/gitea",
"secrets": [
"db_passwd",
"db_root_passwd",
"internal_token",
"jwt_secret",
"secret_key",
],
"configs": ["app_ini"],
}
}
2020-07-01 18:02:19 +00:00
DATA_DIR = Path("./data")
2020-07-01 18:42:47 +00:00
def clone_app_template(app_name: str) -> None:
2020-07-01 18:42:47 +00:00
"""Git clone an app template repository."""
clone_path = DATA_DIR / app_name
2020-07-02 11:41:31 +00:00
clone_url = APPS_SPEC[app_name]["url"]
2020-07-01 18:42:47 +00:00
run(split(f"git clone {clone_url} {clone_path}"))
2020-07-01 18:02:19 +00:00
def create_docker_secret(key: str, value: str) -> None:
"""Load a docker secret into swarm."""
command = f"echo {value} | docker secret create {key} -"
run(command, shell=True)
def dump_db(db: Dict) -> None:
2020-07-01 19:19:09 +00:00
"""Dump the database."""
with open(DATA_DIR / "db.json", "w") as handle:
handle.write(dumps(db))
2020-07-01 19:19:09 +00:00
def load_db() -> Dict:
2020-07-01 19:19:09 +00:00
"""Load the database."""
db_path = DATA_DIR / "db.json"
if exists(db_path):
with open(db_path, "r") as handle:
return loads(handle.read())
return {}
2020-07-02 11:41:31 +00:00
def get_hash(value: str) -> str:
"""Hash a value for swarm versioning with good 'ol md5."""
hasher = md5()
hasher.update(value.encode())
return hasher.hexdigest()
def arrange_configs_and_secrets(app_name, form_data, env):
"""Version secrets and configs for swarm."""
def _create_versions(values, secrets=False):
"""Step through listing and create an env var key/val.
Takes "app_ini" and produces {"APP_INI_VERSION": "app_ini_laksjdklajsdkla"}.
Hash is based on the value of the input passed in from the form.
"""
for value in values:
if value in form_data:
hashed = get_hash(form_data[value])
env_key = f"{value.upper()}_VERSION"
env_val = f"{value}_{hashed}"
env[env_key] = env_val
if secrets:
create_docker_secret(env_val, form_data[value])
_create_versions(APPS_SPEC[app_name]["configs"])
_create_versions(APPS_SPEC[app_name]["secrets"], secrets=True)
2020-07-02 11:41:31 +00:00
return env
def stack_deploy(app_name, stack_name, env):
"""Depoy an application to the swarm."""
compose_yml = DATA_DIR / app_name / "compose.yml"
command = f"docker stack deploy -c {compose_yml} {stack_name}"
run(split(command), env=env)
def get_loaded_env(app_name, request_form):
2020-07-02 11:09:51 +00:00
"""Load environment from install form for compose.yml injection."""
environment = environ.copy()
for key in request_form.keys():
environment[key.upper()] = request.form[key]
return environment
class GiteaInstallForm(FlaskForm):
"""Gitea installation form."""
app_name = StringField("Application name", default="Git with a cup of tea")
2020-07-02 11:09:51 +00:00
domain = StringField("Domain name", validators=[DataRequired()],)
stack_name = StringField("Stack name", default="magic-app-gitea")
db_host = StringField("Database host", default="mariadb:3306")
db_name = StringField("Database name", default="gitea")
db_passwd = PasswordField(
2020-07-02 11:09:51 +00:00
"Database password", validators=[DataRequired(), Length(min=32)],
)
db_root_passwd = PasswordField(
2020-07-02 11:09:51 +00:00
"Database root password", validators=[DataRequired(), Length(min=32)],
)
db_type = StringField("Database type", default="mysql")
db_user = StringField("Database user", default="mysql")
internal_token = PasswordField(
2020-07-02 11:09:51 +00:00
"Internal secret token", validators=[DataRequired(), Length(min=105)],
)
jwt_secret = PasswordField(
2020-07-02 11:09:51 +00:00
"JWT secret", validators=[DataRequired(), Length(min=43)],
)
secret_key = PasswordField(
2020-07-02 11:41:31 +00:00
"Secret key", validators=[DataRequired(), Length(min=64)],
)
ssh_port = StringField("SSH port", default="2222")
2020-07-01 18:02:19 +00:00
@app.route("/")
2020-07-01 18:42:47 +00:00
def index():
"""Home page for app installation possibilities."""
2020-07-02 11:41:31 +00:00
return render_template("second/index.html", apps=[app for app in APPS_SPEC])
2020-07-01 18:42:47 +00:00
@app.route("/install/<app_name>")
def install(app_name):
"""Installation page for an app."""
2020-07-01 18:42:47 +00:00
if app_name == "gitea":
form = GiteaInstallForm()
return render_template("second/install.html", app_name=app_name, form=form)
2020-07-01 18:02:19 +00:00
2020-07-01 19:19:09 +00:00
@app.route("/deploy/<app_name>", methods=["POST"])
def deploy(app_name):
"""Deployment end-point for an app."""
2020-07-02 10:26:02 +00:00
if app_name == "gitea":
form = GiteaInstallForm(request.form)
if not form.validate():
return render_template("second/install.html", app_name=app_name, form=form)
2020-07-02 10:26:02 +00:00
try:
mkdir(DATA_DIR)
except FileExistsError:
pass
clone_app_template(app_name)
2020-07-02 11:41:31 +00:00
env = get_loaded_env(app_name, form.data)
env = arrange_configs_and_secrets(app_name, form.data, env)
2020-07-01 19:19:09 +00:00
dump_db({form.data["stack_name"]: form.data})
2020-07-01 19:19:09 +00:00
stack_deploy(app_name, form.data["stack_name"], env)
2020-07-01 19:19:09 +00:00
2020-07-02 12:29:15 +00:00
return render_template(
"second/deploy.html", app_name=app_name, stack_name=form.data["stack_name"]
)
2020-07-01 19:19:09 +00:00
2020-07-01 18:02:19 +00:00
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")