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:
+76
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user