revert: remove Go function (runtime compilation timeout)
This commit is contained in:
+126
-21
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user