fix(cron): timer auth chain via console gateway, bump image to v1.3.9 - Added handleFissionFunctionGateway in handlers.go: accepts unauthenticated POST /fission-function/<ns>/<fn>/, fetches router JWT, proxies upstream - Registered public routes /fission-function and /fission-function/ in server.go - Bumped image tag v1.3.8 -> v1.3.9 in deploy/console.yaml - Live: patched timer Deployment --routerUrl to http://fission-console.fission.svc.cluster.local:8090 - Added doc/cron/CRON_TIMER_FIX_2026-04-29.md Root cause: timer reads --routerUrl CLI arg only (not FISSION_ROUTER_URL env), router.fission requires JWT that timer does not provide -> 401 on every tick.

This commit is contained in:
Naeel
2026-04-29 09:21:25 +03:00
parent 35fd9ec40e
commit 59a5b77ca9
5 changed files with 255 additions and 2 deletions
+108
View File
@@ -739,6 +739,114 @@ func buildInternalInvokeURL(routerURL, namespace, functionName string) string {
return fmt.Sprintf("%s/fission-function/%s/%s", routerURL, namespace, functionName)
}
// handleFissionFunctionGateway принимает внутренние invoke-запросы timer/router
// и проксирует их через console в upstream router с корректным router JWT.
func (s *Server) handleFissionFunctionGateway(w http.ResponseWriter, r *http.Request) {
rawPath := strings.Trim(strings.TrimPrefix(r.URL.Path, "/fission-function"), "/")
if rawPath == "" {
http.NotFound(w, r)
return
}
parts := strings.Split(rawPath, "/")
namespace := s.ns
functionName := ""
remainingPath := ""
if len(parts) == 1 {
functionName = strings.TrimSpace(parts[0])
} else {
namespace = strings.TrimSpace(parts[0])
functionName = strings.TrimSpace(parts[1])
if len(parts) > 2 {
remainingPath = "/" + strings.Join(parts[2:], "/")
}
}
if namespace == "" || functionName == "" {
writeJSONError(w, http.StatusBadRequest, "namespace and function name are required")
return
}
s.invokeInternalFunction(w, r, namespace, functionName, remainingPath)
}
func (s *Server) invokeInternalFunction(w http.ResponseWriter, r *http.Request, namespace, functionName, extraPath string) {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("read request body: %v", err))
return
}
if len(bytes.TrimSpace(bodyBytes)) == 0 {
bodyBytes = []byte("{}")
}
lookupCtx, lookupCancel := context.WithTimeout(r.Context(), 10*time.Second)
defer lookupCancel()
fn, err := s.dyn.Resource(fission.FunctionGVR).Namespace(namespace).Get(lookupCtx, functionName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
writeJSONError(w, http.StatusNotFound, fmt.Sprintf("function %q not found", functionName))
return
}
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("get function %q: %v", functionName, err))
return
}
invokeTimeout := s.resolveInvokeTimeout(fn)
ctx, cancel := context.WithTimeout(r.Context(), invokeTimeout)
defer cancel()
invokeURL := buildInternalInvokeURL(s.routerURL, namespace, functionName) + extraPath
if r.URL.RawQuery != "" {
invokeURL += "?" + r.URL.RawQuery
}
var invokeBody io.Reader
if shouldForwardRequestBody(r.Method) {
invokeBody = bytes.NewReader(bodyBytes)
}
req, err := http.NewRequestWithContext(ctx, r.Method, invokeURL, invokeBody)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("build invoke request: %v", err))
return
}
if shouldForwardRequestBody(r.Method) {
req.Header.Set("Content-Type", "application/json")
}
copyProxyRequestHeaders(req.Header, r.Header)
if token := s.getRouterToken(); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
start := time.Now()
resp, err := doRequestWithContextTimeout(s.http, req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q timeout after %s", functionName, invokeTimeout))
return
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q timeout after %s", functionName, invokeTimeout))
return
}
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("invoke %q: %v", functionName, err))
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
writeAnyJSON(w, http.StatusOK, map[string]any{
"status": resp.StatusCode,
"latency_ms": time.Since(start).Milliseconds(),
"invoke_url": invokeURL,
"response_raw": string(respBody),
})
}
// handleInvokeRoute даёт пользователю прямой HTTP gateway к своей функции по route.
// Внешний контракт: /fn/<route> + Authorization: Bearer <user-token>.
func (s *Server) handleInvokeRoute(w http.ResponseWriter, r *http.Request) {