ソースを参照

feat: build dockerfile for aws lambda

Yeuoly 11 ヶ月 前
コミット
28ca3e1785

+ 30 - 0
internal/core/plugin_manager/aws_manager/dockerfile/build.go

@@ -0,0 +1,30 @@
+package dockerfile
+
+import (
+	"fmt"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/strings"
+)
+
+func handleTemplate(configuration *plugin_entities.PluginDeclaration, templateFunc func(configuration *plugin_entities.PluginDeclaration) (string, error)) (string, error) {
+	if templateFunc == nil {
+		return "", fmt.Errorf("template function is nil, language: %s, version: %s", configuration.Meta.Runner.Language, configuration.Meta.Runner.Version)
+	}
+	return templateFunc(configuration)
+}
+
+// GenerateDockerfile generates a Dockerfile for the plugin
+func GenerateDockerfile(configuration *plugin_entities.PluginDeclaration) (string, error) {
+	if !strings.Find(configuration.Meta.Arch, constants.AMD64) {
+		return "", fmt.Errorf("unsupported architecture: %s", configuration.Meta.Arch)
+	}
+
+	switch configuration.Meta.Runner.Language {
+	case constants.Python:
+		return handleTemplate(configuration, pythonTemplates[configuration.Meta.Runner.Version])
+	}
+
+	return "", fmt.Errorf("unsupported language: %s", configuration.Meta.Runner.Language)
+}

+ 41 - 0
internal/core/plugin_manager/aws_manager/dockerfile/build_test.go

@@ -0,0 +1,41 @@
+package dockerfile
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+)
+
+func preparePluginDeclaration() *plugin_entities.PluginDeclaration {
+	return &plugin_entities.PluginDeclaration{
+		Meta: plugin_entities.PluginMeta{
+			Runner: plugin_entities.PluginRunner{
+				Language:   constants.Python,
+				Version:    "3.12",
+				Entrypoint: "main",
+			},
+		},
+	}
+}
+
+func TestGenerateDockerfile(t *testing.T) {
+	pluginDeclaration := preparePluginDeclaration()
+	dockerfile, err := GenerateDockerfile(pluginDeclaration)
+	if err != nil {
+		t.Fatalf("Error generating Dockerfile: %v", err)
+	}
+
+	if !strings.Contains(dockerfile, "main") || !strings.Contains(dockerfile, "3.12") {
+		t.Logf("Generated Dockerfile: %s", dockerfile)
+	}
+}
+
+func TestGenerateDockerfileWithInvalidPluginDeclaration(t *testing.T) {
+	pluginDeclaration := &plugin_entities.PluginDeclaration{}
+	_, err := GenerateDockerfile(pluginDeclaration)
+	if err == nil {
+		t.Fatalf("Expected error, got nil")
+	}
+}

+ 24 - 0
internal/core/plugin_manager/aws_manager/dockerfile/python.go

@@ -0,0 +1,24 @@
+package dockerfile
+
+import (
+	_ "embed"
+	"strings"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+)
+
+var (
+	pythonTemplates = map[string]func(configuration *plugin_entities.PluginDeclaration) (string, error){
+		"3.12": GeneratePython312Dockerfile,
+	}
+)
+
+//go:embed python312.dockerfile
+var python312DockerfileTmpl string
+
+// GeneratePython312Dockerfile generates a dockerfile for python 3.12
+func GeneratePython312Dockerfile(configuration *plugin_entities.PluginDeclaration) (string, error) {
+	entrypoint := configuration.Meta.Runner.Entrypoint
+
+	return strings.Replace(python312DockerfileTmpl, "{{entrypoint}}", entrypoint, -1), nil
+}

+ 8 - 0
internal/core/plugin_manager/aws_manager/dockerfile/python312.dockerfile

@@ -0,0 +1,8 @@
+FROM public.ecr.aws/docker/library/python:3.12.0-slim-bullseye
+COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
+
+WORKDIR /app
+ADD . /app
+RUN pip install -r requirements.txt
+
+CMD ["python", "-m", "{{entrypoint}}"]

+ 22 - 0
internal/core/plugin_manager/aws_manager/packager.go

