feat: go1.23 runtime support

- runtimes/go1.23/server.go: HTTP-wrapper + job-runner (SLESS_MODE=job)
- runtimes/go1.23/go.mod: module sless/fn (изолирует от корневого go.mod)
- runtimes/go1.23/Dockerfile: multi-stage build (golang:1.23-alpine → alpine:3.20)
- internal/builder/context.go: go1.23 в runtimeBaseImage + generateDockerfile
- controllers/functionjob_controller.go: go1.23 runner (nil cmd + SLESS_MODE=job env)
- api/v1alpha1/function_types.go: enum go1.21 → go1.23
- config/crd/bases/...: CRD обновлён
- terraform/provider: OneOf обновлён
- examples/hello-go: HTTP + job примеры на go1.23
- deployments/k8s/operator.yaml: v0.1.25
This commit is contained in:
“Naeel”
2026-03-11 15:40:47 +04:00
parent babd8e6109
commit a709b38f6b
14 changed files with 290 additions and 24 deletions
+45 -15
View File
@@ -35,16 +35,19 @@ func PrepareContext(zipData []byte, runtime string) (*bytes.Buffer, error) {
// Это знание принадлежит builder-у, не HTTP-хендлеру.
hasRequirements := false // requirements.txt — python3.11: pip install
hasPackageJSON := false // package.json — nodejs20: npm install
hasGoMod := false // go.mod — go1.23: пользователь может дать свой go.mod
for _, f := range zr.File {
switch f.Name {
case "requirements.txt":
hasRequirements = true
case "package.json":
hasPackageJSON = true
case "go.mod":
hasGoMod = true
}
}
dockerfileContent, err := generateDockerfile(runtime, hasRequirements, hasPackageJSON)
dockerfileContent, err := generateDockerfile(runtime, hasRequirements, hasPackageJSON, hasGoMod)
if err != nil {
// Ошибка здесь означает неподдерживаемый runtime — 400 на уровне handler'а.
return nil, err
@@ -66,33 +69,60 @@ func runtimeBaseImage(runtime string) (string, error) {
return "naeel/sless-runtime-python3.11:v0.1.1", nil
case "nodejs20":
return "naeel/sless-runtime-nodejs20:v0.1.2", nil
case "go1.23":
// Go использует builder-образ (содержит golang:1.23 + server.go).
// Конечный образ multi-stage — alpine без компилятора.
return "naeel/sless-runtime-go1.23:v0.1.0", nil
default:
return "", fmt.Errorf("unsupported runtime: %q (supported: python3.11, nodejs20)", runtime)
return "", fmt.Errorf("unsupported runtime: %q (supported: python3.11, nodejs20, go1.23)", runtime)
}
}
// generateDockerfile генерирует Dockerfile для kaniko.
// Базовый образ содержит HTTP-обёртку (server.py / server.js).
// Пользовательский код копируется в /app/function/ поверх базового образа.
// Базовый образ содержит HTTP-обёртку (server.py / server.js) или builder (для Go).
// Пользовательский код копируется в /app/function/ (или /app/handler/ для Go) поверх базового образа.
// Зависимости устанавливаются ПОСЛЕ COPY — чтобы кеш слоёв работал при повторных сборках.
func generateDockerfile(runtime string, hasRequirements bool, hasPackageJSON bool) ([]byte, error) {
func generateDockerfile(runtime string, hasRequirements bool, hasPackageJSON bool, hasGoMod bool) ([]byte, error) {
baseImage, err := runtimeBaseImage(runtime)
if err != nil {
return nil, err
}
content := fmt.Sprintf("FROM %s\nCOPY . /app/function/\n", baseImage)
switch runtime {
case "python3.11":
if hasRequirements {
content += "RUN pip install --no-cache-dir -r /app/function/requirements.txt\n"
}
case "nodejs20":
if hasPackageJSON {
// cd нужен т.к. npm install читает package.json из текущей директории
content += "RUN cd /app/function && npm install --omit=dev\n"
case "go1.23":
// Go: multi-stage build.
// base-образ содержит: server.go (package main) + go.mod (module sless/fn).
// kaniko копирует пользовательский код в handler/ (package handler).
// go build видит: /app/server.go + /app/handler/*.go под одним модулем sless/fn.
// Если у пользователя есть go.mod — он кладётся в /app/handler/go.mod (игнорируется).
content := fmt.Sprintf(
"FROM %s AS builder\n"+
"WORKDIR /app\n"+
"COPY handler/ /app/handler/\n"+
"RUN CGO_ENABLED=0 go build -o /server .\n"+
"FROM alpine:3.20\n"+
"COPY --from=builder /server /server\n"+
"EXPOSE 8080\n"+
"CMD [\"/server\"]\n",
baseImage,
)
return []byte(content), nil
default: // python3.11, nodejs20
content := fmt.Sprintf("FROM %s\nCOPY . /app/function/\n", baseImage)
switch runtime {
case "python3.11":
if hasRequirements {
content += "RUN pip install --no-cache-dir -r /app/function/requirements.txt\n"
}
case "nodejs20":
if hasPackageJSON {
// cd нужен т.к. npm install читает package.json из текущей директории
content += "RUN cd /app/function && npm install --omit=dev\n"
}
}
return []byte(content), nil
}
return []byte(content), nil
}
// zipToTarGz принимает уже распарсенный *zip.Reader и упаковывает содержимое + Dockerfile в tar.gz.