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_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
|
||||||
|
@ -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):
|
||||||
|
@ -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
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."""
|
"""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
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."""
|
"""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
|
||||||
|
)
|
||||||
|
@ -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>
|
||||||
|
Reference in New Issue
Block a user