Compare commits

...

3 Commits

Author SHA1 Message Date
Ferdinand Thiessen 2880076705 [cli] Fix exporting of plugin interfaces
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-07-31 19:07:40 +02:00
Ferdinand Thiessen fa503fe142 [cli] Added install command to install the database and all plugins
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-07-31 19:07:40 +02:00
Ferdinand Thiessen f1d6b6a2f2 [plugins][cli] Fix initial migration file + Make sure plugin permissions are installed
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-07-31 19:07:32 +02:00
13 changed files with 108 additions and 43 deletions

View File

@ -50,10 +50,16 @@ If not you need to create user and database manually do (or similar on Windows):
) | sudo mysql ) | sudo mysql
Then you can install the database tables, this will update all tables from core + all enabled plugins. Then you can install the database tables, this will update all tables from core + all enabled plugins.
*Hint:* The same command can be later used to upgrade the database after plugins or core are updated. And also install all enabled plugins:
$ flaschengeist install
*Hint:* To only install the database tables, or upgrade the database after plugins or core are updated later
you can use this command:
$ flaschengeist db upgrade heads $ flaschengeist db upgrade heads
## Plugins ## Plugins
To only upgrade one plugin (for example the `events` plugin): To only upgrade one plugin (for example the `events` plugin):
@ -88,6 +94,7 @@ with the difference of the main logger will be forced to output to `stderr` and
of the CLI will override the logging level you have configured for the main logger. of the CLI will override the logging level you have configured for the main logger.
$ flaschengeist run $ flaschengeist run
or with debug messages: or with debug messages:
$ flaschengeist run --debug $ flaschengeist run --debug

View File

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

View File

@ -18,14 +18,21 @@ depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### op.create_table(
"plugin_setting",
sa.Column("id", flaschengeist.models.Serial(), nullable=False),
sa.Column("plugin", sa.String(length=127), nullable=True),
sa.Column("name", sa.String(length=127), nullable=False),
sa.Column("value", sa.PickleType(protocol=4), nullable=True),
sa.PrimaryKeyConstraint("id", name=op.f("pk_plugin_setting")),
)
op.create_table( op.create_table(
"image", "image",
sa.Column("id", flaschengeist.models.Serial(), nullable=False), sa.Column("id", flaschengeist.models.Serial(), nullable=False),
sa.Column("filename_", sa.String(length=127), nullable=False), sa.Column("filename", sa.String(length=255), nullable=False),
sa.Column("mimetype_", sa.String(length=30), nullable=False), sa.Column("mimetype", sa.String(length=127), nullable=False),
sa.Column("thumbnail_", sa.String(length=127), nullable=True), sa.Column("thumbnail", sa.String(length=255), nullable=True),
sa.Column("path_", sa.String(length=127), nullable=True), sa.Column("path", sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint("id", name=op.f("pk_image")), sa.PrimaryKeyConstraint("id", name=op.f("pk_image")),
) )
op.create_table( op.create_table(

View File

@ -88,12 +88,14 @@ def main(*args, **kwargs):
from .export_cmd import export from .export_cmd import export
from .docs_cmd import docs from .docs_cmd import docs
from .run_cmd import run from .run_cmd import run
from .install_cmd import install
# Override logging level # Override logging level
environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX)) environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX))
cli.add_command(export) cli.add_command(export)
cli.add_command(docs) cli.add_command(docs)
cli.add_command(install)
cli.add_command(plugin) cli.add_command(plugin)
cli.add_command(run) cli.add_command(run)
cli(*args, **kwargs) cli(*args, **kwargs)

View File

@ -1,4 +1,5 @@
import click import click
from importlib_metadata import entry_points
@click.command() @click.command()
@ -8,14 +9,21 @@ import click
@click.option("--no-core", help="Skip models / types from flaschengeist core", is_flag=True) @click.option("--no-core", help="Skip models / types from flaschengeist core", is_flag=True)
def export(namespace, output, no_core, plugin): def export(namespace, output, no_core, plugin):
from flaschengeist import logger, models from flaschengeist import logger, models
from flaschengeist.app import get_plugins
from .InterfaceGenerator import InterfaceGenerator from .InterfaceGenerator import InterfaceGenerator
gen = InterfaceGenerator(namespace, output, logger) gen = InterfaceGenerator(namespace, output, logger)
if not no_core: if not no_core:
gen.run(models) gen.run(models)
if plugin: if plugin:
for plugin_class in get_plugins(): for entry_point in entry_points(group="flaschengeist.plugins"):
if (len(plugin) == 0 or plugin_class.id in plugin) and plugin_class.models is not None: if len(plugin) == 0 or entry_point.name in plugin:
gen.run(plugin_class.models) try:
plugin = entry_point.load()
gen.run(plugin.models)
except:
logger.error(
f"Plugin {entry_point.name} could not be loaded due to an error.",
exc_info=True,
)
continue
gen.write() gen.write()

