diff --git a/.envrc.sample b/.envrc.sample index 991560b..5f58940 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -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 diff --git a/magic_app/app.py b/magic_app/app.py index dda2f14..bd6672d 100644 --- a/magic_app/app.py +++ b/magic_app/app.py @@ -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): diff --git a/magic_app/config.py b/magic_app/config.py index bf04d1f..cf980af 100644 --- a/magic_app/config.py +++ b/magic_app/config.py @@ -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 = { diff --git a/magic_app/docker.py b/magic_app/docker.py new file mode 100644 index 0000000..235eb61 --- /dev/null +++ b/magic_app/docker.py @@ -0,0 +1 @@ +"""Docker interaction module""" diff --git a/magic_app/forms.py b/magic_app/forms.py new file mode 100644 index 0000000..00c6efb --- /dev/null +++ b/magic_app/forms.py @@ -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 diff --git a/magic_app/tasks.py b/magic_app/tasks.py index 297063c..edd5af9 100644 --- a/magic_app/tasks.py +++ b/magic_app/tasks.py @@ -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 diff --git a/magic_app/templates.py b/magic_app/templates.py new file mode 100644 index 0000000..b66027f --- /dev/null +++ b/magic_app/templates.py @@ -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}")) diff --git a/magic_app/templates/app_install.html b/magic_app/templates/app_install.html new file mode 100644 index 0000000..764d59a --- /dev/null +++ b/magic_app/templates/app_install.html @@ -0,0 +1,19 @@ +{% from "macros.html" import with_errors %} + + + + +
+Install {{ app_name | capitalize }}
+ + + + diff --git a/magic_app/templates/app_list.html b/magic_app/templates/app_list.html new file mode 100644 index 0000000..167c106 --- /dev/null +++ b/magic_app/templates/app_list.html @@ -0,0 +1,17 @@ + + + + +App | +Status | +
---|---|
{{ app_name }} | +{{ status }} | +
Install {{ app_name }}