[plugins][cli] Fix initial migration file + Make sure plugin permissions are installed

Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2022-07-31 13:22:11 +02:00
parent 573bea2da0
commit 47b838804b
9 changed files with 65 additions and 34 deletions

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

@ -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

@ -19,7 +19,7 @@ class AuthPlain(AuthPlugin):
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

@ -16,6 +16,7 @@ from . import permissions
class RolesPlugin(Plugin): class RolesPlugin(Plugin):
id = "roles"
blueprint = Blueprint("roles", __name__) blueprint = Blueprint("roles", __name__)
permissions = permissions.permissions permissions = permissions.permissions

View File

@ -39,6 +39,8 @@ def scheduled(id: str, replace=False, **kwargs):
class SchedulerPlugin(Plugin): class SchedulerPlugin(Plugin):
id = "scheduler"
def __init__(self, entry_point, config=None): def __init__(self, entry_point, config=None):
super().__init__(entry_point, config) super().__init__(entry_point, config)
self.blueprint = Blueprint(self.name, __name__) self.blueprint = Blueprint(self.name, __name__)

View File

@ -18,6 +18,7 @@ from flaschengeist.utils.datetime import from_iso_format
class UsersPlugin(Plugin): class UsersPlugin(Plugin):
id = "users"
blueprint = Blueprint("users", __name__) blueprint = Blueprint("users", __name__)
permissions = permissions.permissions permissions = permissions.permissions