@@ -0,0 +1,22 @@
+package aws_manager
+
+import (
+	"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_packager/decoder"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities"
+)
+
+type Packager struct {
+	runtime entities.PluginRuntime
+	decoder decoder.PluginDecoder
+}
+
+func NewPackager(runtime entities.PluginRuntime, decoder decoder.PluginDecoder) *Packager {
+	return &Packager{
+		runtime: runtime,
+		decoder: decoder,
+	}
+}
+
+// Pack takes a plugin and packs it into a tar file with dockerfile inside
+// for the
+func (p *Packager) Pack()

+ 1 - 1
internal/core/plugin_manager/remote_manager/server_test.go

@@ -147,7 +147,7 @@ func TestAcceptConnection(t *testing.T) {
 		Plugins: []string{
 			"test",
 		},
-		Execution: plugin_entities.PluginDeclarationExecution{
+		Execution: plugin_entities.PluginExecution{
 			Install: "echo 'hello'",
 			Launch:  "echo 'hello'",
 		},

+ 28 - 0
internal/types/entities/constants/arch.go

@@ -0,0 +1,28 @@
+package constants
+
+import (
+	"github.com/go-playground/validator/v10"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/validators"
+)
+
+type Arch string
+
+const (
+	AMD64 Arch = "amd64"
+	ARM64 Arch = "arm64"
+)
+
+func isAWSLambdaSupportedArch(fl validator.FieldLevel) bool {
+	value := fl.Field().String()
+	return value == string(AMD64)
+}
+
+func isAvailableArch(fl validator.FieldLevel) bool {
+	value := fl.Field().String()
+	return value == string(AMD64) || value == string(ARM64)
+}
+
+func init() {
+	validators.GlobalEntitiesValidator.RegisterValidation("is_aws_lambda_supported_arch", isAWSLambdaSupportedArch)
+	validators.GlobalEntitiesValidator.RegisterValidation("is_available_arch", isAvailableArch)
+}

+ 25 - 0
internal/types/entities/constants/language.go

@@ -0,0 +1,25 @@
+package constants
+
+import (
+	"github.com/go-playground/validator/v10"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/validators"
+)
+
+type Language string
+
+const (
+	Python Language = "python"
+)
+
+func isAvailableLanguage(fl validator.FieldLevel) bool {
+	value := fl.Field().String()
+	switch value {
+	case string(Python):
+		return true
+	}
+	return false
+}
+
+func init() {
+	validators.GlobalEntitiesValidator.RegisterValidation("is_available_language", isAvailableLanguage)
+}

+ 19 - 26
internal/types/entities/plugin_entities/plugin_declaration.go

@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/go-playground/validator/v10"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/validators"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
 )
@@ -91,40 +92,33 @@ type PluginResourceRequirement struct {
 
 type PluginDeclarationPlatformArch string
 
-const (
-	PLUGIN_PLATFORM_ARCH_AMD64 PluginDeclarationPlatformArch = "amd64"
-	PLUGIN_PLATFORM_ARCH_ARM64 PluginDeclarationPlatformArch = "arm64"
-)
-
-func isPluginDeclarationPlatformArch(fl validator.FieldLevel) bool {
-	value := fl.Field().String()
-	switch value {
-	case string(PLUGIN_PLATFORM_ARCH_AMD64),
-		string(PLUGIN_PLATFORM_ARCH_ARM64):
-		return true
-	}
-	return false
+type PluginRunner struct {
+	Language   constants.Language `json:"language" yaml:"language" validate:"required,is_available_language"`
+	Version    string             `json:"version" yaml:"version" validate:"required,max=128"`
+	Entrypoint string             `json:"entrypoint" yaml:"entrypoint" validate:"required,max=256"`
 }
 
-type PluginDeclarationMeta struct {
-	Version string   `json:"version" yaml:"version" validate:"required"`
-	Arch    []string `json:"arch" yaml:"arch" validate:"required,dive,plugin_declaration_platform_arch"`
+type PluginMeta struct {
+	Version string           `json:"version" yaml:"version" validate:"required,version"`
+	Arch    []constants.Arch `json:"arch" yaml:"arch" validate:"required,dive,is_available_arch"`
+	Runner  PluginRunner     `json:"runner" yaml:"runner" validate:"required"`
 }
 
-type PluginDeclarationExecution struct {
+type PluginExecution struct {
 	Install string `json:"install" yaml:"install" validate:"omitempty"`
 	Launch  string `json:"launch" yaml:"launch" validate:"omitempty"`
 }
 
 type PluginDeclaration struct {
-	Version   string                     `json:"version" yaml:"version" validate:"required,version"`
-	Type      DifyManifestType           `json:"type" yaml:"type" validate:"required,eq=plugin"`
-	Author    string                     `json:"author" yaml:"author" validate:"required"`
-	Name      string                     `json:"name" yaml:"name" validate:"required" enum:"plugin"`
-	CreatedAt time.Time                  `json:"created_at" yaml:"created_at" validate:"required"`
-	Resource  PluginResourceRequirement  `json:"resource" yaml:"resource" validate:"required"`
-	Plugins   []string                   `json:"plugins" yaml:"plugins" validate:"required"`
-	Execution PluginDeclarationExecution `json:"execution" yaml:"execution" validate:"required"`
+	Version   string                    `json:"version" yaml:"version" validate:"required,version"`
+	Type      DifyManifestType          `json:"type" yaml:"type" validate:"required,eq=plugin"`
+	Author    string                    `json:"author" yaml:"author" validate:"required,max=128"`
+	Name      string                    `json:"name" yaml:"name" validate:"required,max=128" enum:"plugin"`
+	CreatedAt time.Time                 `json:"created_at" yaml:"created_at" validate:"required"`
+	Resource  PluginResourceRequirement `json:"resource" yaml:"resource" validate:"required"`
+	Plugins   []string                  `json:"plugins" yaml:"plugins" validate:"required,dive,max=128"`
+	Execution PluginExecution           `json:"execution" yaml:"execution" validate:"required"`
+	Meta      PluginMeta                `json:"meta" yaml:"meta" validate:"required"`
 }
 
 var (
@@ -143,7 +137,6 @@ func (p *PluginDeclaration) Identity() string {
 
 func init() {
 	// init validator
-	validators.GlobalEntitiesValidator.RegisterValidation("plugin_declaration_platform_arch", isPluginDeclarationPlatformArch)
 	validators.GlobalEntitiesValidator.RegisterValidation("version", isVersion)
 }
 

+ 37 - 1
internal/types/entities/plugin_entities/plugin_declaration_test.go

@@ -4,6 +4,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
 )
 
@@ -30,10 +31,21 @@ func preparePluginDeclaration() PluginDeclaration {
 			},
 		},
 		Plugins: []string{},
-		Execution: PluginDeclarationExecution{
+		Execution: PluginExecution{
 			Install: "echo 'hello'",
 			Launch:  "echo 'hello'",
 		},
+		Meta: PluginMeta{
+			Version: "0.0.1",
+			Arch: []constants.Arch{
+				constants.AMD64,
+			},
+			Runner: PluginRunner{
+				Language:   constants.Python,
+				Version:    "3.12",
+				Entrypoint: "main",
+			},
+		},
 	}
 }
 
@@ -98,6 +110,30 @@ func TestPluginDeclarationIncorrectVersion(t *testing.T) {
 	}
 }
 
+func TestPluginUnsupportedLanguage(t *testing.T) {
+	declaration := preparePluginDeclaration()
+	declaration.Meta.Runner.Language = "test"
+	declaration_bytes := parser.MarshalJsonBytes(declaration)
+
+	_, err := parser.UnmarshalJsonBytes[PluginDeclaration](declaration_bytes)
+	if err == nil {
+		t.Errorf("failed to validate language")
+		return
+	}
+}
+
+func TestPluginUnsupportedArch(t *testing.T) {
+	declaration := preparePluginDeclaration()
+	declaration.Meta.Arch[0] = constants.Arch("test")
+	declaration_bytes := parser.MarshalJsonBytes(declaration)
+
+	_, err := parser.UnmarshalJsonBytes[PluginDeclaration](declaration_bytes)
+	if err == nil {
+		t.Errorf("failed to validate arch")
+		return
+	}
+}
+
 func TestPluginDeclarationIncorrectType(t *testing.T) {
 	declaration := preparePluginDeclaration()
 	declaration.Type = "test"

+ 10 - 0
internal/utils/strings/find.go

@@ -0,0 +1,10 @@
+package strings
+
+func Find[T comparable](slice []T, value T) bool {
+	for _, v := range slice {
+		if v == value {
+			return true
+		}
+	}
+	return false
+}