Hacking towards the second.py functionality

This commit is contained in:
Luke Murphy 2020-07-05 02:41:06 +02:00
parent b914d1ae73
commit 4adac0ec20
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
14 changed files with 217 additions and 23 deletions

View File

@ -1,8 +1,9 @@
export CELERY_BROKER_URL=redis://localhost:6379 export CELERY_BROKER_URL=redis://localhost:6379
export CELERY_RESULT_BACKEND=redis://localhost:6379 export CELERY_RESULT_BACKEND=redis://localhost:6379
export FLASK_ENV=development
export FLASK_APP=wsgi:app export FLASK_APP=wsgi:app
export FLASK_ENV=development
export REDIS_HOST=localhost export REDIS_HOST=localhost
export REDIS_PORT=6379 export REDIS_PORT=6379
export REDIS_SESSION_DB=0 export REDIS_SESSION_DB=0
export SECRET_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
export SERVER_PORT=0.0.0.0:8000 export SERVER_PORT=0.0.0.0:8000

View File

@ -57,9 +57,9 @@ def configure_celery(app):
def configure_views(app): def configure_views(app):
"""Configure API resource views.""" """Configure API resource views."""
from magic_app.views import home from magic_app.views import apps
app.register_blueprint(home) app.register_blueprint(apps)
def configure_logging(app): def configure_logging(app):

View File

