- tenant.go: restore StartNSReconciler (sync FISSION_RESOURCE_NAMESPACES every 30s) - main.go: call nsm.StartNSReconciler on startup - handlers.go: handleCreateFunctionFromArchive (multipart), handleUpdateFunctionArchive (PUT /archive) source-type annotation, source_type in GET response - functions.js: submitCreate/submitEdit archive mode, openEdit uses source_type - index.html: version label updated in BOTH places (line 103 and 403) to v1.3.45 - console.yaml: image v1.3.45 - doc: archive-and-edit-modal-flow.md
9.7 KiB
Archive Upload и Edit Modal — описание логики (v1.3.45, 2026-05-04)
Контекст
До v1.3.45 функции создавались только через ввод кода (JSON поле code).
Edit modal при открытии не сбрасывал режим (code/archive) — показывал то что было
выбрано ранее. Создание и обновление через zip-архив не поддерживалось вовсе.
1. Аннотация fission-console/source-type
Добавлена новая аннотация на Kubernetes-объекте Function:
fission-console/source-type: "code" | "archive"
Ставится в момент создания функции и обновляется при каждом сохранении.
Хранится в metadata.annotations Function объекта в K8s — персистентно.
Зачем: позволяет при открытии edit modal знать каким способом была создана функция, без анализа содержимого Package (которое не даёт надёжного ответа).
2. Backend — создание функции
2a. Определение режима (handlers.go handleCreateFunction)
isArchiveUpload := strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data")
if isArchiveUpload {
s.handleCreateFunctionFromArchive(w, r, ns)
return
}
Если Content-Type начинается с multipart/form-data — передаём в новый хендлер.
Иначе — старый JSON-путь без изменений.
2b. Создание через JSON-код (без изменений, кроме аннотации)
В fnAnnotations добавлено:
fissionSourceTypeAnnotation: "code",
Все старые функции (созданные до v1.3.45) не имеют этой аннотации — API вернёт
source_type: "code" по умолчанию.
2c. Создание через архив (handleCreateFunctionFromArchive)
Вход: multipart/form-data с полями:
name,language,environment,entrypoint,route,methods,timeout,ttlarchive— файл.zip
Алгоритм:
r.ParseMultipartForm(32MB)— парсим форму- Валидация
nameпо regexp^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, max 57 символов - Если задан
language— черезfission.EnsureEnvironmentсоздаём/получаем Environment r.FormFile("archive")→io.ReadAll(io.LimitReader(f, 32MB))— читаем архивs.buildDeploySpec(ctx, archiveBytes)— загружаем в Fission storagesvc (S3), получаем{type: "url", url: "http://storagesvc/.../archive?id=..."}или fallback на literal- Создаём Package с
spec.deployment = deploySpec - Создаём Function с аннотацией
fission-console/source-type: archive - Создаём HTTPTrigger
- При ошибке на шаге 7 или 8 — откат (удаляем уже созданные объекты)
Ответ: 201 Created с JSON {name, namespace, environment, route, source_type: "archive"}
3. Backend — обновление функции через архив
Новый endpoint: PUT /console/api/functions/:name/archive
Зарегистрирован в handleFunctionsAction:
if len(parts) == 2 && parts[1] == "archive" && r.Method == http.MethodPut {
s.handleUpdateFunctionArchive(w, r, name)
return
}
handleUpdateFunctionArchive — алгоритм:
- Парсим multipart form (32MB лимит)
r.FormFile("archive")→ читаем байты (лимит 32MB)- Получаем текущую Function из K8s
- Запоминаем
oldPkgName(для удаления после замены) - Генерируем
newPkgName = name + "-pkg-" + base36(unixMs)— уникальное имя - Загружаем архив в storagesvc → создаём новый Package
- Обновляем Function:
spec.package.packageref.name = newPkgName - Обновляем аннотации:
source-type: archive,updated-at: now - Обновляем
spec.functionTimeoutесли переданtimeout - Update Function в K8s
- Удаляем старый Package (best-effort, ошибки игнорируем)
Почему создаём новый Package вместо обновления старого:
Executor кэширует function service по functionUid. Изменение содержимого
существующего Package не сбрасывает кэш. Новое имя пакета гарантирует cache miss.
(Та же логика что в handleUpdateFunctionCode.)
4. Backend — GET /functions/:name
Добавлено поле source_type в ответ:
sourceType := "code" // default для старых функций без аннотации
if ann := fn.GetAnnotations(); ann != nil {
if v := ann[fissionSourceTypeAnnotation]; v != "" {
sourceType = v
}
}
Поле возвращается в JSON ответе:
{
"name": "my-func",
"code": "...",
"source_type": "archive",
...
}
5. Frontend — submitCreate
До v1.3.45 всегда отправлял JSON с полем code.
Теперь определяет режим по видимости c-archive-area:
var archiveArea = document.getElementById('c-archive-area');
var isArchiveMode = archiveArea && archiveArea.style.display !== 'none';
var archiveFile = isArchiveMode ? document.getElementById('c-archive-file').files[0] : null;
Если archive mode и файл выбран → FormData:
var fd = new FormData();
fd.append('name', name);
fd.append('language', lang);
// ... остальные поля ...
fd.append('archive', archiveFile);
fetch(API_BASE + '/functions', { method: 'POST', headers: authHeaders(), body: fd });
Иначе → старый JSON путь без изменений.
6. Frontend — openEdit
До v1.3.45 сбрасывал режим на основе fn.code !== '', что было неправильно:
extractPackageSourceCode умеет доставать код даже из архивных функций через URL,
поэтому fn.code всегда был непустым.
Теперь использует fn.source_type:
if (fn.source_type === 'archive') {
setCodeMode('e', 'archive');
} else {
setCodeMode('e', 'code');
}
Поведение:
- Функция создана через код → открывается редактор кода с текущим кодом
- Функция создана из архива → открывается archive area с file input (пустым)
- Старые функции без аннотации →
source_type: "code"по умолчанию → code mode
7. Frontend — submitEdit
До v1.3.45 всегда отправлял PUT /functions/:name/code с JSON.
Теперь определяет режим аналогично submitCreate:
var archiveArea = document.getElementById('e-archive-area');
var isArchiveMode = archiveArea && archiveArea.style.display !== 'none';
var archiveFile = isArchiveMode ? document.getElementById('e-archive-file').files[0] : null;
Если archive mode и файл выбран → PUT /functions/:name/archive с FormData:
fd.append('timeout', String(parseTimeout(...)));
fd.append('archive', archiveFile);
fetch(...'/archive', { method: 'PUT', headers: authHeaders(), body: fd });
Иначе → старый PUT /functions/:name/code с JSON.
Важно: если пользователь открыл функцию в archive mode но не выбрал новый файл —
archiveFile будет null и код уйдёт по JSON-пути (отправит пустой код).
Это expected behaviour: пользователь должен явно выбрать файл для обновления архива.
8. Исправление версии в UI
В index.html номер версии присутствует в двух местах:
- Строка 103: верхний правый угол (
<div>в header) - Строка 403: блок "Помощь" (
<span>в help modal)
До v1.3.45 оба места обновлялись несинхронно — строка 103 отставала.
Начиная с v1.3.45 оба места обновляются одновременно через multi_replace_string_in_file.
9. Проблема с imagePullPolicy
При первом деплое v1.3.43 (после исправления версии) UI показывал v1.3.40.
Причина: imagePullPolicy: IfNotPresent — K8s имел образ v1.3.43 в кэше
и не тянул обновлённый образ с тем же тегом.
Решение: менять тег при каждой сборке (v1.3.43 → v1.3.44 → v1.3.45). Это принципиальное правило: никогда не пересобирать образ с тем же тегом.
10. Константы и аннотации (handlers.go)
const maxArchiveUploadSize = 32 << 20 // 32 MB
const fissionSourceTypeAnnotation = "fission-console/source-type"
Существующие константы не изменялись:
const maxCodeSize = 1 << 20 // 1 MB — только для inline кода
const defaultFunctionInvokeTimeout = 60 * time.Second