ソースを参照

feat: support upload bundle

Yeuoly 8 ヶ月 前
コミット
9c9a6c26c0

+ 5 - 0
cmd/commandline/bundle/dep.go

@@ -187,6 +187,11 @@ func ListDependencies(bundlePath string) {
 			}
 
 			log.Info("Dependency Type: Package, Path: %s", packageDependency.Path)
+			if asset, err := packager.FetchAsset(packageDependency.Path); err != nil {
+				log.Error("Package %s not found", packageDependency.Path)
+			} else {
+				log.Info("Package %s: %d bytes", packageDependency.Path, len(asset))
+			}
 		}
 	}
 }

+ 2 - 1
internal/core/bundle_packager/generic.go

@@ -5,6 +5,7 @@ import (
 	"bytes"
 	"errors"
 	"os"
+	"path/filepath"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_packager/decoder"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
@@ -41,7 +42,7 @@ func (p *GenericBundlePackager) Export() ([]byte, error) {
 
 	// write the assets
 	for name, asset := range p.assets {
-		assetFile, err := zipWriter.Create(name)
+		assetFile, err := zipWriter.Create(filepath.Join("_assets", name))
 		if err != nil {
 			return nil, err
 		}

+ 2 - 0
internal/core/bundle_packager/local.go

@@ -57,6 +57,8 @@ func NewLocalBundlePackager(path string) (BundlePackager, error) {
 
 		prefix := filepath.Join(path, "_assets")
 		assetName := strings.TrimPrefix(filePath, prefix)
+		// trim slash
+		assetName = strings.Trim(assetName, "/")
 		packager.assets[assetName] = bytes.NewBuffer(assetBytes)
 
 		return nil

+ 75 - 0
internal/core/bundle_packager/memory_zip.go

@@ -0,0 +1,75 @@
+package bundle_packager
+
+import (
+	"archive/zip"
+	"bytes"
+	"io"
+	"strings"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
+
+type MemoryZipBundlePackager struct {
+	GenericBundlePackager
+
+	zipReader *zip.Reader
+}
+
+func NewMemoryZipBundlePackager(zipFile []byte) (*MemoryZipBundlePackager, error) {
+	// try read manifest file
+	zipReader, err := zip.NewReader(bytes.NewReader(zipFile), int64(len(zipFile)))
+	if err != nil {
+		return nil, err
+	}
+
+	manifestFile, err := zipReader.Open("manifest.yaml")
+	if err != nil {
+		return nil, err
+	}
+	defer manifestFile.Close()
+
+	manifestBytes, err := io.ReadAll(manifestFile)
+	if err != nil {
+		return nil, err
+	}
+
+	bundle, err := parser.UnmarshalYamlBytes[bundle_entities.Bundle](manifestBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	packager := &MemoryZipBundlePackager{
+		GenericBundlePackager: *NewGenericBundlePackager(&bundle),
+		zipReader:             zipReader,
+	}
+
+	// walk through the zip file and load the assets
+	for _, file := range zipReader.File {
+		// if file starts with "_assets/"
+		if strings.HasPrefix(file.Name, "_assets/") {
+			// load the asset
+			asset, err := file.Open()
+			if err != nil {
+				return nil, err
+			}
+			defer asset.Close()
+
+			assetBytes, err := io.ReadAll(asset)
+			if err != nil {
+				return nil, err
+			}
+
+			// trim the prefix "_assets/"
+			assetName := strings.TrimPrefix(file.Name, "_assets/")
+
+			packager.assets[assetName] = bytes.NewBuffer(assetBytes)
+		}
+	}
+
+	return packager, nil
+}
+
+func (p *MemoryZipBundlePackager) Save() error {
+	return nil
+}

+ 8 - 55
internal/core/bundle_packager/zip.go

@@ -2,20 +2,14 @@ package bundle_packager
 
 import (
 	"archive/zip"
-	"bytes"
 	"io"
 	"os"
-	"strings"
-
-	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
-	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
 )
 
 type ZipBundlePackager struct {
-	GenericBundlePackager
+	*MemoryZipBundlePackager
 
-	zipReader *zip.Reader
-	path      string
+	path string
 }
 
 func NewZipBundlePackager(path string) (BundlePackager, error) {
@@ -25,63 +19,22 @@ func NewZipBundlePackager(path string) (BundlePackager, error) {
 	}
 	defer zipFile.Close()
 
-	zipFileInfo, err := zipFile.Stat()
-	if err != nil {
-		return nil, err
-	}
-
-	zipReader, err := zip.NewReader(zipFile, zipFileInfo.Size())
-	if err != nil {
-		return nil, err
-	}
-
-	// try read manifest file
-	manifestFile, err := zipReader.Open("manifest.yaml")
+	zipBytes, err := io.ReadAll(zipFile)
 	if err != nil {
 		return nil, err
 	}
-	defer manifestFile.Close()
 
-	manifestBytes, err := io.ReadAll(manifestFile)
+	memoryPackager, err := NewMemoryZipBundlePackager(zipBytes)
 	if err != nil {
 		return nil, err
 	}
 
-	bundle, err := parser.UnmarshalYamlBytes[bundle_entities.Bundle](manifestBytes)
-	if err != nil {
-		return nil, err
-	}
-
-	packager := &ZipBundlePackager{
-		GenericBundlePackager: *NewGenericBundlePackager(&bundle),
-		zipReader:             zipReader,
-		path:                  path,
-	}
-
-	// walk through the zip file and load the assets
-	for _, file := range zipReader.File {
-		// if file starts with "assets/"
-		if strings.HasPrefix(file.Name, "assets/") {
-			// load the asset
-			asset, err := file.Open()
-			if err != nil {
-				return nil, err
-			}
-			defer asset.Close()
-
-			assetBytes, err := io.ReadAll(asset)
-			if err != nil {
-				return nil, err
-			}
-
-			// trim the prefix "assets/"
-			assetName := strings.TrimPrefix(file.Name, "assets/")
-
-			packager.assets[assetName] = bytes.NewBuffer(assetBytes)
-		}
+	zipBundlePackager := &ZipBundlePackager{
+		MemoryZipBundlePackager: memoryPackager,
+		path:                    path,
 	}
 
-	return packager, nil
+	return zipBundlePackager, nil
 }
 
 func (p *ZipBundlePackager) Save() error {

+ 8 - 1
internal/server/controllers/plugins.go

@@ -76,7 +76,14 @@ func UploadBundle(app *app.Config) gin.HandlerFunc {
 
 		verifySignature := c.PostForm("verify_signature") == "true"
 
-		c.JSON(http.StatusOK, service.UploadPluginBundle(app, c, tenantId, difyBundleFileHeader, verifySignature))
+		difyBundleFile, err := difyBundleFileHeader.Open()
+		if err != nil {
+			c.JSON(http.StatusOK, entities.NewErrorResponse(-400, err.Error()))
+			return
+		}
+		defer difyBundleFile.Close()
+
+		c.JSON(http.StatusOK, service.UploadPluginBundle(app, c, tenantId, difyBundleFile, verifySignature))
 	}
 }
 

+ 1 - 1
internal/server/http_server.go

@@ -113,7 +113,7 @@ func (app *App) endpointManagementGroup(group *gin.RouterGroup) {
 }
 
 func (app *App) pluginManagementGroup(group *gin.RouterGroup, config *app.Config) {
-	group.POST("/install/upload/pkg", controllers.UploadPlugin(config))
+	group.POST("/install/upload/package", controllers.UploadPlugin(config))
 	group.POST("/install/upload/bundle", controllers.UploadBundle(config))
 	group.POST("/install/identifiers", controllers.InstallPluginFromIdentifiers(config))
 	group.POST("/install/upgrade", controllers.UpgradePlugin(config))

+ 20 - 11
internal/service/plugin_decoder.go

@@ -62,10 +62,15 @@ func UploadPluginBundle(
 	config *app.Config,
 	c *gin.Context,
 	tenant_id string,
-	dify_bundle_file *multipart.FileHeader,
+	dify_bundle_file multipart.File,
 	verify_signature bool,
 ) *entities.Response {
-	packager, err := bundle_packager.NewZipBundlePackager(dify_bundle_file.Filename)
+	bundleFile, err := io.ReadAll(dify_bundle_file)
+	if err != nil {
+		return entities.NewErrorResponse(-500, err.Error())
+	}
+
+	packager, err := bundle_packager.NewMemoryZipBundlePackager(bundleFile)
 	if err != nil {
 		return entities.NewErrorResponse(-500, errors.Join(err, errors.New("failed to create bundle packager")).Error())
 	}
@@ -87,12 +92,14 @@ func UploadPluginBundle(
 					"type": "github",
 					"value": map[string]any{
 						"repo_address": dep.RepoPattern.Repo(),
-						"github_repo":  dep.RepoPattern.GithubRepo(),
+						"repo":         dep.RepoPattern.GithubRepo(),
 						"release":      dep.RepoPattern.Release(),
 						"packages":     dep.RepoPattern.Asset(),
 					},
 				})
-			} else if dep, ok := dependency.Value.(bundle_entities.MarketplaceDependency); ok {
+			}
+		} else if dependency.Type == bundle_entities.DEPENDENCY_TYPE_MARKETPLACE {
+			if dep, ok := dependency.Value.(bundle_entities.MarketplaceDependency); ok {
 				result = append(result, map[string]any{
 					"type": "marketplace",
 					"value": map[string]any{
@@ -101,33 +108,35 @@ func UploadPluginBundle(
 						"version":      dep.MarketplacePattern.Version(),
 					},
 				})
-			} else if dep, ok := dependency.Value.(bundle_entities.PackageDependency); ok {
+			}
+		} else if dependency.Type == bundle_entities.DEPENDENCY_TYPE_PACKAGE {
+			if dep, ok := dependency.Value.(bundle_entities.PackageDependency); ok {
 				// fetch package
 				path := dep.Path
 				if asset, err := packager.FetchAsset(path); err != nil {
-					return entities.NewErrorResponse(-500, errors.Join(err, errors.New("failed to fetch package")).Error())
+					return entities.NewErrorResponse(-500, errors.Join(errors.New("failed to fetch package from bundle"), err).Error())
 				} else {
 					// decode and save
 					decoder, err := decoder.NewZipPluginDecoder(asset)
 					if err != nil {
-						return entities.NewErrorResponse(-500, err.Error())
+						return entities.NewErrorResponse(-500, errors.Join(errors.New("failed to create package decoder"), err).Error())
 					}
 
 					pluginUniqueIdentifier, err := decoder.UniqueIdentity()
 					if err != nil {
-						return entities.NewErrorResponse(-500, err.Error())
+						return entities.NewErrorResponse(-500, errors.Join(errors.New("failed to get package unique identifier"), err).Error())
 					}
 
 					declaration, err := manager.SavePackage(pluginUniqueIdentifier, asset)
 					if err != nil {
-						return entities.NewErrorResponse(-500, err.Error())
+						return entities.NewErrorResponse(-500, errors.Join(errors.New("failed to save package"), err).Error())
 					}
 
 					if config.ForceVerifyingSignature || verify_signature {
 						if !declaration.Verified {
-							return entities.NewErrorResponse(-500, errors.Join(err, errors.New(
+							return entities.NewErrorResponse(-500, errors.Join(errors.New(
 								"plugin verification has been enabled, and the plugin you want to install has a bad signature",
-							)).Error())
+							), err).Error())
 						}
 					}