pre-golang-removal: snapshot before removing all golang integration (2026-05-01)
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
package fission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fission-console/internal/model"
|
||||
"fission-console/internal/runtime"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
const (
|
||||
functionCreatedAtAnnotation = "fission-console/createdAt"
|
||||
functionUpdatedAtAnnotation = "fission-console/updatedAt"
|
||||
)
|
||||
|
||||
// CreateFunctionResult holds the result of a CreateFunction call.
|
||||
type CreateFunctionResult struct {
|
||||
Name string `json:"name"`
|
||||
Package string `json:"package"`
|
||||
HTTPTrigger string `json:"httptrigger"`
|
||||
Route string `json:"route"`
|
||||
ExpiresAt string `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateFunctionResult holds the result of an UpdateFunctionCode call.
|
||||
type UpdateFunctionResult struct {
|
||||
Updated bool `json:"updated"`
|
||||
Package string `json:"package"`
|
||||
}
|
||||
|
||||
// CreateFunction orchestrates the creation of a complete Fission function set: Package, Function, and HTTPTrigger.
|
||||
func CreateFunction(ctx context.Context, dyn dynamic.Interface, ns string, req model.CreateFunctionRequest) (*CreateFunctionResult, error) {
|
||||
pkgName := req.Name + "-pkg"
|
||||
triggerName := req.Name + "-route"
|
||||
|
||||
methodValues := make([]any, 0, len(req.Methods))
|
||||
for _, method := range req.Methods {
|
||||
methodValues = append(methodValues, method)
|
||||
}
|
||||
|
||||
var pkgSpec map[string]any
|
||||
if req.Language == "go" {
|
||||
srcZip, err := runtime.BuildGoSourceZip(req.Code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build go source archive: %w", err)
|
||||
}
|
||||
literal := base64.StdEncoding.EncodeToString(srcZip)
|
||||
pkgSpec = map[string]any{
|
||||
"source": map[string]any{
|
||||
"type": "literal",
|
||||
"literal": literal,
|
||||
},
|
||||
"deployment": map[string]any{},
|
||||
"environment": map[string]any{"name": req.Environment, "namespace": ns},
|
||||
"buildcommand": "build",
|
||||
}
|
||||
} else {
|
||||
deployBytes, archiveErr := runtime.BuildDeployArchive(req.Language, req.Code)
|
||||
if archiveErr != nil {
|
||||
return nil, fmt.Errorf("build %s archive: %w", req.Language, archiveErr)
|
||||
}
|
||||
pkgSpec = map[string]any{
|
||||
"deployment": map[string]any{"type": "literal", "literal": base64.StdEncoding.EncodeToString(deployBytes)},
|
||||
"environment": map[string]any{"name": req.Environment, "namespace": ns},
|
||||
"source": map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
pkg := &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "Package",
|
||||
"metadata": map[string]any{"name": pkgName, "namespace": ns},
|
||||
"spec": pkgSpec,
|
||||
}}
|
||||
|
||||
fnAnnotations := map[string]any{
|
||||
"fission-console/language": req.Language,
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
fnAnnotations[functionCreatedAtAnnotation] = now.Format(time.RFC3339)
|
||||
fnAnnotations[functionUpdatedAtAnnotation] = now.Format(time.RFC3339)
|
||||
var expiresAtStr string
|
||||
if req.TTL != "" {
|
||||
expiresAt, ttlErr := parseTTL(req.TTL)
|
||||
if ttlErr != nil {
|
||||
return nil, fmt.Errorf("invalid ttl %q: %w", req.TTL, ttlErr)
|
||||
}
|
||||
expiresAtStr = expiresAt.UTC().Format(time.RFC3339)
|
||||
fnAnnotations["fission-console/expires-at"] = expiresAtStr
|
||||
}
|
||||
|
||||
if _, err := dyn.Resource(PackageGVR).Namespace(ns).Create(ctx, pkg, metav1.CreateOptions{}); err != nil {
|
||||
return nil, fmt.Errorf("create package: %w", err)
|
||||
}
|
||||
|
||||
fn := &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "Function",
|
||||
"metadata": map[string]any{"name": req.Name, "namespace": ns, "annotations": fnAnnotations},
|
||||
"spec": map[string]any{
|
||||
"environment": map[string]any{"name": req.Environment, "namespace": ns},
|
||||
"functionTimeout": req.Timeout,
|
||||
"InvokeStrategy": map[string]any{
|
||||
"ExecutionStrategy": map[string]any{"ExecutorType": "poolmgr"},
|
||||
"StrategyType": "execution",
|
||||
},
|
||||
"package": map[string]any{
|
||||
"packageref": map[string]any{"name": pkgName, "namespace": ns},
|
||||
"functionName": req.Entrypoint,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
if _, err := dyn.Resource(FunctionGVR).Namespace(ns).Create(ctx, fn, metav1.CreateOptions{}); err != nil {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), pkgName, metav1.DeleteOptions{})
|
||||
return nil, fmt.Errorf("create function: %w", err)
|
||||
}
|
||||
|
||||
httpTrigger := &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "HTTPTrigger",
|
||||
"metadata": map[string]any{"name": triggerName, "namespace": ns},
|
||||
"spec": map[string]any{
|
||||
"relativeurl": req.Route,
|
||||
"methods": methodValues,
|
||||
"createingress": true,
|
||||
"functionref": map[string]any{"type": "name", "name": req.Name},
|
||||
},
|
||||
}}
|
||||
|
||||
if _, err := dyn.Resource(HTTPTrigGVR).Namespace(ns).Create(ctx, httpTrigger, metav1.CreateOptions{}); err != nil {
|
||||
_ = dyn.Resource(FunctionGVR).Namespace(ns).Delete(context.Background(), req.Name, metav1.DeleteOptions{})
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), pkgName, metav1.DeleteOptions{})
|
||||
return nil, fmt.Errorf("create httptrigger: %w", err)
|
||||
}
|
||||
|
||||
return &CreateFunctionResult{
|
||||
Name: req.Name,
|
||||
Package: pkgName,
|
||||
HTTPTrigger: triggerName,
|
||||
Route: req.Route,
|
||||
ExpiresAt: expiresAtStr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateFunctionCode orchestrates updating the code of an existing function.
|
||||
func UpdateFunctionCode(ctx context.Context, dyn dynamic.Interface, ns, name string, req model.UpdateCodeRequest) (*UpdateFunctionResult, error) {
|
||||
fn, err := dyn.Resource(FunctionGVR).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get function %q: %w", name, err)
|
||||
}
|
||||
|
||||
oldPkgName, _, _ := unstructured.NestedString(fn.Object, "spec", "package", "packageref", "name")
|
||||
lang, _, _ := unstructured.NestedString(fn.Object, "metadata", "annotations", "fission-console/language")
|
||||
|
||||
deployBytes, archiveErr := runtime.BuildDeployArchive(lang, req.Code)
|
||||
if archiveErr != nil {
|
||||
return nil, fmt.Errorf("build %s archive: %w", lang, archiveErr)
|
||||
}
|
||||
|
||||
envName, _, _ := unstructured.NestedString(fn.Object, "spec", "environment", "name")
|
||||
newPkgName := name + "-pkg-" + strconv.FormatInt(time.Now().UnixMilli(), 36)
|
||||
newPkg := &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "Package",
|
||||
"metadata": map[string]any{"name": newPkgName, "namespace": ns},
|
||||
"spec": map[string]any{
|
||||
"deployment": map[string]any{"type": "literal", "literal": base64.StdEncoding.EncodeToString(deployBytes)},
|
||||
"environment": map[string]any{"name": envName, "namespace": ns},
|
||||
"source": map[string]any{},
|
||||
},
|
||||
}}
|
||||
|
||||
createdPkg, err := dyn.Resource(PackageGVR).Namespace(ns).Create(ctx, newPkg, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create new package: %w", err)
|
||||
}
|
||||
|
||||
if err := unstructured.SetNestedField(fn.Object, req.Timeout, "spec", "functionTimeout"); err != nil {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), newPkgName, metav1.DeleteOptions{})
|
||||
return nil, fmt.Errorf("set function timeout: %w", err)
|
||||
}
|
||||
|
||||
annotations := fn.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[functionUpdatedAtAnnotation] = time.Now().UTC().Format(time.RFC3339)
|
||||
fn.SetAnnotations(annotations)
|
||||
|
||||
if err := unstructured.SetNestedField(fn.Object, map[string]any{
|
||||
"name": newPkgName,
|
||||
"namespace": ns,
|
||||
"resourceversion": createdPkg.GetResourceVersion(),
|
||||
}, "spec", "package", "packageref"); err != nil {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), newPkgName, metav1.DeleteOptions{})
|
||||
return nil, fmt.Errorf("set function packageref: %w", err)
|
||||
}
|
||||
|
||||
if _, err := dyn.Resource(FunctionGVR).Namespace(ns).Update(ctx, fn, metav1.UpdateOptions{}); err != nil {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), newPkgName, metav1.DeleteOptions{})
|
||||
return nil, fmt.Errorf("update function %q: %w", name, err)
|
||||
}
|
||||
|
||||
if oldPkgName != "" && oldPkgName != newPkgName {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(context.Background(), oldPkgName, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
return &UpdateFunctionResult{
|
||||
Updated: true,
|
||||
Package: newPkgName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteFunction orchestrates the deletion of a function and its related resources.
|
||||
func DeleteFunction(ctx context.Context, dyn dynamic.Interface, ns, name string) error {
|
||||
// Best-effort deletion of related resources.
|
||||
_ = dyn.Resource(HTTPTrigGVR).Namespace(ns).Delete(ctx, name+"-route", metav1.DeleteOptions{})
|
||||
|
||||
fn, err := dyn.Resource(FunctionGVR).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("get function for deletion: %w", err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if pkgName, found, _ := unstructured.NestedString(fn.Object, "spec", "package", "packageref", "name"); found && pkgName != "" {
|
||||
_ = dyn.Resource(PackageGVR).Namespace(ns).Delete(ctx, pkgName, metav1.DeleteOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
if err := dyn.Resource(FunctionGVR).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("delete function: %w", err)
|
||||
}
|
||||
|
||||
if fn != nil {
|
||||
if envName, found, _ := unstructured.NestedString(fn.Object, "spec", "environment", "name"); found && envName != "" {
|
||||
CleanupEnvironmentIfUnused(context.Background(), dyn, ns, envName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTTL(ttl string) (time.Time, error) {
|
||||
duration, err := time.ParseDuration(ttl)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Now().UTC().Add(duration), nil
|
||||
}
|
||||
Reference in New Issue
Block a user