Compare commits

..

1 Commits

Author SHA1 Message Date
Ferdinand Thiessen 74ca9b247d The enabled state of plugins is now loaded from database rather than config file
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-08-18 20:01:33 +02:00
8 changed files with 29 additions and 49 deletions

View File

@ -1,5 +1,4 @@
from pathlib import Path from pathlib import Path
alembic_migrations_path = str(Path(__file__).resolve().parent / "migrations") alembic_migrations = str(Path(__file__).resolve().parent / "migrations")
alembic_script_path = str(Path(__file__).resolve().parent)

View File

@ -5,10 +5,11 @@ from flask_cors import CORS
from datetime import datetime, date from datetime import datetime, date
from flask.json import JSONEncoder, jsonify from flask.json import JSONEncoder, jsonify
from importlib.metadata import entry_points from importlib.metadata import entry_points
from sqlalchemy.exc import OperationalError
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.controller import pluginController from flaschengeist.models import Plugin
from flaschengeist.utils.hook import Hook from flaschengeist.utils.hook import Hook
from flaschengeist.config import configure_app from flaschengeist.config import configure_app
@ -37,9 +38,11 @@ class CustomJSONEncoder(JSONEncoder):
@Hook("plugins.loaded") @Hook("plugins.loaded")
def load_plugins(app: Flask): def load_plugins(app: Flask):
app.config["FG_PLUGINS"] = {} app.config["FG_PLUGINS"] = {}
enabled_plugins = Plugin.query.filter(Plugin.enabled == True).all()
all_plugins = entry_points(group="flaschengeist.plugins") all_plugins = entry_points(group="flaschengeist.plugins")
for plugin in pluginController.get_enabled_plugins(): for plugin in enabled_plugins:
logger.debug(f"Searching for enabled plugin {plugin.name}") logger.debug(f"Searching for enabled plugin {plugin.name}")
entry_point = all_plugins.select(name=plugin.name) entry_point = all_plugins.select(name=plugin.name)
if not entry_point: if not entry_point:
@ -49,7 +52,7 @@ def load_plugins(app: Flask):
) )
continue continue
try: try:
loaded = entry_point[0].load()(entry_point[0]) loaded = entry_point.load()(entry_point)
if hasattr(plugin, "blueprint") and plugin.blueprint is not None: if hasattr(plugin, "blueprint") and plugin.blueprint is not None:
app.register_blueprint(plugin.blueprint) app.register_blueprint(plugin.blueprint)
except: except:

View File

@ -3,7 +3,7 @@ from click.decorators import pass_context
from flask.cli import with_appcontext from flask.cli import with_appcontext
from flask_migrate import upgrade from flask_migrate import upgrade
from flaschengeist.alembic import alembic_migrations_path from flaschengeist.alembic import alembic_migrations
from flaschengeist.cli.plugin_cmd import install_plugin_command from flaschengeist.cli.plugin_cmd import install_plugin_command
from flaschengeist.utils.hook import Hook from flaschengeist.utils.hook import Hook
@ -14,7 +14,7 @@ from flaschengeist.utils.hook import Hook
@Hook("plugins.installed") @Hook("plugins.installed")
def install(ctx): def install(ctx):
# Install database # Install database
upgrade(alembic_migrations_path, revision="heads") upgrade(alembic_migrations, revision="heads")
# Install plugins # Install plugins
install_plugin_command(ctx, [], True) install_plugin_command(ctx, [], True)

View File

@ -8,35 +8,14 @@ import sqlalchemy
from typing import Union from typing import Union
from flask import current_app from flask import current_app
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from sqlalchemy.exc import OperationalError
from importlib.metadata import entry_points from importlib.metadata import entry_points
from .. import logger from .. import logger
from ..database import db from ..database import db
from ..utils.hook import Hook from ..utils import Hook
from ..models import Plugin, PluginSetting, Notification from ..models import Plugin, PluginSetting, Notification
def get_enabled_plugins():
try:
enabled_plugins = Plugin.query.filter(Plugin.enabled == True).all()
except OperationalError as e:
class PluginStub:
def __init__(self, name) -> None:
self.name = name
logger.error("Could not connect to database or database not initialized! No plugins enabled!")
logger.debug("Can not query enabled plugins", exc_info=True)
enabled_plugins = [
PluginStub("auth"),
PluginStub("roles"),
PluginStub("users"),
PluginStub("scheduler"),
]
return enabled_plugins
def get_setting(plugin_id: str, name: str, **kwargs): def get_setting(plugin_id: str, name: str, **kwargs):
"""Get plugin setting from database """Get plugin setting from database
@ -50,7 +29,9 @@ def get_setting(plugin_id: str, name: str, **kwargs):
`KeyError` if no such setting exists in the database `KeyError` if no such setting exists in the database
""" """
try: try:
setting = PluginSetting.query.filter(PluginSetting.plugin == plugin_id).filter(PluginSetting.name == name).one() setting = (
PluginSetting.query.filter(PluginSetting.plugin == plugin_id).filter(PluginSetting.name == name).one()
)
return setting.value return setting.value
except sqlalchemy.orm.exc.NoResultFound: except sqlalchemy.orm.exc.NoResultFound:
if "default" in kwargs: if "default" in kwargs:
@ -68,7 +49,9 @@ def set_setting(plugin_id: str, name: str, value):
value: Value to be stored value: Value to be stored
""" """
setting = ( setting = (
PluginSetting.query.filter(PluginSetting.plugin == plugin_id).filter(PluginSetting.name == name).one_or_none() PluginSetting.query.filter(PluginSetting.plugin == plugin_id)
.filter(PluginSetting.name == name)
.one_or_none()
) )
if setting is not None: if setting is not None:
if value is None: if value is None:

