Hacking towards the db.json, trimming as much as possible
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Luke Murphy
2020-07-01 10:40:54 +02:00
parent 06b344b801
commit 8e2bce536f
10 changed files with 154 additions and 72 deletions

View File

@ -1,40 +1,145 @@
from json import loads
from os.path import exists
from pathlib import Path
from re import findall
from shlex import split
from subprocess import run
from flask import Flask, render_template, send_from_directory
from flask_wtf import FlaskForm
from ruamel.yaml import YAML
from wtforms import StringField
from wtforms.validators import DataRequired
app = Flask(__name__)
# Note(decentral1se): load from env vars at some point
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
def get_apps():
"""All apps that can be installed."""
apps_path = (Path(app.root_path) / "static" / "apps.json").absolute()
yaml = YAML()
# Note(decentral1se): paths needs to change dependening
# on deployment mode ./data in dev, /data in production
DATA_DIR = Path("./data")
STATIC_DIR = Path(app.root_path).absolute() / "static"
class InstallAppInputForm(FlaskForm):
"""Dynamically generated input form for app deploy."""
pass
def get_apps_json():
"""Retrieve the apps listing."""
global STATIC_DIR
try:
apps_path = STATIC_DIR / "apps.json"
with open(apps_path, "r") as handle:
return [key for key in loads(handle.read())]
return loads(handle.read())
except Exception as exception:
print("Failed to load apps.json, saw {}".format(str(exception)))
app.logger.error(f"Failed to load apps.json, saw {exception}")
def clone_app_template(app_name):
"""Git clone an app template repository."""
global DATA_DIR
try:
apps_json = get_apps_json()
app_repo_url = apps_json[app_name]
except KeyError:
app.logger.error(f"No repository for {app_name} found")
# Note(decentral1se): should catch this or make more specific error
raise RuntimeError()
try:
clone_path = DATA_DIR / app_name
command = f"git clone {app_repo_url} {clone_path}"
run(split(command))
except Exception as exception:
app.logger.error(f"Failed to run {command}, saw {exception}")
# Note(decentral1se): should catch this or make more specific error
raise RuntimeError()
def get_env_vars(path):
"""Get required env vars from a compose.yml."""
try:
compose_path = Path(path) / "compose.yml"
with open(compose_path, "r") as handle:
compose_yml = handle.read()
except Exception as exception:
app.logger.error(f"Failed to load {compose_path}, saw {exception}")
# Note(decentral1se): should catch this or make more specific error
raise RuntimeError()
# Note(decentral1se): matches all env vars (e.g ${FOO} and returns FOO)
return findall(r"\${(.*?)}", compose_yml)
def get_secrets(path):
"""Get required secrets from a compose.yml."""
try:
compose_path = Path(path) / "compose.yml"
with open(compose_path, "r") as handle:
compose_yml = yaml.load(handle.read())
except Exception as exception:
app.logger.error(f"Failed to load {compose_path}, saw {exception}")
# Note(decentral1se): should catch this or make more specific error
raise RuntimeError()
# Note(decentral1se): retrieves top level names like "db_passwd" not
# "gitea_db_passwd_v1" and I am unsure at this point which is the best to
# bring in but at least the plumbing is there
return [secret for secret in compose_yml["secrets"].keys()]
def get_compose_inputs(path):
"""Retrieve a list of all required inputs for stack deploy."""
return {
"env_vars": get_env_vars(path),
"secrets": get_secrets(path),
}
@app.route("/")
def home():
apps = get_apps()
"""Home page."""
apps = [app_name for app_name in get_apps_json()]
return render_template("index.html", apps=apps)
@app.route("/apps")
def apps_list():
"""The JSON list of all apps."""
return send_from_directory("static", "apps.json")
@app.route("/<app>")
def app_config(app_name):
# TODO(decentral1se): clone the app repository to somewhere under /tmp read
# all the env vars, secrets, etc. and fire up a wtform and deploy button
# using a new app.html template in the templates directory
pass
@app.route("/<app_name>")
def install_app(app_name):
"""Install an app"""
clone_app_template(app_name)
inputs = get_compose_inputs(DATA_DIR / app_name)
for env_var in inputs["env_vars"]:
setattr(InstallAppInputForm, env_var, StringField())
for secret in inputs["secrets"]:
setattr(InstallAppInputForm, secret, StringField())
form = InstallAppInputForm()
return render_template("install.html", app_name=app_name, form=form)
@app.route("/deploy/<app_name>")
def deploy_app(app_name):
return "TODO"
if __name__ == "__main__":
app.run(host="0.0.0.0")
app.run(debug=True, host="0.0.0.0")

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Install {{ app_name }}</title>
</head>
<body>
<p>Install {{ app_name }}</p>
<form method="POST" action="/deploy">
{% for field in form %}
<div>
{{ field.label() }}
{{ field() }}
</div>
{% endfor %}
<input type="submit" value="Deploy" />
</form>
</body>
</html>