feat: multi-language support — PHP, Ruby, Perl, Go (binary-env)

- PHP: ghcr.io/fission/php-env, entrypoint main.php::handler
- Ruby: ghcr.io/fission/ruby-env, entrypoint handler (single-word)
- Perl: ghcr.io/fission/perl-env (version=1), sub {} return value
- Go: ghcr.io/fission/binary-env with pre-compiled binary (shell script placeholder)

Provider changes:
- package_resource: added main.php, main.rb, main.pl to source_dir candidates
- function_resource: relaxed entrypoint validation to allow PHP (::), Ruby, Perl (single-word)
- function_resource_test: updated BadFormat test to reflect new valid formats
This commit is contained in:
Naeel
2026-04-15 17:01:40 +03:00
parent c78b539d24
commit 6d66e5b566
12 changed files with 222 additions and 6 deletions
+3
View File
@@ -8,6 +8,9 @@ bin/
*.test
*.out
# Compiled function binaries (generated, not versioned)
examples/*/dist/
# Provider binaries
terraform-provider-fission
terraform-provider-fission_*
+26
View File
@@ -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")
}
+42
View File
@@ -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"]
}
+3
View File
@@ -0,0 +1,3 @@
sub {
return "Hello from Perl in Fission";
}
+40
View File
@@ -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"]
}
+7
View File
@@ -0,0 +1,7 @@
<?php
function handler($context)
{
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $context["response"];
$response->getBody()->write("Hello from PHP in Fission");
}
+39
View File
@@ -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"]
}
+5
View File
@@ -0,0 +1,5 @@
# frozen_string_literal: true
def handler
"Hello from Ruby in Fission"
end
+39
View File
@@ -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"]
}
@@ -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" {
@@ -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)
}
}
@@ -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.