From db68ae4a73072c3ba4dc27e1fe85976bcbf619cc Mon Sep 17 00:00:00 2001 From: Yeuoly Date: Thu, 14 Nov 2024 22:58:57 +0800 Subject: [PATCH] feat: support upload bundle --- api/configs/feature/__init__.py | 5 ++++ api/controllers/console/workspace/plugin.py | 21 +++++++++++++++ api/core/plugin/entities/bundle.py | 30 +++++++++++++++++++++ api/core/plugin/entities/plugin.py | 6 ++--- api/core/plugin/manager/plugin.py | 20 +++++++++++++- api/services/plugin/plugin_service.py | 11 ++++++++ 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 api/core/plugin/entities/bundle.py diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 4bb2bd2904..6641861329 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -166,6 +166,11 @@ class PluginConfig(BaseSettings): default=15728640, ) + PLUGIN_MAX_BUNDLE_SIZE: PositiveInt = Field( + description="Maximum allowed size for plugin bundles in bytes", + default=15728640 * 12, + ) + class MarketplaceConfig(BaseSettings): """ diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 1cb83c136f..b2cbe0ce3b 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -94,6 +94,26 @@ class PluginUploadFromGithubApi(Resource): return jsonable_encoder(response) +class PluginUploadFromBundleApi(Resource): + @setup_required + @login_required + @account_initialization_required + @plugin_permission_required(install_required=True) + def post(self): + tenant_id = current_user.current_tenant_id + + file = request.files["bundle"] + + # check file size + if file.content_length > dify_config.PLUGIN_MAX_BUNDLE_SIZE: + raise ValueError("File size exceeds the maximum allowed size") + + content = file.read() + response = PluginService.upload_bundle(tenant_id, content) + + return jsonable_encoder(response) + + class PluginInstallFromPkgApi(Resource): @setup_required @login_required @@ -346,6 +366,7 @@ api.add_resource(PluginListApi, "/workspaces/current/plugin/list") api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") api.add_resource(PluginUploadFromGithubApi, "/workspaces/current/plugin/upload/github") +api.add_resource(PluginUploadFromBundleApi, "/workspaces/current/plugin/upload/bundle") api.add_resource(PluginInstallFromPkgApi, "/workspaces/current/plugin/install/pkg") api.add_resource(PluginInstallFromGithubApi, "/workspaces/current/plugin/install/github") api.add_resource(PluginUpgradeFromMarketplaceApi, "/workspaces/current/plugin/upgrade/marketplace") diff --git a/api/core/plugin/entities/bundle.py b/api/core/plugin/entities/bundle.py new file mode 100644 index 0000000000..0cf85c0548 --- /dev/null +++ b/api/core/plugin/entities/bundle.py @@ -0,0 +1,30 @@ +from enum import Enum + +from pydantic import BaseModel + +from core.plugin.entities.plugin import PluginDeclaration, PluginInstallationSource + + +class PluginBundleDependency(BaseModel): + class Type(str, Enum): + Github = PluginInstallationSource.Github.value + Marketplace = PluginInstallationSource.Marketplace.value + Package = PluginInstallationSource.Package.value + + class Github(BaseModel): + repo_address: str + repo: str + release: str + packages: str + + class Marketplace(BaseModel): + organization: str + plugin: str + version: str + + class Package(BaseModel): + unique_identifier: str + manifest: PluginDeclaration + + type: Type + value: Github | Marketplace | Package diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index 2f10fda4d8..b2fad58fc4 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -162,9 +162,9 @@ class GenericProviderID: class PluginDependency(BaseModel): class Type(str, Enum): - Github = "github" - Marketplace = "marketplace" - Package = "package" + Github = PluginInstallationSource.Github.value + Marketplace = PluginInstallationSource.Marketplace.value + Package = PluginInstallationSource.Package.value class Github(BaseModel): repo: str diff --git a/api/core/plugin/manager/plugin.py b/api/core/plugin/manager/plugin.py index bd0612107d..a3592c0c9e 100644 --- a/api/core/plugin/manager/plugin.py +++ b/api/core/plugin/manager/plugin.py @@ -2,6 +2,7 @@ from collections.abc import Sequence from pydantic import BaseModel +from core.plugin.entities.bundle import PluginBundleDependency from core.plugin.entities.plugin import ( PluginDeclaration, PluginEntity, @@ -52,12 +53,29 @@ class PluginInstallationManager(BasePluginManager): return self._request_with_plugin_daemon_response( "POST", - f"plugin/{tenant_id}/management/install/upload", + f"plugin/{tenant_id}/management/install/upload/package", PluginUploadResponse, files=body, data=data, ) + def upload_bundle( + self, + tenant_id: str, + bundle: bytes, + verify_signature: bool = False, + ) -> Sequence[PluginBundleDependency]: + """ + Upload a plugin bundle and return the dependencies. + """ + return self._request_with_plugin_daemon_response( + "POST", + f"plugin/{tenant_id}/management/install/upload/bundle", + list[PluginBundleDependency], + files={"dify_bundle": ("dify_bundle", bundle, "application/octet-stream")}, + data={"verify_signature": "true" if verify_signature else "false"}, + ) + def install_from_identifiers( self, tenant_id: str, identifiers: Sequence[str], source: PluginInstallationSource, meta: dict ) -> PluginInstallTaskStartResponse: diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 2c62c7422e..8519d06acc 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -6,6 +6,7 @@ from configs import dify_config from core.helper import marketplace from core.helper.download import download_with_size_limit from core.helper.marketplace import download_plugin_pkg +from core.plugin.entities.bundle import PluginBundleDependency from core.plugin.entities.plugin import PluginDeclaration, PluginEntity, PluginInstallationSource from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginUploadResponse from core.plugin.manager.asset import PluginAssetManager @@ -189,6 +190,16 @@ class PluginService: verify_signature, ) + @staticmethod + def upload_bundle( + tenant_id: str, bundle: bytes, verify_signature: bool = False + ) -> Sequence[PluginBundleDependency]: + """ + Upload a plugin bundle and return the dependencies. + """ + manager = PluginInstallationManager() + return manager.upload_bundle(tenant_id, bundle, verify_signature) + @staticmethod def install_from_local_pkg(tenant_id: str, plugin_unique_identifiers: Sequence[str]): manager = PluginInstallationManager()