mirror of
https://github.com/langgenius/dify.git
synced 2025-11-23 16:31:09 +00:00
235 lines
9.2 KiB
Python
235 lines
9.2 KiB
Python
import json
|
|
import operator
|
|
import typing
|
|
|
|
import click
|
|
from celery import shared_task
|
|
|
|
from core.helper import marketplace
|
|
from core.helper.marketplace import MarketplacePluginDeclaration
|
|
from core.plugin.entities.plugin import PluginInstallationSource
|
|
from core.plugin.impl.plugin import PluginInstaller
|
|
from extensions.ext_redis import redis_client
|
|
from models.account import TenantPluginAutoUpgradeStrategy
|
|
|
|
RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3
|
|
CACHE_REDIS_KEY_PREFIX = "plugin_autoupgrade_check_task:cached_plugin_manifests:"
|
|
CACHE_REDIS_TTL = 60 * 15 # 15 minutes
|
|
|
|
|
|
def _get_redis_cache_key(plugin_id: str) -> str:
|
|
"""Generate Redis cache key for plugin manifest."""
|
|
return f"{CACHE_REDIS_KEY_PREFIX}{plugin_id}"
|
|
|
|
|
|
def _get_cached_manifest(plugin_id: str) -> typing.Union[MarketplacePluginDeclaration, None, bool]:
|
|
"""
|
|
Get cached plugin manifest from Redis.
|
|
Returns:
|
|
- MarketplacePluginDeclaration: if found in cache
|
|
- None: if cached as not found (marketplace returned no result)
|
|
- False: if not in cache at all
|
|
"""
|
|
try:
|
|
key = _get_redis_cache_key(plugin_id)
|
|
cached_data = redis_client.get(key)
|
|
if cached_data is None:
|
|
return False
|
|
|
|
cached_json = json.loads(cached_data)
|
|
if cached_json is None:
|
|
return None
|
|
|
|
return MarketplacePluginDeclaration.model_validate(cached_json)
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _set_cached_manifest(plugin_id: str, manifest: typing.Union[MarketplacePluginDeclaration, None]) -> None:
|
|
"""
|
|
Cache plugin manifest in Redis.
|
|
Args:
|
|
plugin_id: The plugin ID
|
|
manifest: The manifest to cache, or None if not found in marketplace
|
|
"""
|
|
try:
|
|
key = _get_redis_cache_key(plugin_id)
|
|
if manifest is None:
|
|
# Cache the fact that this plugin was not found
|
|
redis_client.setex(key, CACHE_REDIS_TTL, json.dumps(None))
|
|
else:
|
|
# Cache the manifest data
|
|
redis_client.setex(key, CACHE_REDIS_TTL, manifest.model_dump_json())
|
|
except Exception:
|
|
# If Redis fails, continue without caching
|
|
# traceback.print_exc()
|
|
pass
|
|
|
|
|
|
def marketplace_batch_fetch_plugin_manifests(
|
|
plugin_ids_plain_list: list[str],
|
|
) -> list[MarketplacePluginDeclaration]:
|
|
"""Fetch plugin manifests with Redis caching support."""
|
|
cached_manifests: dict[str, typing.Union[MarketplacePluginDeclaration, None]] = {}
|
|
not_cached_plugin_ids: list[str] = []
|
|
|
|
# Check Redis cache for each plugin
|
|
for plugin_id in plugin_ids_plain_list:
|
|
cached_result = _get_cached_manifest(plugin_id)
|
|
if cached_result is False:
|
|
# Not in cache, need to fetch
|
|
not_cached_plugin_ids.append(plugin_id)
|
|
else:
|
|
# Either found manifest or cached as None (not found in marketplace)
|
|
# At this point, cached_result is either MarketplacePluginDeclaration or None
|
|
if isinstance(cached_result, bool):
|
|
# This should never happen due to the if condition above, but for type safety
|
|
continue
|
|
cached_manifests[plugin_id] = cached_result
|
|
|
|
# Fetch uncached plugins from marketplace
|
|
if not_cached_plugin_ids:
|
|
manifests = marketplace.batch_fetch_plugin_manifests_ignore_deserialization_error(not_cached_plugin_ids)
|
|
|
|
# Cache the fetched manifests
|
|
for manifest in manifests:
|
|
cached_manifests[manifest.plugin_id] = manifest
|
|
_set_cached_manifest(manifest.plugin_id, manifest)
|
|
|
|
# Cache plugins that were not found in marketplace
|
|
fetched_plugin_ids = {manifest.plugin_id for manifest in manifests}
|
|
for plugin_id in not_cached_plugin_ids:
|
|
if plugin_id not in fetched_plugin_ids:
|
|
cached_manifests[plugin_id] = None
|
|
_set_cached_manifest(plugin_id, None)
|
|
|
|
# Build result list from cached manifests
|
|
result: list[MarketplacePluginDeclaration] = []
|
|
for plugin_id in plugin_ids_plain_list:
|
|
cached_manifest: typing.Union[MarketplacePluginDeclaration, None] = cached_manifests.get(plugin_id)
|
|
if cached_manifest is not None:
|
|
result.append(cached_manifest)
|
|
|
|
return result
|
|
|
|
|
|
@shared_task(queue="plugin")
|
|
def process_tenant_plugin_autoupgrade_check_task(
|
|
tenant_id: str,
|
|
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting,
|
|
upgrade_time_of_day: int,
|
|
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
|
|
exclude_plugins: list[str],
|
|
include_plugins: list[str],
|
|
):
|
|
try:
|
|
manager = PluginInstaller()
|
|
|
|
click.echo(
|
|
click.style(
|
|
f"Checking upgradable plugin for tenant: {tenant_id}",
|
|
fg="green",
|
|
)
|
|
)
|
|
|
|
if strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED:
|
|
return
|
|
|
|
# get plugin_ids to check
|
|
plugin_ids: list[tuple[str, str, str]] = [] # plugin_id, version, unique_identifier
|
|
click.echo(click.style(f"Upgrade mode: {upgrade_mode}", fg="green"))
|
|
|
|
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 excluded 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
|
|
]
|
|
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:
|
|
return
|
|
|
|
plugin_ids_plain_list = [plugin_id for plugin_id, _, _ in plugin_ids]
|
|
|
|
manifests = marketplace_batch_fetch_plugin_manifests(plugin_ids_plain_list)
|
|
|
|
if not manifests:
|
|
return
|
|
|
|
for manifest in manifests:
|
|
for plugin_id, version, original_unique_identifier in plugin_ids:
|
|
if manifest.plugin_id != plugin_id:
|
|
continue
|
|
|
|
try:
|
|
current_version = version
|
|
latest_version = manifest.latest_version
|
|
|
|
def fix_only_checker(latest_version: str, current_version: str):
|
|
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: operator.ne,
|
|
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(
|
|
f"Upgrade plugin: {original_unique_identifier} -> {new_unique_identifier}",
|
|
fg="green",
|
|
)
|
|
)
|
|
_ = 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(f"Error when upgrading plugin: {e}", fg="red"))
|
|
# traceback.print_exc()
|
|
break
|
|
|
|
except Exception as e:
|
|
click.echo(click.style(f"Error when checking upgradable plugin: {e}", fg="red"))
|
|
# traceback.print_exc()
|
|
return
|