From ad31f17888de76dfc5661169982a4ffadc4a6fa8 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Thu, 2 Jul 2020 12:23:48 +0200 Subject: [PATCH] Refactor and add a number of helper methods --- second.py | 84 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/second.py b/second.py index 2bc9599..f49d370 100644 --- a/second.py +++ b/second.py @@ -4,8 +4,11 @@ from json import dumps, loads from os import environ from os.path import exists from pathlib import Path +from random import choice from shlex import split +from string import ascii_lowercase from subprocess import run +from typing import Dict from flask import Flask, render_template, request from flask_wtf import FlaskForm @@ -22,37 +25,25 @@ APPS = {"gitea": "https://git.autonomic.zone/compose-stacks/gitea"} DATA_DIR = Path("./data") -class GiteaConfigForm(FlaskForm): - app_name = StringField("Application name", default="Gitea") - db_host = StringField("Database host", default="mariadb:3306") - db_name = StringField("Database name", default="gitea") - db_passwd = PasswordField("Database password", validators=[DataRequired()]) - db_root_passwd = PasswordField( - "Root database password", validators=[DataRequired()] - ) - db_type = StringField("Database type", default="mysql") - db_user = StringField("Database user", default="mysql") - domain = StringField("Domain name", validators=[DataRequired()]) - ssh_port = StringField("SSH port", default="2222") - stack_name = StringField("Stack name", default="gitea") - internal_token = PasswordField("Internal token", validators=[DataRequired()]) - jwt_secret = PasswordField("JWT secret", validators=[DataRequired()]) +def get_secret(n: int) -> str: + """Generate a ASCII secret that is n chars long.""" + return "".join(choice(ascii_lowercase) for _ in range(n)) -def clone_app_template(app_name): +def clone_app_template(app_name: str) -> None: """Git clone an app template repository.""" clone_path = DATA_DIR / app_name - clone_url = apps[app_name] + clone_url = APPS[app_name] run(split(f"git clone {clone_url} {clone_path}")) -def dump_db(db): +def dump_db(db: Dict) -> None: """Dump the database.""" with open(DATA_DIR / "db.json", "w") as handle: - return handle.write(dumps(db)) + handle.write(dumps(db)) -def load_db(): +def load_db() -> Dict: """Load the database.""" db_path = DATA_DIR / "db.json" if exists(db_path): @@ -61,33 +52,64 @@ def load_db(): return {} +def stack_deploy(app_name, env): + """Depoy an application to the swarm.""" + compose_yml = DATA_DIR / app_name / "compose.yml" + command = f"docker stack deploy -c {compose_yml} {app_name}" + run(split(command), env=env) + + +def get_loaded_env(app_name, request_form): + """Load an environment with install form values 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") + domain = StringField("Domain name", validators=[DataRequired()]) + + db_host = StringField("Database host", default="mariadb:3306") + db_name = StringField("Database name", default="gitea") + db_passwd = PasswordField("DB password", default=get_secret(n=32)) + db_root_passwd = PasswordField("Root DB password", default=get_secret(n=32)) + db_type = StringField("Database type", default="mysql") + db_user = StringField("Database user", default="mysql") + internal_token = PasswordField("Internal token", default=get_secret(n=105)) + jwt_secret = PasswordField("JWT secret", default=get_secret(n=43)) + secret_key = PasswordField("Secret key", default=get_secret(n=64)) + ssh_port = StringField("SSH port", default="2222") + + @app.route("/") def index(): + """Home page for app installation possibilities.""" return render_template("second/index.html", apps=[app for app in APPS]) -@app.route("/config/") -def config(app_name): +@app.route("/install/") +def install(app_name): + """Installation page for an app.""" if app_name == "gitea": - # Note(decentral1se): load db.json and pre-populate the form? - form = GiteaConfigForm() - return render_template("second/config.html", app_name=app_name, form=form) + form = GiteaInstallForm() + return render_template("second/install.html", app_name=app_name, form=form) @app.route("/deploy/", methods=["POST"]) def deploy(app_name): - environment = environ.copy() - for key in request.form.keys(): - environment[key.upper()] = request.form[key] + """Deployment end-point for an app.""" + environment = get_loaded_env(app_name, request.form) # Note(decentral1se): how to handle the following? # configs -> ${STACK_NAME}_app_ini_${APP_INI_VERSION} # secrets -> ${STACK_NAME}_db_passwd_${DB_PASSWD_VERSION} # and dump it all to the db.json when we're done here too - compose_yml = DATA_DIR / app_name / "compose.yml" - command = f"docker stack deploy -c {compose_yml} {app_name}" - run(split(command), env=environment) + stack_deploy(app_name, environment) if __name__ == "__main__":