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