CLI interface for external apps
This commit is contained in:
parent
6e427f275f
commit
a2019a32d0
5 changed files with 113 additions and 8 deletions
|
@ -1,7 +1,12 @@
|
||||||
from flask import jsonify
|
from flask import jsonify, current_app
|
||||||
from flask_jwt_extended import jwt_required
|
from flask_jwt_extended import jwt_required
|
||||||
from flask_cors import cross_origin
|
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
|
from areas import api_v1
|
||||||
|
|
||||||
CONFIG_DATA = [
|
CONFIG_DATA = [
|
||||||
|
@ -26,17 +31,24 @@ APP_DATA = {"id": 1, "name": "Nextcloud", "selected": True, "status": "ON for ev
|
||||||
|
|
||||||
APP_NOT_INSTALLED_STATUS = "Not installed"
|
APP_NOT_INSTALLED_STATUS = "Not installed"
|
||||||
|
|
||||||
|
|
||||||
@api_v1.route('/apps', methods=['GET'])
|
@api_v1.route('/apps', methods=['GET'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
def get_apps():
|
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/<string:slug>', methods=['GET'])
|
@api_v1.route('/apps/<string:slug>', methods=['GET'])
|
||||||
@jwt_required()
|
#@jwt_required()
|
||||||
def get_app(slug):
|
def get_app(slug):
|
||||||
return jsonify(APPS_DATA[0])
|
|
||||||
|
app = AppsService.get_app(slug)
|
||||||
|
return jsonify(app)
|
||||||
|
|
||||||
|
|
||||||
@api_v1.route('/apps', methods=['POST'])
|
@api_v1.route('/apps', methods=['POST'])
|
||||||
|
|
|
@ -4,7 +4,13 @@ class AppsService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_apps():
|
def get_all_apps():
|
||||||
apps = App.query.all()
|
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
|
@staticmethod
|
||||||
def get_app_roles():
|
def get_app_roles():
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey, Integer, String
|
from sqlalchemy import ForeignKey, Integer, String, Boolean
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from database import db
|
from database import db
|
||||||
import helpers.kubernetes as k8s
|
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):
|
class App(db.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -18,12 +21,31 @@ class App(db.Model):
|
||||||
id = db.Column(Integer, primary_key=True)
|
id = db.Column(Integer, primary_key=True)
|
||||||
name = db.Column(String(length=64))
|
name = db.Column(String(length=64))
|
||||||
slug = db.Column(String(length=64), unique=True)
|
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):
|
def __repr__(self):
|
||||||
return f"{self.id} <{self.name}>"
|
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):
|
def get_status(self):
|
||||||
"""Returns a string that describes the app state in the cluster"""
|
"""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
|
kustomization = self.kustomization
|
||||||
if kustomization is not None and "status" in kustomization:
|
if kustomization is not None and "status" in kustomization:
|
||||||
ks_ready, ks_message = App.check_condition(kustomization['status'])
|
ks_ready, ks_message = App.check_condition(kustomization['status'])
|
||||||
|
@ -78,7 +100,9 @@ class App(db.Model):
|
||||||
"""
|
"""
|
||||||
# Delete all roles first
|
# Delete all roles first
|
||||||
for role in self.roles:
|
for role in self.roles:
|
||||||
role.delete()
|
db.session.delete(role)
|
||||||
|
#role.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
return db.session.commit()
|
return db.session.commit()
|
||||||
|
|
|
@ -71,6 +71,34 @@ def create_app(slug, name):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
current_app.logger.info(f"App definition: {name} ({slug}) created")
|
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")
|
@app_cli.command("list")
|
||||||
|
@ -151,6 +179,11 @@ def install_app(slug):
|
||||||
current_app.logger.error(f"App {slug} does not exist")
|
current_app.logger.error(f"App {slug} does not exist")
|
||||||
return
|
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()
|
current_status = app.get_status()
|
||||||
if current_status == APP_NOT_INSTALLED_STATUS:
|
if current_status == APP_NOT_INSTALLED_STATUS:
|
||||||
app.install()
|
app.install()
|
||||||
|
|
30
migrations/versions/e08df0bef76f_.py
Normal file
30
migrations/versions/e08df0bef76f_.py
Normal file
|
@ -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 ###
|
Loading…
Reference in a new issue