From a2019a32d06fc1d158fd921932e4a14da75f8111 Mon Sep 17 00:00:00 2001 From: Mart van Santen Date: Sat, 24 Sep 2022 01:04:29 +0800 Subject: [PATCH] CLI interface for external apps --- areas/apps/apps.py | 20 +++++++++++++---- areas/apps/apps_service.py | 8 ++++++- areas/apps/models.py | 30 ++++++++++++++++++++++--- cliapp/cliapp/cli.py | 33 ++++++++++++++++++++++++++++ migrations/versions/e08df0bef76f_.py | 30 +++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 migrations/versions/e08df0bef76f_.py diff --git a/areas/apps/apps.py b/areas/apps/apps.py index fe9f30a..7d71ef6 100644 --- a/areas/apps/apps.py +++ b/areas/apps/apps.py @@ -1,7 +1,12 @@ -from flask import jsonify +from flask import jsonify, current_app from flask_jwt_extended import jwt_required from flask_cors import cross_origin +from sqlalchemy import func +from config import * +from .apps_service import AppsService +from database import db + from areas import api_v1 CONFIG_DATA = [ @@ -26,17 +31,24 @@ APP_DATA = {"id": 1, "name": "Nextcloud", "selected": True, "status": "ON for ev APP_NOT_INSTALLED_STATUS = "Not installed" + @api_v1.route('/apps', methods=['GET']) @jwt_required() @cross_origin() def get_apps(): - return jsonify(APPS_DATA) + apps = AppsService.get_all_apps() + for obj in apps: + current_app.logger.info(obj['slug']) + current_app.logger.info(str(obj)) + return jsonify(apps) @api_v1.route('/apps/', methods=['GET']) -@jwt_required() +#@jwt_required() def get_app(slug): - return jsonify(APPS_DATA[0]) + + app = AppsService.get_app(slug) + return jsonify(app) @api_v1.route('/apps', methods=['POST']) diff --git a/areas/apps/apps_service.py b/areas/apps/apps_service.py index 3ab57c1..e5897df 100644 --- a/areas/apps/apps_service.py +++ b/areas/apps/apps_service.py @@ -4,7 +4,13 @@ class AppsService: @staticmethod def get_all_apps(): apps = App.query.all() - return [{"id": app.id, "name": app.name, "slug": app.slug} for app in apps] + return [{"id": app.id, "name": app.name, "slug": app.slug, "external": app.external, "url": app.get_url(), "status": app.get_status()} for app in apps] + + @staticmethod + def get_app(slug): + app = App.query.filter_by(slug=slug).first() + return {"id": app.id, "name": app.name, "slug": app.slug, "external": app.external, "url": app.get_url(), "status": app.get_status()} + @staticmethod def get_app_roles(): diff --git a/areas/apps/models.py b/areas/apps/models.py index f28971e..ea75d52 100644 --- a/areas/apps/models.py +++ b/areas/apps/models.py @@ -2,12 +2,15 @@ import os -from sqlalchemy import ForeignKey, Integer, String +from sqlalchemy import ForeignKey, Integer, String, Boolean from sqlalchemy.orm import relationship from database import db import helpers.kubernetes as k8s -from .apps import APP_NOT_INSTALLED_STATUS +# Circular import, need fixing +#from .apps import APP_NOT_INSTALLED_STATUS + +APP_NOT_INSTALLED_STATUS = "Not installed" class App(db.Model): """ @@ -18,12 +21,31 @@ class App(db.Model): id = db.Column(Integer, primary_key=True) name = db.Column(String(length=64)) slug = db.Column(String(length=64), unique=True) + external = db.Column(Boolean, unique=False, nullable=False, default=True, server_default='0') + url = db.Column(String(length=128), unique=False) def __repr__(self): return f"{self.id} <{self.name}>" + def get_url(self): + """Returns the URL where this application is running""" + + if self.external: + return self.url + + # TODO: Get URL from Kubernetes + return "unknown" + def get_status(self): """Returns a string that describes the app state in the cluster""" + + if self.external: + return("External app") + + + # TODO: Get some kind of caching for those values, as this is called + # on every app list, causing significant delays in the interface + kustomization = self.kustomization if kustomization is not None and "status" in kustomization: ks_ready, ks_message = App.check_condition(kustomization['status']) @@ -78,7 +100,9 @@ class App(db.Model): """ # Delete all roles first for role in self.roles: - role.delete() + db.session.delete(role) + #role.delete() + db.session.commit() db.session.delete(self) return db.session.commit() diff --git a/cliapp/cliapp/cli.py b/cliapp/cliapp/cli.py index 6a689bf..bf0004e 100644 --- a/cliapp/cliapp/cli.py +++ b/cliapp/cliapp/cli.py @@ -71,6 +71,34 @@ def create_app(slug, name): db.session.commit() current_app.logger.info(f"App definition: {name} ({slug}) created") +@app_cli.command("create-external") +@click.argument("slug") +@click.argument("name") +@click.argument("url") +def create_external_app(slug, name, url): + """Create an app for external access + :param slug: str short name of the app + :param name: str name of the application + :param url: str URL of application + """ + + obj = App() + obj.name = name + obj.slug = slug + obj.external = True + obj.url = url + + app_obj = App.query.filter_by(slug=slug).first() + + if app_obj: + current_app.logger.info(f"App definition: {name} ({slug}) already exists in database") + return + + db.session.add(obj) + db.session.commit() + current_app.logger.info(f"App definition: {name} ({slug}) created") + + @app_cli.command("list") @@ -151,6 +179,11 @@ def install_app(slug): current_app.logger.error(f"App {slug} does not exist") return + if app.external: + current_app.logger.info( + f"App {slug} is an external app and can not be provisioned automatically") + return + current_status = app.get_status() if current_status == APP_NOT_INSTALLED_STATUS: app.install() diff --git a/migrations/versions/e08df0bef76f_.py b/migrations/versions/e08df0bef76f_.py new file mode 100644 index 0000000..fdcb18e --- /dev/null +++ b/migrations/versions/e08df0bef76f_.py @@ -0,0 +1,30 @@ +"""Add fields for external apps + +Revision ID: e08df0bef76f +Revises: b514cca2d47b +Create Date: 2022-09-23 16:38:06.557307 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e08df0bef76f' +down_revision = 'b514cca2d47b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('app', sa.Column('external', sa.Boolean(), server_default='0', nullable=False)) + op.add_column('app', sa.Column('url', sa.String(length=128), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('app', 'url') + op.drop_column('app', 'external') + # ### end Alembic commands ###