View File

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

View File

@ -4,7 +4,9 @@ from flask import current_app
from flask.cli import with_appcontext from flask.cli import with_appcontext
from importlib_metadata import EntryPoint, entry_points from importlib_metadata import EntryPoint, entry_points
from flaschengeist.database import db
from flaschengeist.config import config from flaschengeist.config import config
from flaschengeist.models.user import Permission
@click.group() @click.group()
@ -12,6 +14,35 @@ def plugin():
pass pass
def install_plugin_command(ctx, plugin, all):
"""Install one or more plugins"""
if not all and len(plugin) == 0:
ctx.fail("At least one plugin must be specified, or use `--all` flag.")
if all:
plugins = current_app.config["FG_PLUGINS"]
else:
try:
plugins = {plugin_name: current_app.config["FG_PLUGINS"][plugin_name] for plugin_name in plugin}
except KeyError as e:
ctx.fail(f"Invalid plugin name, could not find >{e.args[0]}<")
for name, plugin in plugins.items():
click.echo(f"Installing {name}{'.'*(20-len(name))}", nl=False)
# Install permissions
if plugin.permissions:
cur_perm = set(x.name for x in Permission.query.filter(Permission.name.in_(plugin.permissions)).all())
all_perm = set(plugin.permissions)
add = all_perm - cur_perm
if add:
db.session.bulk_save_objects([Permission(name=x) for x in all_perm])
db.session.commit()
# Custom installation steps
plugin.install()
click.secho(" ok", fg="green")
@plugin.command() @plugin.command()
@click.argument("plugin", nargs=-1, type=str) @click.argument("plugin", nargs=-1, type=str)
@click.option("--all", help="Install all enabled plugins", is_flag=True) @click.option("--all", help="Install all enabled plugins", is_flag=True)
@ -19,20 +50,7 @@ def plugin():
@pass_context @pass_context
def install(ctx, plugin, all): def install(ctx, plugin, all):
"""Install one or more plugins""" """Install one or more plugins"""
if not all and len(plugin) == 0: return install_plugin_command(ctx, plugin, all)
ctx.fail("At least one plugin must be specified, or use `--all` flag.")
if all:
plugins = current_app.config["FG_PLUGINS"].values()
else:
try:
plugins = [current_app.config["FG_PLUGINS"][p] for p in plugin]
except KeyError as e:
ctx.fail(f"Invalid plugin ID, could not find >{e.args[0]}<")
for p in plugins:
name = p.id.split(".")[-1]
click.echo(f"Installing {name}{'.'*(20-len(name))}", nl=False)
p.install()
click.secho(" ok", fg="green")
@plugin.command() @plugin.command()
@ -44,14 +62,16 @@ def uninstall(ctx: click.Context, plugin):
if len(plugin) == 0: if len(plugin) == 0:
ctx.fail("At least one plugin must be specified") ctx.fail("At least one plugin must be specified")
try: try:
plugins = [current_app.config["FG_PLUGINS"][p] for p in plugin] plugins = {plugin_name: current_app.config["FG_PLUGINS"][plugin_name] for plugin_name in plugin}
except KeyError as e: except KeyError as e:
ctx.fail(f"Invalid plugin ID, could not find >{e.args[0]}<") ctx.fail(f"Invalid plugin ID, could not find >{e.args[0]}<")
if ( if (
click.prompt( click.prompt(
"You are going to uninstall:\n\n" "You are going to uninstall:\n\n"
f"\t{', '.join([p.id.split('.')[-1] for p in plugins])}\n\n" f"\t{', '.join([plugin_name for plugin_name in plugins.keys()])}\n\n"
"Are you sure?", "Are you sure?",
default="n", default="n",
show_choices=True, show_choices=True,
@ -60,10 +80,9 @@ def uninstall(ctx: click.Context, plugin):
!= "y" != "y"
): ):
ctx.exit() ctx.exit()
for p in plugins: for name, plugin in plugins.items():
name = p.id.split(".")[-1]
click.echo(f"Uninstalling {name}{'.'*(20-len(name))}", nl=False) click.echo(f"Uninstalling {name}{'.'*(20-len(name))}", nl=False)
p.uninstall() plugin.uninstall()
click.secho(" ok", fg="green") click.secho(" ok", fg="green")
@ -78,7 +97,7 @@ def ls(enabled, no_header):
return p.version return p.version
plugins = entry_points(group="flaschengeist.plugins") plugins = entry_points(group="flaschengeist.plugins")
enabled_plugins = [key for key, value in config.items() if "enabled" in value] + [config["FLASCHENGEIST"]["auth"]] enabled_plugins = [key for key, value in config.items() if "enabled" in value and value["enabled"]] + [config["FLASCHENGEIST"]["auth"]]
loaded_plugins = current_app.config["FG_PLUGINS"].keys() loaded_plugins = current_app.config["FG_PLUGINS"].keys()
if not no_header: if not no_header:

View File

@ -9,11 +9,11 @@ from ..database import db
class Image(db.Model, ModelSerializeMixin): class Image(db.Model, ModelSerializeMixin):
__tablename__ = "image" __tablename__ = "image"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
filename_: str = db.Column(db.String(127), nullable=False) filename_: str = db.Column("filename", db.String(255), nullable=False)
mimetype_: str = db.Column(db.String(30), nullable=False) mimetype_: str = db.Column("mimetype", db.String(127), nullable=False)
thumbnail_: str = db.Column(db.String(127)) thumbnail_: str = db.Column("thumbnail", db.String(255))
path_: str = db.Column(db.String(127)) path_: str = db.Column("path", db.String(255))
def open(self): def open(self):
return open(self.path_, "rb") return open(self.path_, "rb")

View File

@ -8,6 +8,6 @@ from ..database import db
class _PluginSetting(db.Model): class _PluginSetting(db.Model):
__tablename__ = "plugin_setting" __tablename__ = "plugin_setting"
id = db.Column("id", Serial, primary_key=True) id = db.Column("id", Serial, primary_key=True)
plugin: str = db.Column(db.String(30)) plugin: str = db.Column(db.String(127))
name: str = db.Column(db.String(30), nullable=False) name: str = db.Column(db.String(127), nullable=False)
value: Any = db.Column(db.PickleType(protocol=4)) value: Any = db.Column(db.PickleType(protocol=4))

View File

@ -126,7 +126,8 @@ class Plugin:
def install(self): def install(self):
"""Installation routine """Installation routine
Is always called with Flask application context Is always called with Flask application context,
it is called after the plugin permissions are installed.
""" """
pass pass

View File

@ -13,7 +13,6 @@ from flaschengeist.controller import sessionController, userController
class AuthRoutePlugin(Plugin): class AuthRoutePlugin(Plugin):
id = "dev.flaschengeist.auth"
blueprint = Blueprint("auth", __name__) blueprint = Blueprint("auth", __name__)

View File

@ -14,12 +14,10 @@ from flaschengeist import logger
class AuthPlain(AuthPlugin): class AuthPlain(AuthPlugin):
id = "auth_plain"
def install(self): def install(self):
plugins_installed(self.post_install) plugins_installed(self.post_install)
def post_install(self, **kwargs): def post_install(self, *args, **kwargs):
if User.query.filter(User.deleted == False).count() == 0: if User.query.filter(User.deleted == False).count() == 0:
logger.info("Installing admin user") logger.info("Installing admin user")
role = Role.query.filter(Role.name == "Superuser").first() role = Role.query.filter(Role.name == "Superuser").first()

View File

@ -104,7 +104,7 @@ def frontend(userid, current_session):
raise Forbidden raise Forbidden
if request.method == "POST": if request.method == "POST":
if request.content_length > 1024**2: if request.content_length > 1024 ** 2:
raise BadRequest raise BadRequest
current_session.user_.set_attribute("frontend", request.get_json()) current_session.user_.set_attribute("frontend", request.get_json())
return no_content() return no_content()