From d8221e73a01a28f89fd2aba3e970cd5dc3913c93 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Fri, 2 May 2025 17:06:16 +0800 Subject: [PATCH 1/7] feat(auto upgrade): add upgrade setting --- api/models/account.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/models/account.py b/api/models/account.py index bb6a2a4735..b4037ddedb 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -296,3 +296,29 @@ class TenantPluginPermission(Base): db.String(16), nullable=False, server_default="everyone" ) debug_permission: Mapped[DebugPermission] = mapped_column(db.String(16), nullable=False, server_default="noone") + +class TenantPluginAutoUpgradeStrategy(Base): + class StrategyOption(enum.StrEnum): + DISABLED = "disabled" + FIX_ONLY = "fix_only" + LATEST = "latest" + + class UpgradeMode(enum.StrEnum): + PARTIAL = "partial" + EXCLUDE = "exclude" + + __tablename__ = "tenant_plugin_auto_upgrade_strategies" + __table_args__ = ( + db.PrimaryKeyConstraint("id", name="tenant_plugin_auto_upgrade_strategy_pkey"), + db.UniqueConstraint("tenant_id", name="unique_tenant_plugin_auto_upgrade_strategy"), + ) + + id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + strategy_option: Mapped[StrategyOption] = mapped_column(db.String(16), nullable=False) + upgrade_time_of_day: Mapped[int] = mapped_column(db.Integer, nullable=False) + upgrade_mode: Mapped[UpgradeMode] = mapped_column(db.String(16), nullable=False) + exclude_plugins: Mapped[list[str]] = mapped_column(db.ARRAY(db.String(255)), nullable=False) + include_plugins: Mapped[list[str]] = mapped_column(db.ARRAY(db.String(255)), nullable=False) + created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) From abed88f8cba130ece94581a6c26e95e3dec0a8f8 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 15 May 2025 16:53:12 +0800 Subject: [PATCH 2/7] feat: crud for auto upgrade strategy --- api/controllers/console/workspace/plugin.py | 65 +++++++++++++++++++ .../versions/2025_05_15_1635-16081485540c_.py | 41 ++++++++++++ api/models/account.py | 17 +++-- .../plugin/plugin_auto_upgrade_service.py | 50 ++++++++++++++ 4 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 api/migrations/versions/2025_05_15_1635-16081485540c_.py create mode 100644 api/services/plugin/plugin_auto_upgrade_service.py diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index fda5a7d3bb..34f059e093 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -13,6 +13,7 @@ from core.model_runtime.utils.encoders import jsonable_encoder from core.plugin.impl.exc import PluginDaemonClientSideError from libs.login import login_required from models.account import TenantPluginPermission +from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService from services.plugin.plugin_permission_service import PluginPermissionService from services.plugin.plugin_service import PluginService @@ -493,6 +494,67 @@ class PluginFetchPermissionApi(Resource): ) +class PluginChangeAutoUpgradeStrategyApi(Resource): + @setup_required + @login_required + @account_initialization_required + def post(self): + user = current_user + if not user.is_admin_or_owner: + raise Forbidden() + + req = reqparse.RequestParser() + req.add_argument("strategy_setting", type=str, required=True, location="json") + req.add_argument("upgrade_time_of_day", type=int, required=True, location="json") + req.add_argument("upgrade_mode", type=str, required=True, location="json") + req.add_argument("exclude_plugins", type=list, required=True, location="json") + req.add_argument("include_plugins", type=list, required=True, location="json") + args = req.parse_args() + + tenant_id = user.current_tenant_id + + return { + "success": PluginAutoUpgradeService.change_strategy( + tenant_id, + args["strategy_setting"], + args["upgrade_time_of_day"], + args["upgrade_mode"], + args["exclude_plugins"], + args["include_plugins"], + ) + } + + +class PluginFetchAutoUpgradeStrategyApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self): + tenant_id = current_user.current_tenant_id + + strategy = PluginAutoUpgradeService.get_strategy(tenant_id) + if not strategy: + return jsonable_encoder( + { + "strategy_setting": "fix_only", + "upgrade_time_of_day": 0, + "upgrade_mode": "exclude", + "exclude_plugins": [], + "include_plugins": [], + } + ) + + return jsonable_encoder( + { + "strategy_setting": strategy.strategy_setting, + "upgrade_time_of_day": strategy.upgrade_time_of_day, + "upgrade_mode": strategy.upgrade_mode, + "exclude_plugins": strategy.exclude_plugins, + "include_plugins": strategy.include_plugins, + } + ) + + api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") api.add_resource(PluginListApi, "/workspaces/current/plugin/list") api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") @@ -517,3 +579,6 @@ api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marke api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change") api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch") + +api.add_resource(PluginFetchAutoUpgradeStrategyApi, "/workspaces/current/plugin/autoupgrade/fetch") +api.add_resource(PluginChangeAutoUpgradeStrategyApi, "/workspaces/current/plugin/autoupgrade/change") diff --git a/api/migrations/versions/2025_05_15_1635-16081485540c_.py b/api/migrations/versions/2025_05_15_1635-16081485540c_.py new file mode 100644 index 0000000000..3db4754e2d --- /dev/null +++ b/api/migrations/versions/2025_05_15_1635-16081485540c_.py @@ -0,0 +1,41 @@ +"""empty message + +Revision ID: 16081485540c +Revises: d28f2004b072 +Create Date: 2025-05-15 16:35:39.113777 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '16081485540c' +down_revision = 'd28f2004b072' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('tenant_plugin_auto_upgrade_strategies', + sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False), + sa.Column('tenant_id', models.types.StringUUID(), nullable=False), + sa.Column('strategy_setting', sa.String(length=16), server_default='fix_only', nullable=False), + sa.Column('upgrade_time_of_day', sa.Integer(), nullable=False), + sa.Column('upgrade_mode', sa.String(length=16), server_default='exclude', nullable=False), + sa.Column('exclude_plugins', sa.ARRAY(sa.String(length=255)), nullable=False), + sa.Column('include_plugins', sa.ARRAY(sa.String(length=255)), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.PrimaryKeyConstraint('id', name='tenant_plugin_auto_upgrade_strategy_pkey'), + sa.UniqueConstraint('tenant_id', name='unique_tenant_plugin_auto_upgrade_strategy') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('tenant_plugin_auto_upgrade_strategies') + # ### end Alembic commands ### diff --git a/api/models/account.py b/api/models/account.py index b4037ddedb..3c4c96afbe 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -297,8 +297,9 @@ class TenantPluginPermission(Base): ) debug_permission: Mapped[DebugPermission] = mapped_column(db.String(16), nullable=False, server_default="noone") + class TenantPluginAutoUpgradeStrategy(Base): - class StrategyOption(enum.StrEnum): + class StrategySetting(enum.StrEnum): DISABLED = "disabled" FIX_ONLY = "fix_only" LATEST = "latest" @@ -315,10 +316,14 @@ class TenantPluginAutoUpgradeStrategy(Base): id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) - strategy_option: Mapped[StrategyOption] = mapped_column(db.String(16), nullable=False) - upgrade_time_of_day: Mapped[int] = mapped_column(db.Integer, nullable=False) - upgrade_mode: Mapped[UpgradeMode] = mapped_column(db.String(16), nullable=False) - exclude_plugins: Mapped[list[str]] = mapped_column(db.ARRAY(db.String(255)), nullable=False) - include_plugins: Mapped[list[str]] = mapped_column(db.ARRAY(db.String(255)), nullable=False) + strategy_setting: Mapped[StrategySetting] = mapped_column(db.String(16), nullable=False, server_default="fix_only") + upgrade_time_of_day: Mapped[int] = mapped_column(db.Integer, nullable=False, default=0) # seconds of the day + upgrade_mode: Mapped[UpgradeMode] = mapped_column(db.String(16), nullable=False, server_default="exclude") + exclude_plugins: Mapped[list[str]] = mapped_column( + db.ARRAY(db.String(255)), nullable=False + ) # plugin_id (author/name) + include_plugins: Mapped[list[str]] = mapped_column( + db.ARRAY(db.String(255)), nullable=False + ) # plugin_id (author/name) created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) diff --git a/api/services/plugin/plugin_auto_upgrade_service.py b/api/services/plugin/plugin_auto_upgrade_service.py new file mode 100644 index 0000000000..b93d6ed915 --- /dev/null +++ b/api/services/plugin/plugin_auto_upgrade_service.py @@ -0,0 +1,50 @@ +from sqlalchemy.orm import Session + +from extensions.ext_database import db +from models.account import TenantPluginAutoUpgradeStrategy + + +class PluginAutoUpgradeService: + @staticmethod + def get_strategy(tenant_id: str) -> TenantPluginAutoUpgradeStrategy | None: + with Session(db.engine) as session: + return ( + session.query(TenantPluginAutoUpgradeStrategy) + .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .first() + ) + + @staticmethod + def change_strategy( + tenant_id: str, + strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting, + upgrade_time_of_day: int, + upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode, + exclude_plugins: list[str], + include_plugins: list[str], + ) -> None: + with Session(db.engine) as session: + exist_strategy = ( + session.query(TenantPluginAutoUpgradeStrategy) + .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .first() + ) + if not exist_strategy: + strategy = TenantPluginAutoUpgradeStrategy( + tenant_id=tenant_id, + strategy_setting=strategy_setting, + upgrade_time_of_day=upgrade_time_of_day, + upgrade_mode=upgrade_mode, + exclude_plugins=exclude_plugins, + include_plugins=include_plugins, + ) + session.add(strategy) + else: + exist_strategy.strategy_setting = strategy_setting + exist_strategy.upgrade_time_of_day = upgrade_time_of_day + exist_strategy.upgrade_mode = upgrade_mode + exist_strategy.exclude_plugins = exclude_plugins + exist_strategy.include_plugins = include_plugins + + session.commit() + return True From 44c90045ab149fdf8c4fd6b5102c600c806c8a8c Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 19 May 2025 15:08:47 +0800 Subject: [PATCH 3/7] feat(auto-upgrade): celery scheduled task --- api/extensions/ext_celery.py | 6 + api/schedule/check_upgradable_plugin_task.py | 124 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 api/schedule/check_upgradable_plugin_task.py diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py index 26bd6b3577..872d422be0 100644 --- a/api/extensions/ext_celery.py +++ b/api/extensions/ext_celery.py @@ -70,6 +70,7 @@ def init_app(app: DifyApp) -> Celery: "schedule.update_tidb_serverless_status_task", "schedule.clean_messages", "schedule.mail_clean_document_notify_task", + "schedule.check_upgradable_plugin_task", ] day = dify_config.CELERY_BEAT_SCHEDULER_TIME beat_schedule = { @@ -98,6 +99,11 @@ def init_app(app: DifyApp) -> Celery: "task": "schedule.mail_clean_document_notify_task.mail_clean_document_notify_task", "schedule": crontab(minute="0", hour="10", day_of_week="1"), }, + # every 15 minutes + "check_upgradable_plugin_task": { + "task": "schedule.check_upgradable_plugin_task.check_upgradable_plugin_task", + "schedule": timedelta(minutes=15), + }, } celery_app.conf.update(beat_schedule=beat_schedule, imports=imports) diff --git a/api/schedule/check_upgradable_plugin_task.py b/api/schedule/check_upgradable_plugin_task.py new file mode 100644 index 0000000000..95e9d6d16a --- /dev/null +++ b/api/schedule/check_upgradable_plugin_task.py @@ -0,0 +1,124 @@ +import time + +import click + +import app +from core.helper import marketplace +from core.plugin.entities.plugin import PluginInstallationSource +from core.plugin.impl.plugin import PluginInstaller +from extensions.ext_database import db +from models.account import TenantPluginAutoUpgradeStrategy + +AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL = 15 * 60 # 15 minutes + +RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3 + + +@app.celery.task(queue="dataset") +def check_upgradable_plugin_task(): + click.echo(click.style("Start check upgradable plugin.", fg="green")) + start_at = time.perf_counter() + + now_seconds_of_day = time.time() % 86400 # we assume the tz is UTC + + # get strategies that set to be performed in the next AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL + strategies = ( + db.session.query(TenantPluginAutoUpgradeStrategy) + .filter( + TenantPluginAutoUpgradeStrategy.upgrade_time_of_day >= now_seconds_of_day, + TenantPluginAutoUpgradeStrategy.upgrade_time_of_day + < now_seconds_of_day + AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL, + TenantPluginAutoUpgradeStrategy.strategy_setting + != TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED, + ) + .all() + ) + + manager = PluginInstaller() + + for strategy in strategies: + try: + tenant_id = strategy.tenant_id + strategy_setting = strategy.strategy_setting + upgrade_mode = strategy.upgrade_mode + exclude_plugins = strategy.exclude_plugins + include_plugins = strategy.include_plugins + + if strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED: + continue + + # get plugins that need to be checked + plugin_ids: list[tuple[str, str, str]] = [] # plugin_id, version, unique_identifier + + if upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL and include_plugins: + all_plugins = manager.list_plugins(tenant_id) + + for plugin in all_plugins: + if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id in include_plugins: + plugin_ids.append((plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier)) + + elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE: + # get all plugins and remove the exclude plugins + all_plugins = manager.list_plugins(tenant_id) + plugin_ids = [ + (plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier) + for plugin in all_plugins + if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id not in exclude_plugins + ] + + if not plugin_ids: + continue + + # fetch latest versions from marketplace + manifests = marketplace.batch_fetch_plugin_manifests(plugin_ids) + + for manifest in manifests: + for plugin_id, version, original_unique_identifier in plugin_ids: + current_version = version + latest_version = manifest.latest_version + + # @yeuoly review here + def fix_only_checker(latest_version, current_version): + latest_version_tuple = tuple(int(val) for val in latest_version.split(".")) + current_version_tuple = tuple(int(val) for val in current_version.split(".")) + + if ( + latest_version_tuple[0] == current_version_tuple[0] + and latest_version_tuple[1] == current_version_tuple[1] + ): + return latest_version_tuple[2] != current_version_tuple[2] + return False + + version_checker = { + TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST: lambda latest_version, + current_version: latest_version != current_version, + TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY: fix_only_checker, + } + + if version_checker[strategy_setting](latest_version, current_version): + # execute upgrade + new_unique_identifier = manifest.latest_package_identifier + + marketplace.record_install_plugin_event(new_unique_identifier) + click.echo(click.style("Upgrade plugin: {}".format(new_unique_identifier), fg="green")) + task_start_resp = manager.upgrade_plugin( + tenant_id, + original_unique_identifier, + new_unique_identifier, + PluginInstallationSource.Marketplace, + { + "plugin_unique_identifier": new_unique_identifier, + }, + ) + + except Exception as e: + click.echo(click.style("Error when checking upgradable plugin: {}".format(e), fg="red")) + continue + + end_at = time.perf_counter() + click.echo( + click.style( + "Checked upgradable plugin success latency: {}".format(end_at - start_at), + fg="green", + ) + ) From e6e6504ae4e841832ec707b90da4d89b92f9f310 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 20 May 2025 15:08:06 +0800 Subject: [PATCH 4/7] fix: bugs --- api/extensions/ext_celery.py | 2 +- api/schedule/check_upgradable_plugin_task.py | 88 ++++++++++++-------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py index 872d422be0..aaa3c76688 100644 --- a/api/extensions/ext_celery.py +++ b/api/extensions/ext_celery.py @@ -102,7 +102,7 @@ def init_app(app: DifyApp) -> Celery: # every 15 minutes "check_upgradable_plugin_task": { "task": "schedule.check_upgradable_plugin_task.check_upgradable_plugin_task", - "schedule": timedelta(minutes=15), + "schedule": crontab(minute="*/15"), }, } celery_app.conf.update(beat_schedule=beat_schedule, imports=imports) diff --git a/api/schedule/check_upgradable_plugin_task.py b/api/schedule/check_upgradable_plugin_task.py index 95e9d6d16a..96b05a814e 100644 --- a/api/schedule/check_upgradable_plugin_task.py +++ b/api/schedule/check_upgradable_plugin_task.py @@ -1,4 +1,5 @@ import time +import traceback import click @@ -14,12 +15,13 @@ AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL = 15 * 60 # 15 minutes RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3 -@app.celery.task(queue="dataset") +@app.celery.task(queue="plugin") def check_upgradable_plugin_task(): click.echo(click.style("Start check upgradable plugin.", fg="green")) start_at = time.perf_counter() now_seconds_of_day = time.time() % 86400 # we assume the tz is UTC + click.echo(click.style("Now seconds of day: {}".format(now_seconds_of_day), fg="green")) # get strategies that set to be performed in the next AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL strategies = ( @@ -69,50 +71,70 @@ def check_upgradable_plugin_task(): if not plugin_ids: continue + plugin_ids_plain_list = [plugin_id for plugin_id, _, _ in plugin_ids] + + click.echo(click.style("Fetching manifests for plugins: {}".format(plugin_ids_plain_list), fg="green")) + # fetch latest versions from marketplace - manifests = marketplace.batch_fetch_plugin_manifests(plugin_ids) + manifests = marketplace.batch_fetch_plugin_manifests(plugin_ids_plain_list) for manifest in manifests: for plugin_id, version, original_unique_identifier in plugin_ids: - current_version = version - latest_version = manifest.latest_version + if manifest.plugin_id != plugin_id: + continue - # @yeuoly review here - def fix_only_checker(latest_version, current_version): - latest_version_tuple = tuple(int(val) for val in latest_version.split(".")) - current_version_tuple = tuple(int(val) for val in current_version.split(".")) + try: + current_version = version + latest_version = manifest.latest_version - if ( - latest_version_tuple[0] == current_version_tuple[0] - and latest_version_tuple[1] == current_version_tuple[1] - ): - return latest_version_tuple[2] != current_version_tuple[2] - return False + # @yeuoly review here + def fix_only_checker(latest_version, current_version): + latest_version_tuple = tuple(int(val) for val in latest_version.split(".")) + current_version_tuple = tuple(int(val) for val in current_version.split(".")) - version_checker = { - TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST: lambda latest_version, - current_version: latest_version != current_version, - TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY: fix_only_checker, - } + if ( + latest_version_tuple[0] == current_version_tuple[0] + and latest_version_tuple[1] == current_version_tuple[1] + ): + return latest_version_tuple[2] != current_version_tuple[2] + return False - if version_checker[strategy_setting](latest_version, current_version): - # execute upgrade - new_unique_identifier = manifest.latest_package_identifier + version_checker = { + TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST: lambda latest_version, + current_version: latest_version != current_version, + TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY: fix_only_checker, + } - marketplace.record_install_plugin_event(new_unique_identifier) - click.echo(click.style("Upgrade plugin: {}".format(new_unique_identifier), fg="green")) - task_start_resp = manager.upgrade_plugin( - tenant_id, - original_unique_identifier, - new_unique_identifier, - PluginInstallationSource.Marketplace, - { - "plugin_unique_identifier": new_unique_identifier, - }, - ) + if version_checker[strategy_setting](latest_version, current_version): + # execute upgrade + new_unique_identifier = manifest.latest_package_identifier + + marketplace.record_install_plugin_event(new_unique_identifier) + click.echo( + click.style( + "Upgrade plugin: {} -> {}".format( + original_unique_identifier, new_unique_identifier + ), + fg="green", + ) + ) + task_start_resp = manager.upgrade_plugin( + tenant_id, + original_unique_identifier, + new_unique_identifier, + PluginInstallationSource.Marketplace, + { + "plugin_unique_identifier": new_unique_identifier, + }, + ) + except Exception as e: + click.echo(click.style("Error when upgrading plugin: {}".format(e), fg="red")) + traceback.print_exc() + break except Exception as e: click.echo(click.style("Error when checking upgradable plugin: {}".format(e), fg="red")) + traceback.print_exc() continue end_at = time.perf_counter() From c983967d82be2f9a8ecc084c99751c83e435fc80 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 24 Jun 2025 15:44:03 +0800 Subject: [PATCH 5/7] feat: combine plugin preferences apis --- api/controllers/console/workspace/plugin.py | 117 +++++++++++++------- api/models/account.py | 1 + 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 34f059e093..72bf4f6c5b 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -12,7 +12,7 @@ from controllers.console.wraps import account_initialization_required, setup_req from core.model_runtime.utils.encoders import jsonable_encoder from core.plugin.impl.exc import PluginDaemonClientSideError from libs.login import login_required -from models.account import TenantPluginPermission +from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermission from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService from services.plugin.plugin_permission_service import PluginPermissionService from services.plugin.plugin_service import PluginService @@ -494,7 +494,7 @@ class PluginFetchPermissionApi(Resource): ) -class PluginChangeAutoUpgradeStrategyApi(Resource): +class PluginChangePreferencesApi(Resource): @setup_required @login_required @account_initialization_required @@ -504,55 +504,90 @@ class PluginChangeAutoUpgradeStrategyApi(Resource): raise Forbidden() req = reqparse.RequestParser() - req.add_argument("strategy_setting", type=str, required=True, location="json") - req.add_argument("upgrade_time_of_day", type=int, required=True, location="json") - req.add_argument("upgrade_mode", type=str, required=True, location="json") - req.add_argument("exclude_plugins", type=list, required=True, location="json") - req.add_argument("include_plugins", type=list, required=True, location="json") + req.add_argument("permission", type=dict, required=True, location="json") + req.add_argument("auto_upgrade", type=dict, required=True, location="json") args = req.parse_args() tenant_id = user.current_tenant_id - return { - "success": PluginAutoUpgradeService.change_strategy( - tenant_id, - args["strategy_setting"], - args["upgrade_time_of_day"], - args["upgrade_mode"], - args["exclude_plugins"], - args["include_plugins"], - ) - } + permission = args["permission"] + + install_permission = TenantPluginPermission.InstallPermission(permission.get("install_permission", "everyone")) + debug_permission = TenantPluginPermission.DebugPermission(permission.get("debug_permission", "everyone")) + + auto_upgrade = args["auto_upgrade"] + + strategy_setting = TenantPluginAutoUpgradeStrategy.StrategySetting( + auto_upgrade.get("strategy_setting", "fix_only") + ) + upgrade_time_of_day = auto_upgrade.get("upgrade_time_of_day", 0) + upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode(auto_upgrade.get("upgrade_mode", "exclude")) + exclude_plugins = auto_upgrade.get("exclude_plugins", []) + include_plugins = auto_upgrade.get("include_plugins", []) + + # set permission + set_permission_result = PluginPermissionService.change_permission( + tenant_id, + install_permission, + debug_permission, + ) + if not set_permission_result: + return jsonable_encoder({"success": False, "message": "Failed to set permission"}) + + # set auto upgrade strategy + set_auto_upgrade_strategy_result = PluginAutoUpgradeService.change_strategy( + tenant_id, + strategy_setting, + upgrade_time_of_day, + upgrade_mode, + exclude_plugins, + include_plugins, + ) + if not set_auto_upgrade_strategy_result: + return jsonable_encoder({"success": False, "message": "Failed to set auto upgrade strategy"}) + + return jsonable_encoder({"success": True}) -class PluginFetchAutoUpgradeStrategyApi(Resource): +class PluginFetchPreferencesApi(Resource): @setup_required @login_required @account_initialization_required def get(self): tenant_id = current_user.current_tenant_id - strategy = PluginAutoUpgradeService.get_strategy(tenant_id) - if not strategy: - return jsonable_encoder( - { - "strategy_setting": "fix_only", - "upgrade_time_of_day": 0, - "upgrade_mode": "exclude", - "exclude_plugins": [], - "include_plugins": [], - } - ) + permission = PluginPermissionService.get_permission(tenant_id) - return jsonable_encoder( - { - "strategy_setting": strategy.strategy_setting, - "upgrade_time_of_day": strategy.upgrade_time_of_day, - "upgrade_mode": strategy.upgrade_mode, - "exclude_plugins": strategy.exclude_plugins, - "include_plugins": strategy.include_plugins, + if not permission: + permission = { + "install_permission": TenantPluginPermission.InstallPermission.EVERYONE, + "debug_permission": TenantPluginPermission.DebugPermission.EVERYONE, } - ) + else: + permission = { + "install_permission": permission.install_permission, + "debug_permission": permission.debug_permission, + } + + auto_upgrade = PluginAutoUpgradeService.get_strategy(tenant_id) + if not auto_upgrade: + auto_upgrade = { + "strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + "upgrade_time_of_day": 0, + "upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + "exclude_plugins": [], + "include_plugins": [], + } + else: + auto_upgrade = { + "strategy_setting": auto_upgrade.strategy_setting, + "upgrade_time_of_day": auto_upgrade.upgrade_time_of_day, + "upgrade_mode": auto_upgrade.upgrade_mode, + "exclude_plugins": auto_upgrade.exclude_plugins, + "include_plugins": auto_upgrade.include_plugins, + } + + return jsonable_encoder({"permission": permission, "auto_upgrade": auto_upgrade}) api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") @@ -577,8 +612,8 @@ api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tas api.add_resource(PluginUninstallApi, "/workspaces/current/plugin/uninstall") api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marketplace/pkg") -api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change") -api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch") +api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change") # deprecated +api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch") # deprecated -api.add_resource(PluginFetchAutoUpgradeStrategyApi, "/workspaces/current/plugin/autoupgrade/fetch") -api.add_resource(PluginChangeAutoUpgradeStrategyApi, "/workspaces/current/plugin/autoupgrade/change") +api.add_resource(PluginFetchPreferencesApi, "/workspaces/current/plugin/preferences/fetch") +api.add_resource(PluginChangePreferencesApi, "/workspaces/current/plugin/preferences/change") diff --git a/api/models/account.py b/api/models/account.py index 3c4c96afbe..e110aa2000 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -305,6 +305,7 @@ class TenantPluginAutoUpgradeStrategy(Base): LATEST = "latest" class UpgradeMode(enum.StrEnum): + ALL = "all" PARTIAL = "partial" EXCLUDE = "exclude" From 1c595f3fccb52534d524211f7cb3ccb93ec91226 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 24 Jun 2025 15:45:54 +0800 Subject: [PATCH 6/7] feat: add supports for `update_all` strategy --- api/schedule/check_upgradable_plugin_task.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/schedule/check_upgradable_plugin_task.py b/api/schedule/check_upgradable_plugin_task.py index 96b05a814e..b709689d27 100644 --- a/api/schedule/check_upgradable_plugin_task.py +++ b/api/schedule/check_upgradable_plugin_task.py @@ -67,6 +67,13 @@ def check_upgradable_plugin_task(): for plugin in all_plugins if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id not in exclude_plugins ] + elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL: + all_plugins = manager.list_plugins(tenant_id) + plugin_ids = [ + (plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier) + for plugin in all_plugins + if plugin.source == PluginInstallationSource.Marketplace + ] if not plugin_ids: continue From e42068a20e41086c3508328d16f662e46cafe996 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 24 Jun 2025 16:25:01 +0800 Subject: [PATCH 7/7] feat: exclude one plugin --- api/controllers/console/workspace/plugin.py | 16 ++++++++ .../plugin/plugin_auto_upgrade_service.py | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 72bf4f6c5b..6ad357c36e 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -590,6 +590,21 @@ class PluginFetchPreferencesApi(Resource): return jsonable_encoder({"permission": permission, "auto_upgrade": auto_upgrade}) +class PluginAutoUpgradeExcludePluginApi(Resource): + @setup_required + @login_required + @account_initialization_required + def post(self): + # exclude one single plugin + tenant_id = current_user.current_tenant_id + + req = reqparse.RequestParser() + req.add_argument("plugin_id", type=str, required=True, location="json") + args = req.parse_args() + + return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])}) + + api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") api.add_resource(PluginListApi, "/workspaces/current/plugin/list") api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") @@ -617,3 +632,4 @@ api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permissio api.add_resource(PluginFetchPreferencesApi, "/workspaces/current/plugin/preferences/fetch") api.add_resource(PluginChangePreferencesApi, "/workspaces/current/plugin/preferences/change") +api.add_resource(PluginAutoUpgradeExcludePluginApi, "/workspaces/current/plugin/preferences/autoupgrade/exclude") diff --git a/api/services/plugin/plugin_auto_upgrade_service.py b/api/services/plugin/plugin_auto_upgrade_service.py index b93d6ed915..d05292a4bc 100644 --- a/api/services/plugin/plugin_auto_upgrade_service.py +++ b/api/services/plugin/plugin_auto_upgrade_service.py @@ -48,3 +48,40 @@ class PluginAutoUpgradeService: session.commit() return True + + @staticmethod + def exclude_plugin(tenant_id: str, plugin_id: str) -> bool: + with Session(db.engine) as session: + exist_strategy = ( + session.query(TenantPluginAutoUpgradeStrategy) + .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .first() + ) + if not exist_strategy: + # create for this tenant + PluginAutoUpgradeService.change_strategy( + tenant_id, + TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + 0, + TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + [plugin_id], + [], + ) + return True + else: + if exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE: + if plugin_id not in exist_strategy.exclude_plugins: + new_exclude_plugins = exist_strategy.exclude_plugins.copy() + new_exclude_plugins.append(plugin_id) + exist_strategy.exclude_plugins = new_exclude_plugins + elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL: + if plugin_id in exist_strategy.include_plugins: + new_include_plugins = exist_strategy.include_plugins.copy() + new_include_plugins.remove(plugin_id) + exist_strategy.include_plugins = new_include_plugins + elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL: + exist_strategy.upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + exist_strategy.exclude_plugins = [plugin_id] + + session.commit() + return True