View File

@ -4,9 +4,7 @@ from flask_sqlalchemy import SQLAlchemy
from importlib.metadata import EntryPoint, entry_points, distribution from importlib.metadata import EntryPoint, entry_points, distribution
from sqlalchemy import MetaData from sqlalchemy import MetaData
from flaschengeist.alembic import alembic_script_path
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.controller import pluginController
# https://alembic.sqlalchemy.org/en/latest/naming.html # https://alembic.sqlalchemy.org/en/latest/naming.html
metadata = MetaData( metadata = MetaData(
@ -33,26 +31,25 @@ def configure_alembic(config: Config):
uninstall can break the alembic version management. uninstall can break the alembic version management.
""" """
# Set main script location # Set main script location
config.set_main_option("script_location", alembic_script_path) config.set_main_option(
"script_location", str(distribution("flaschengeist").locate_file("") / "flaschengeist" / "alembic")
)
# Set Flaschengeist's migrations # Set Flaschengeist's migrations
migrations = [config.get_main_option("script_location") + "/migrations"] migrations = [config.get_main_option("script_location") + "/migrations"]
# Gather all migration paths # Gather all migration paths
all_plugins = entry_points(group="flaschengeist.plugins") ep: EntryPoint
for plugin in pluginController.get_enabled_plugins(): for ep in entry_points(group="flaschengeist.plugins"):
entry_point = all_plugins.select(name=plugin.name)
if not entry_point:
continue
try: try:
directory = entry_point.dist.locate_file("") directory = ep.dist.locate_file("")
for loc in entry_point.module.split(".") + ["migrations"]: for loc in ep.module.split(".") + ["migrations"]:
directory /= loc directory /= loc
if directory.exists(): if directory.exists():
logger.debug(f"Adding migration version path {directory}") logger.debug(f"Adding migration version path {directory}")
migrations.append(str(directory.resolve())) migrations.append(str(directory.resolve()))
except: except:
logger.warning(f"Could not load migrations of plugin {plugin.name} for database migration.") logger.warning(f"Could not load migrations of plugin {ep.name} for database migration.")
logger.debug("Plugin loading failed", exc_info=True) logger.debug("Plugin loading failed", exc_info=True)
# write back seperator (we changed it if neither seperator nor locations were specified) # write back seperator (we changed it if neither seperator nor locations were specified)

View File

@ -2,4 +2,4 @@ from .session import *
from .user import * from .user import *
from .plugin import * from .plugin import *
from .notification import * from .notification import *
from .image import * from .image import *

View File

@ -17,7 +17,6 @@ role_permission_association_table = db.Table(
db.Column("permission_id", Serial, db.ForeignKey("permission.id")), db.Column("permission_id", Serial, db.ForeignKey("permission.id")),
) )
class Permission(db.Model, ModelSerializeMixin): class Permission(db.Model, ModelSerializeMixin):
__tablename__ = "permission" __tablename__ = "permission"
name: str = db.Column(db.String(30), unique=True) name: str = db.Column(db.String(30), unique=True)

View File

@ -2,7 +2,6 @@ from flask import Blueprint
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.config import config
from flaschengeist.plugins import BasePlugin from flaschengeist.plugins import BasePlugin
from flaschengeist.utils.HTTP import no_content from flaschengeist.utils.HTTP import no_content
@ -39,8 +38,8 @@ def scheduled(id: str, replace=False, **kwargs):
class SchedulerPlugin(BasePlugin): class SchedulerPlugin(BasePlugin):
def __init__(self, entry_point): def __init__(self, entry_point, config=None):
super().__init__(entry_point) super().__init__(entry_point, config)
self.blueprint = Blueprint(self.name, __name__) self.blueprint = Blueprint(self.name, __name__)
def __view_func(): def __view_func():
@ -53,9 +52,9 @@ class SchedulerPlugin(BasePlugin):
except: except:
logger.error("Error while executing scheduled tasks!", exc_info=True) logger.error("Error while executing scheduled tasks!", exc_info=True)
cron = config.get("scheduler", {}).get("cron", "passive_web").lower() cron = None if config is None else config.get("cron", "passive_web").lower()
if cron == "passive_web": if cron is None or cron == "passive_web":
self.blueprint.teardown_app_request(__passiv_func) self.blueprint.teardown_app_request(__passiv_func)
elif cron == "active_web": elif cron == "active_web":
self.blueprint.add_url_rule("/cron", view_func=__view_func) self.blueprint.add_url_rule("/cron", view_func=__view_func)