revert: remove Go function (runtime compilation timeout)

This commit is contained in:
Naeel
2026-04-15 16:24:57 +03:00
parent 66404fde3a
commit cccc5ca024
5 changed files with 198 additions and 72 deletions
+126 -21
View File
@@ -6,9 +6,11 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"sort"
@@ -39,11 +41,12 @@ var (
const defaultSATokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
type server struct {
dyn dynamic.Interface
ns string
routerURL string
http *http.Client
saTokenPath string
dyn dynamic.Interface
ns string
routerURL string
http *http.Client
saTokenPath string
invokeTimeout time.Duration
authUser string
authPass string
@@ -71,6 +74,8 @@ func main() {
namespace := envDefault("FISSION_NAMESPACE", "default")
routerURL := strings.TrimRight(envDefault("FISSION_ROUTER_URL", "http://router.fission.svc.cluster.local"), "/")
port := envDefault("PORT", "8090")
httpTimeout := envDurationDefault("FISSION_HTTP_TIMEOUT", 30*time.Second)
invokeTimeout := envDurationDefault("FISSION_INVOKE_TIMEOUT", 20*time.Second)
cfg, err := buildConfig(kubeconfig)
if err != nil {
@@ -87,13 +92,14 @@ func main() {
saTokenPath := envDefault("SA_TOKEN_PATH", defaultSATokenPath)
s := &server{
dyn: dyn,
ns: namespace,
routerURL: routerURL,
http: &http.Client{Timeout: 30 * time.Second},
saTokenPath: saTokenPath,
authUser: authUser,
authPass: authPass,
dyn: dyn,
ns: namespace,
routerURL: routerURL,
http: &http.Client{Timeout: httpTimeout},
saTokenPath: saTokenPath,
invokeTimeout: invokeTimeout,
authUser: authUser,
authPass: authPass,
}
mux := http.NewServeMux()
@@ -126,7 +132,7 @@ func main() {
httpServer := &http.Server{
Addr: ":" + port,
Handler: withCORS(logRequests(mux)),
Handler: withSecurityHeaders(withCORS(logRequests(mux))),
ReadHeaderTimeout: 10 * time.Second,
}
@@ -341,12 +347,7 @@ func (s *server) handleGetFunction(w http.ResponseWriter, r *http.Request, name
if packageName != "" {
pkg, pkgErr := s.dyn.Resource(packageGVR).Namespace(s.ns).Get(ctx, packageName, metav1.GetOptions{})
if pkgErr == nil {
literal, _, _ := unstructured.NestedString(pkg.Object, "spec", "deployment", "literal")
if literal != "" {
if decodedCode, decErr := decodeLiteralToSource(literal); decErr == nil {
code = decodedCode
}
}
code = s.extractPackageSourceCode(ctx, pkg)
}
}
@@ -459,7 +460,12 @@ func (s *server) handleInvokeFunction(w http.ResponseWriter, r *http.Request, na
bodyBytes = []byte("{}")
}
ctx, cancel := context.WithTimeout(r.Context(), 25*time.Second)
invokeTimeout := s.invokeTimeout
if invokeTimeout <= 0 {
invokeTimeout = 20 * time.Second
}
ctx, cancel := context.WithTimeout(r.Context(), invokeTimeout)
defer cancel()
invokeURL := fmt.Sprintf("%s/fission-function/v2/functions/%s", s.routerURL, name)
@@ -516,6 +522,15 @@ func (s *server) handleInvokeFunction(w http.ResponseWriter, r *http.Request, na
resp, err := s.http.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q timeout after %s: function specialization likely failed (for example, syntax error)", name, invokeTimeout))
return
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q timeout after %s: function specialization likely failed (for example, syntax error)", name, invokeTimeout))
return
}
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q: %v", name, err))
return
}
@@ -694,6 +709,17 @@ func withCORS(next http.Handler) http.Handler {
})
}
func withSecurityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests; block-all-mixed-content")
next.ServeHTTP(w, r)
})
}
func envDefault(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
@@ -701,6 +727,19 @@ func envDefault(key, fallback string) string {
return fallback
}
func envDurationDefault(key string, fallback time.Duration) time.Duration {
raw := strings.TrimSpace(os.Getenv(key))
if raw == "" {
return fallback
}
d, err := time.ParseDuration(raw)
if err != nil || d <= 0 {
log.Printf("invalid duration for %s=%q, using default %s", key, raw, fallback)
return fallback
}
return d
}
func normalizeMethods(in []string) []string {
if len(in) == 0 {
return []string{"GET"}
@@ -721,12 +760,78 @@ func normalizeMethods(in []string) []string {
return out
}
func (s *server) extractPackageSourceCode(ctx context.Context, pkg *unstructured.Unstructured) string {
literalPaths := [][]string{
{"spec", "source", "literal"},
{"spec", "deployment", "literal"},
}
for _, p := range literalPaths {
literal, found, _ := unstructured.NestedString(pkg.Object, p...)
if !found || strings.TrimSpace(literal) == "" {
continue
}
if decodedCode, decErr := decodeLiteralToSource(literal); decErr == nil && strings.TrimSpace(decodedCode) != "" {
return decodedCode
}
}
urlPaths := [][]string{
{"spec", "source", "url"},
{"spec", "deployment", "url"},
}
for _, p := range urlPaths {
urlValue, found, _ := unstructured.NestedString(pkg.Object, p...)
if !found || strings.TrimSpace(urlValue) == "" {
continue
}
archiveBytes, fetchErr := s.fetchPackageArchive(ctx, urlValue)
if fetchErr != nil {
continue
}
decodedCode, decErr := decodeArchiveBytesToSource(archiveBytes)
if decErr == nil && strings.TrimSpace(decodedCode) != "" {
return decodedCode
}
}
return ""
}
func (s *server) fetchPackageArchive(ctx context.Context, archiveURL string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, archiveURL, nil)
if err != nil {
return nil, err
}
resp, err := s.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("archive request failed: %s", resp.Status)
}
return io.ReadAll(resp.Body)
}
func decodeLiteralToSource(literal string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(literal)
if err != nil {
return "", err
}
return decodeArchiveBytesToSource(decoded)
}
func decodeArchiveBytesToSource(decoded []byte) (string, error) {
if len(decoded) == 0 {
return "", fmt.Errorf("empty payload")
}
if utf8.Valid(decoded) {
return string(decoded), nil
}
@@ -737,7 +842,7 @@ func decodeLiteralToSource(literal string) (string, error) {
}
}
return string(decoded), nil
return "", fmt.Errorf("payload does not contain utf-8 source")
}
func decodeZipSource(zipBytes []byte) (string, error) {