Hacking towards the second.py functionality
This commit is contained in:
parent
b914d1ae73
commit
4adac0ec20
@ -1,8 +1,9 @@
|
||||
export CELERY_BROKER_URL=redis://localhost:6379
|
||||
export CELERY_RESULT_BACKEND=redis://localhost:6379
|
||||
export FLASK_ENV=development
|
||||
export FLASK_APP=wsgi:app
|
||||
export FLASK_ENV=development
|
||||
export REDIS_HOST=localhost
|
||||
export REDIS_PORT=6379
|
||||
export REDIS_SESSION_DB=0
|
||||
export SECRET_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
export SERVER_PORT=0.0.0.0:8000
|
||||
|
@ -57,9 +57,9 @@ def configure_celery(app):
|
||||
|
||||
def configure_views(app):
|
||||
"""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):
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""The Application settings."""
|
||||
from os import environ, pardir
|
||||
from os.path import abspath, dirname, join
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Base:
|
||||
@ -8,10 +9,10 @@ class Base:
|
||||
|
||||
DEBUG = False
|
||||
JSON_AS_ASCII = False
|
||||
SECRET_KEY = environ["SECRET_KEY"]
|
||||
|
||||
APP_DIR = abspath(dirname(__file__))
|
||||
PROJECT_ROOT = abspath(join(APP_DIR, pardir))
|
||||
SWAGGER_DIR = abspath(join(PROJECT_ROOT, "swagger_docs"))
|
||||
|
||||
REDIS_HOST = environ["REDIS_HOST"]
|
||||
REDIS_PORT = environ["REDIS_PORT"]
|
||||
@ -25,9 +26,9 @@ class Development(Base):
|
||||
"""The Development configuration."""
|
||||
|
||||
ENV = "development"
|
||||
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
DEBUG = True
|
||||
DATA_DIR = Path(Base.PROJECT_ROOT) / "data"
|
||||
|
||||
|
||||
class Testing(Base):
|
||||
@ -41,6 +42,7 @@ class Production(Base):
|
||||
"""The production configuration."""
|
||||
|
||||
ENV = "production"
|
||||
DATA_DIR = "/data"
|
||||
|
||||
|
||||
CONFIG = {
|
||||
|
1
magic_app/docker.py
Normal file
1
magic_app/docker.py
Normal file
@ -0,0 +1 @@
|
||||
"""Docker interaction module"""
|
50
magic_app/forms.py
Normal file
50
magic_app/forms.py
Normal 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
|
@ -1,7 +1,14 @@
|
||||
"""Celery tasks module."""
|
||||
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
|
||||
def hello_world():
|
||||
print("Hello, World")
|
||||
def install_app(app_name: str, form_data) -> None:
|
||||
"""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
29
magic_app/templates.py
Normal 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}"))
|
19
magic_app/templates/app_install.html
Normal file
19
magic_app/templates/app_install.html
Normal 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>
|
17
magic_app/templates/app_list.html
Normal file
17
magic_app/templates/app_list.html
Normal 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>
|
27
magic_app/templates/app_status.html
Normal file
27
magic_app/templates/app_status.html
Normal 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>
|
@ -1,7 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head></head>
|
||||
<body></body>
|
||||
</html>
|
||||
</html>
|
16
magic_app/templates/macros.html
Normal file
16
magic_app/templates/macros.html
Normal 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 %}
|
@ -1,10 +1,44 @@
|
||||
"""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("/")
|
||||
def hello_world():
|
||||
@apps.route("/")
|
||||
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
|
||||
)
|
||||
|
@ -10,10 +10,8 @@
|
||||
<body>
|
||||
<p>Install {{ app_name }}</p>
|
||||
<form method="POST" action="/deploy/{{ app_name }}">
|
||||
{% for field in form %}
|
||||
{{ field.label() }}
|
||||
{{ with_errors(field, style='font-weight: bold') }}
|
||||
{% endfor %}
|
||||
{% for field in form %} {{ field.label() }} {{ with_errors(field,
|
||||
style='font-weight: bold') }} {% endfor %}
|
||||
<input type="submit" value="Deploy" />
|
||||
</form>
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user