diff --git a/.gitignore b/.gitignore index f0ed2c9..128e7cf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ bin/ *.test *.out +# Compiled function binaries (generated, not versioned) +examples/*/dist/ + # Provider binaries terraform-provider-fission terraform-provider-fission_* diff --git a/examples/go-hello/code/main.go b/examples/go-hello/code/main.go new file mode 100644 index 0000000..e15c7dc --- /dev/null +++ b/examples/go-hello/code/main.go @@ -0,0 +1,26 @@ +package main + +import "fmt" + +// Handler используется binary-env: binary получает HTTP request через stdin, +// stdout является HTTP response body. +// +// Для деплоя нужно скомпилировать перед terraform apply: +// CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o ../dist/handler . +// +// Compiled size: ~1.6MB (слишком велико для literal deployment). +// Используется shell-based handler в dist/handler (см. ниже). +func main() { + fmt.Print("Hello from Go in Fission") +} +package main + +import ( + "fmt" + "net/http" +) + +func Handler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintf(w, "Hello from Go in Fission") +} diff --git a/examples/go-hello/main.tf b/examples/go-hello/main.tf new file mode 100644 index 0000000..7e3d69e --- /dev/null +++ b/examples/go-hello/main.tf @@ -0,0 +1,42 @@ +terraform { + required_providers { + fission = { + source = "nail/fission" + version = "~> 0.1.0" + } + } +} + +provider "fission" { + kubeconfig_path = "/home/naeel/.kube/config" + namespace = "default" +} + +# Go использует binary-env: функция является скомпилированным Linux-бинарником. +# Перед apply необходимо скомпилировать: +# CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o dist/handler code/ +resource "fission_environment" "go" { + name = "tf-go-hello-env" + image = "ghcr.io/fission/binary-env" + version = 3 +} + +resource "fission_package" "pkg" { + name = "tf-go-hello-pkg" + environment = fission_environment.go.name + code_path = "${path.module}/dist/handler" +} + +resource "fission_function" "fn" { + name = "tf-go-hello-fn" + environment = fission_environment.go.name + package_name = fission_package.pkg.name + entrypoint = "handler" +} + +resource "fission_http_trigger" "route" { + name = "tf-go-hello-route" + function = fission_function.fn.name + url = "/go-hello" + methods = ["GET"] +} diff --git a/examples/perl-hello/code/main.pl b/examples/perl-hello/code/main.pl new file mode 100644 index 0000000..e5e75e0 --- /dev/null +++ b/examples/perl-hello/code/main.pl @@ -0,0 +1,3 @@ +sub { + return "Hello from Perl in Fission"; +} diff --git a/examples/perl-hello/main.tf b/examples/perl-hello/main.tf new file mode 100644 index 0000000..4377028 --- /dev/null +++ b/examples/perl-hello/main.tf @@ -0,0 +1,40 @@ +terraform { + required_providers { + fission = { + source = "nail/fission" + version = "~> 0.1.0" + } + } +} + +provider "fission" { + kubeconfig_path = "/home/naeel/.kube/config" + namespace = "default" +} + +# version = 1: Perl env uses v1 specialization (/specialize endpoint) +resource "fission_environment" "perl" { + name = "tf-perl-hello-env" + image = "ghcr.io/fission/perl-env" + version = 1 +} + +resource "fission_package" "pkg" { + name = "tf-perl-hello-pkg" + environment = fission_environment.perl.name + source_dir = "${path.module}/code" +} + +resource "fission_function" "fn" { + name = "tf-perl-hello-fn" + environment = fission_environment.perl.name + package_name = fission_package.pkg.name + entrypoint = "handler" +} + +resource "fission_http_trigger" "route" { + name = "tf-perl-hello-route" + function = fission_function.fn.name + url = "/perl-hello" + methods = ["GET"] +} diff --git a/examples/php-hello/code/main.php b/examples/php-hello/code/main.php new file mode 100644 index 0000000..e908e39 --- /dev/null +++ b/examples/php-hello/code/main.php @@ -0,0 +1,7 @@ +getBody()->write("Hello from PHP in Fission"); +} diff --git a/examples/php-hello/main.tf b/examples/php-hello/main.tf new file mode 100644 index 0000000..8cd779b --- /dev/null +++ b/examples/php-hello/main.tf @@ -0,0 +1,39 @@ +terraform { + required_providers { + fission = { + source = "nail/fission" + version = "~> 0.1.0" + } + } +} + +provider "fission" { + kubeconfig_path = "/home/naeel/.kube/config" + namespace = "default" +} + +resource "fission_environment" "php" { + name = "tf-php-hello-env" + image = "ghcr.io/fission/php-env" + version = 3 +} + +resource "fission_package" "pkg" { + name = "tf-php-hello-pkg" + environment = fission_environment.php.name + source_dir = "${path.module}/code" +} + +resource "fission_function" "fn" { + name = "tf-php-hello-fn" + environment = fission_environment.php.name + package_name = fission_package.pkg.name + entrypoint = "main.php::handler" +} + +resource "fission_http_trigger" "route" { + name = "tf-php-hello-route" + function = fission_function.fn.name + url = "/php-hello" + methods = ["GET"] +} diff --git a/examples/ruby-hello/code/main.rb b/examples/ruby-hello/code/main.rb new file mode 100644 index 0000000..5d9be58 --- /dev/null +++ b/examples/ruby-hello/code/main.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +def handler + "Hello from Ruby in Fission" +end diff --git a/examples/ruby-hello/main.tf b/examples/ruby-hello/main.tf new file mode 100644 index 0000000..0e9859f --- /dev/null +++ b/examples/ruby-hello/main.tf @@ -0,0 +1,39 @@ +terraform { + required_providers { + fission = { + source = "nail/fission" + version = "~> 0.1.0" + } + } +} + +provider "fission" { + kubeconfig_path = "/home/naeel/.kube/config" + namespace = "default" +} + +resource "fission_environment" "ruby" { + name = "tf-ruby-hello-env" + image = "ghcr.io/fission/ruby-env" + version = 3 +} + +resource "fission_package" "pkg" { + name = "tf-ruby-hello-pkg" + environment = fission_environment.ruby.name + source_dir = "${path.module}/code" +} + +resource "fission_function" "fn" { + name = "tf-ruby-hello-fn" + environment = fission_environment.ruby.name + package_name = fission_package.pkg.name + entrypoint = "handler" +} + +resource "fission_http_trigger" "route" { + name = "tf-ruby-hello-route" + function = fission_function.fn.name + url = "/ruby-hello" + methods = ["GET"] +} diff --git a/terraform/provider/internal/resources/function_resource.go b/terraform/provider/internal/resources/function_resource.go index 33fac2c..09124c2 100644 --- a/terraform/provider/internal/resources/function_resource.go +++ b/terraform/provider/internal/resources/function_resource.go @@ -290,9 +290,16 @@ func unstructuredToFunctionModel(functionObject *unstructured.Unstructured, base } func validateEntrypointAgainstPackageSource(entrypoint string, pkg *unstructured.Unstructured) error { + if entrypoint == "" { + return fmt.Errorf("entrypoint не может быть пустым") + } + + // Для Python/Go/JS: валидируем формат module.function и наличие функции в исходнике. + // Для PHP (module::function), Ruby (function), Perl (function) — допускаем любой непустой формат. parts := strings.Split(entrypoint, ".") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return fmt.Errorf("entrypoint %q должен иметь формат module.function", entrypoint) + // Не стандартный module.function — допускаем (PHP, Ruby, Perl и др.) + return nil } if parts[0] != "main" { diff --git a/terraform/provider/internal/resources/function_resource_test.go b/terraform/provider/internal/resources/function_resource_test.go index 0996d61..88ff0ed 100644 --- a/terraform/provider/internal/resources/function_resource_test.go +++ b/terraform/provider/internal/resources/function_resource_test.go @@ -70,7 +70,12 @@ func TestValidateEntrypointAgainstPackageSourcePythonMissing(t *testing.T) { func TestValidateEntrypointAgainstPackageSourceBadFormat(t *testing.T) { pkg := &unstructured.Unstructured{} - if err := validateEntrypointAgainstPackageSource("main", pkg); err == nil { - t.Fatalf("expected validation error for bad entrypoint format") + // Пустой entrypoint должен быть ошибкой + if err := validateEntrypointAgainstPackageSource("", pkg); err == nil { + t.Fatalf("expected validation error for empty entrypoint") + } + // Одиночное слово допустимо (Ruby, Perl) + if err := validateEntrypointAgainstPackageSource("handler", pkg); err != nil { + t.Fatalf("unexpected error for single-word entrypoint: %v", err) } } diff --git a/terraform/provider/internal/resources/package_resource.go b/terraform/provider/internal/resources/package_resource.go index 8a27051..d4d7105 100644 --- a/terraform/provider/internal/resources/package_resource.go +++ b/terraform/provider/internal/resources/package_resource.go @@ -71,7 +71,7 @@ func (r *PackageResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, "source_dir": schema.StringAttribute{ Optional: true, - Description: "Путь к директории с кодом (поддерживаются main.py, main.js, main.go).", + Description: "Путь к директории с кодом (поддерживаются main.py, main.js, main.go, main.php, main.rb, main.pl).", }, "code_path": schema.StringAttribute{ Optional: true, @@ -355,7 +355,7 @@ func loadPackageLiteral(sourceDir, codePath string) ([]byte, error) { // resolveMainSourceFile выбирает основной файл исходника из source_dir. func resolveMainSourceFile(sourceDir string) (string, error) { - candidates := []string{"main.py", "main.js", "main.go"} + candidates := []string{"main.py", "main.js", "main.go", "main.php", "main.rb", "main.pl"} for _, candidate := range candidates { candidatePath := filepath.Join(sourceDir, candidate) fileInfo, err := os.Stat(candidatePath) @@ -364,7 +364,7 @@ func resolveMainSourceFile(sourceDir string) (string, error) { } } - return "", fmt.Errorf("source_dir %q must contain one of: main.py, main.js, main.go", sourceDir) + return "", fmt.Errorf("source_dir %q must contain one of: main.py, main.js, main.go, main.php, main.rb, main.pl", sourceDir) } // packageToUnstructured преобразует Terraform model в Kubernetes CRD payload.