@ -1,6 +1,7 @@
"""The Application settings.""" """The Application settings."""
from os import environ, pardir from os import environ, pardir
from os.path import abspath, dirname, join from os.path import abspath, dirname, join
from pathlib import Path
class Base: class Base:
@ -8,10 +9,10 @@ class Base:
DEBUG = False DEBUG = False
JSON_AS_ASCII = False JSON_AS_ASCII = False
SECRET_KEY = environ["SECRET_KEY"]
APP_DIR = abspath(dirname(__file__)) APP_DIR = abspath(dirname(__file__))
PROJECT_ROOT = abspath(join(APP_DIR, pardir)) PROJECT_ROOT = abspath(join(APP_DIR, pardir))
SWAGGER_DIR = abspath(join(PROJECT_ROOT, "swagger_docs"))
REDIS_HOST = environ["REDIS_HOST"] REDIS_HOST = environ["REDIS_HOST"]
REDIS_PORT = environ["REDIS_PORT"] REDIS_PORT = environ["REDIS_PORT"]
@ -25,9 +26,9 @@ class Development(Base):
"""The Development configuration.""" """The Development configuration."""
ENV = "development" ENV = "development"
CELERY_ALWAYS_EAGER = True CELERY_ALWAYS_EAGER = True
DEBUG = True DEBUG = True
DATA_DIR = Path(Base.PROJECT_ROOT) / "data"
class Testing(Base): class Testing(Base):
@ -41,6 +42,7 @@ class Production(Base):
"""The production configuration.""" """The production configuration."""
ENV = "production" ENV = "production"
DATA_DIR = "/data"
CONFIG = { CONFIG = {

1
magic_app/docker.py Normal file
View File

@ -0,0 +1 @@
"""Docker interaction module"""

50
magic_app/forms.py Normal file
View File

@ -0,0 +1,50 @@
"""Forms for app installation."""
from os import environ
from flask import request
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField
from wtforms.validators import DataRequired, Length
class GiteaInstallForm(FlaskForm):
"""Gitea installation form."""
# "simple"
app_name = StringField("Application name", default="Git with a cup of tea")
domain = StringField("Domain name", validators=[DataRequired()],)
stack_name = StringField("Stack name", default="magic-app-gitea")
# "advanced"
db_host = StringField("Database host", default="mariadb:3306")
db_name = StringField("Database name", default="gitea")
db_type = StringField("Database type", default="mysql")
db_user = StringField("Database user", default="mysql")
ssh_host_port = StringField("SSH host port", default="2225")
# secrets
db_passwd = PasswordField(
"Database password", validators=[DataRequired(), Length(min=32)],
)
db_root_passwd = PasswordField(
"Database root password", validators=[DataRequired(), Length(min=32)],
)
internal_token = PasswordField(
"Internal secret token", validators=[DataRequired(), Length(min=105)],
)
jwt_secret = PasswordField(
"JWT secret", validators=[DataRequired(), Length(min=43)],
)
secret_key = PasswordField(
"Secret key", validators=[DataRequired(), Length(min=64)],
)
def form_to_env(app_name, request_form):
"""Load form data into a environment."""
env = environ.copy()
for key in request_form.keys():
env[key.upper()] = request.form[key]
return env

View File

@ -1,7 +1,14 @@
"""Celery tasks module.""" """Celery tasks module."""
from magic_app.app import celery from magic_app.app import celery
from magic_app.forms import form_to_env
from magic_app.templates import clone_app_template, create_data_dir
@celery.task @celery.task
def hello_world(): def install_app(app_name: str, form_data) -> None:
print("Hello, World") """Install an application."""
create_data_dir()
clone_app_template(app_name)
# Note(decentral1se): this is where I left off...
env = form_to_env() # noqa

29
magic_app/templates.py Normal file
View File

@ -0,0 +1,29 @@
"""Compose template handling."""
from os import mkdir
from os.path import exists
from shlex import split
from shutil import rmtree
from subprocess import run
from flask import current_app
APP_TEMPLATES = {
"gitea": "https://git.autonomic.zone/compose-stacks/gitea",
}
def create_data_dir() -> None:
"""Create data directory for compose templates."""
try:
mkdir(current_app.config["DATA_DIR"])
except FileExistsError:
pass
def clone_app_template(app_name: str) -> None:
"""Clone an application template repository."""
clone_path = current_app.config["DATA_DIR"] / app_name
clone_url = APP_TEMPLATES[app_name]
if exists(clone_path):
rmtree(clone_path)
run(split(f"git clone {clone_url} {clone_path}"))

View File

@ -0,0 +1,19 @@
{% from "macros.html" import with_errors %}
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<title>Install {{ app_name | capitalize }}</title>
</head>
<body>
<p>Install {{ app_name | capitalize }}</p>
<form method="POST" action="{{ url_for("apps.install", app_name=app_name) }}">
{% for field in form %}
{{ field.label() }} {{ with_errors(field, style='font-weight: bold') }}
{% endfor %}
<input type="submit" value="Install" />
</form>
</body>
</html>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<title>Application Listing</title>
</head>
<body>
<ul>
{% for app in apps %}
<li>
<a href="{{ url_for('apps.install', app_name=app) }}">{{ app }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<title>{{ app_name | capitalize }} Status</title>
<style>
table,
th,
td {
border: 1px solid black;
}
</style>
</head>
<body>
<table>
<tr>
<th>App</th>
<th>Status</th>
</tr>
<tr>
<th>{{ app_name }}</th>
<th>{{ status }}</th>
</tr>
</table>
</body>
</html>
</html>

View File

@ -1,7 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head></head>
<body></body>
</html>
</html>

View File

@ -0,0 +1,16 @@
{#
From: https://wtforms.readthedocs.io/en/2.3.x/specific_problems/#rendering-errors
Usage: with_errors(form.field, style='font-weight: bold')
#}
{% macro with_errors(field) %}
<div class="form_field">
{% if field.errors %}
{% set css_class = 'has_error ' + kwargs.pop('class', '') %}
{{ field(class=css_class, **kwargs) }}
<ul class="errors">{% for error in field.errors %}<li>{{ error|e }}</li>{% endfor %}</ul>
{% else %}
{{ field(**kwargs) }}
{% endif %}
</div>
{% endmacro %}

View File

@ -1,10 +1,44 @@
"""View routing.""" """View routing."""
from flask import Blueprint from flask import Blueprint, redirect, render_template, url_for
home = Blueprint("home", __name__) from magic_app.forms import GiteaInstallForm
apps = Blueprint("apps", __name__)
@home.route("/") @apps.route("/")
def hello_world(): def listing():
return render_template("app_list.html", apps=["gitea"])
return "Hello, World"
@apps.route("/install/<app_name>", methods=("GET", "POST"))
def install(app_name):
"""Install an application."""
from magic_app.tasks import install_app
if app_name == "gitea":
form = GiteaInstallForm()
if form.validate_on_submit():
install_app.apply_async(args=[app_name])
return redirect(url_for("apps.status", app_name=app_name))
return render_template("app_install.html", app_name=app_name, form=form)
@apps.route("/install-test/<app_name>")
def install_test(app_name):
"""Development aid to quickly test installation logic."""
from magic_app.tasks import install_app
install_app.apply_async(args=[app_name])
return f"<a href='{app_name}'>Try again?</a>"
@apps.route("/status/<app_name>")
def status(app_name):
"""Show status of applications."""
return render_template(
"app_status.html", status="UNKNOWN", app_name=app_name
)

View File

@ -10,10 +10,8 @@
<body> <body>
<p>Install {{ app_name }}</p> <p>Install {{ app_name }}</p>
<form method="POST" action="/deploy/{{ app_name }}"> <form method="POST" action="/deploy/{{ app_name }}">
{% for field in form %} {% for field in form %} {{ field.label() }} {{ with_errors(field,
{{ field.label() }} style='font-weight: bold') }} {% endfor %}
{{ with_errors(field, style='font-weight: bold') }}
{% endfor %}
<input type="submit" value="Deploy" /> <input type="submit" value="Deploy" />
</form> </form>
</body> </body>