fix: nodejs ESM wrapper via new Function, README for fission-console

- Replace two-file zip (_userfn.cjs + main.js) with single-file approach
- User code embedded via new Function(module, exports, code) for safe CJS isolation
- Auto-adds status:200 when function omits it
- Add fission-console technical README section (v0.6.7)
This commit is contained in:
Naeel
2026-04-19 16:52:22 +03:00
parent 551a5b2f8e
commit 1c533fc577
2 changed files with 269 additions and 3 deletions
+76 -3
View File
@@ -311,7 +311,7 @@ func (s *server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
return
}
if req.Entrypoint == "" {
req.Entrypoint = "main.main"
req.Entrypoint = defaultEntrypoint(req.Language)
}
if req.Route == "" {
req.Route = "/" + req.Name
@@ -358,7 +358,18 @@ func (s *server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
"buildcommand": "build",
}
} else {
literal := base64.StdEncoding.EncodeToString([]byte(req.Code))
var deployBytes []byte
if req.Language == "nodejs" {
zipBytes, zipErr := buildJSDeployZip(req.Code)
if zipErr != nil {
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("build nodejs archive: %v", zipErr))
return
}
deployBytes = zipBytes
} else {
deployBytes = []byte(req.Code)
}
literal := base64.StdEncoding.EncodeToString(deployBytes)
pkgSpec = map[string]any{
"deployment": map[string]any{
"type": "literal",
@@ -390,6 +401,7 @@ func (s *server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
// Парсим TTL — если указан, записываем аннотацию на Function CRD.
// reaper периодически читает эту аннотацию и удаляет протухшие функции + чистит environment если он больше не используется.
fnAnnotations := map[string]any{}
fnAnnotations["fission-console/language"] = req.Language
if req.TTL != "" {
expiresAt, ttlErr := parseTTL(req.TTL)
if ttlErr != nil {
@@ -669,6 +681,55 @@ func (s *server) buildGoSourceZip(code string) ([]byte, error) {
return buf.Bytes(), nil
}
func buildJSDeployZip(code string) ([]byte, error) {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
// main.js — единственный файл: ESM wrapper + код пользователя инлайн через new Function
// new Function безопасно изолирует module/exports от глобального контекста
codeJSON, err := json.Marshal(code)
if err != nil {
return nil, fmt.Errorf("marshal user code: %w", err)
}
wrapper := fmt.Sprintf(`const __mod = { exports: {} };
(new Function('module', 'exports', %s))(__mod, __mod.exports);
const _fn = __mod.exports;
export default async function(ctx) {
const fn = typeof _fn === 'function' ? _fn : (_fn.default || _fn.handler || _fn.main);
if (!fn) throw new Error('no exported function found in user code');
const result = await fn(ctx);
if (!result) return { status: 200, body: '' };
if (typeof result.status !== 'undefined') return result;
return { status: 200, ...result };
}
`, string(codeJSON))
fw, err := zw.Create("main.js")
if err != nil {
return nil, err
}
if _, err := fw.Write([]byte(wrapper)); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func defaultEntrypoint(lang string) string {
switch lang {
case "nodejs", "php", "ruby", "perl":
return "main"
case "go":
return "Handler"
default:
return "main.main"
}
}
func (s *server) handleGetFunction(w http.ResponseWriter, r *http.Request, name string) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -764,7 +825,19 @@ func (s *server) handleUpdateFunctionCode(w http.ResponseWriter, r *http.Request
return
}
literal := base64.StdEncoding.EncodeToString([]byte(req.Code))
lang, _, _ := unstructured.NestedString(fn.Object, "metadata", "annotations", "fission-console/language")
var deployBytes []byte
if lang == "nodejs" {
zipBytes, zipErr := buildJSDeployZip(req.Code)
if zipErr != nil {
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("build nodejs archive: %v", zipErr))
return
}
deployBytes = zipBytes
} else {
deployBytes = []byte(req.Code)
}
literal := base64.StdEncoding.EncodeToString(deployBytes)
if err := unstructured.SetNestedField(pkg.Object, literal, "spec", "deployment", "literal"); err != nil {
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("set package literal: %v", err))
return