Files
fission-console/doc/descriptions/archive-and-edit-modal-flow.md
T
“Naeel” d342b649a8 fix: restore NSReconciler, archive upload create/edit, fix version label both places; v1.3.45
- 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
2026-05-04 06:16:11 +04:00

9.7 KiB
Raw Blame History

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, ttl
  • archive — файл .zip

Алгоритм:

  1. r.ParseMultipartForm(32MB) — парсим форму
  2. Валидация name по regexp ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, max 57 символов
  3. Если задан language — через fission.EnsureEnvironment создаём/получаем Environment
  4. r.FormFile("archive")io.ReadAll(io.LimitReader(f, 32MB)) — читаем архив
  5. s.buildDeploySpec(ctx, archiveBytes) — загружаем в Fission storagesvc (S3), получаем {type: "url", url: "http://storagesvc/.../archive?id=..."} или fallback на literal
  6. Создаём Package с spec.deployment = deploySpec
  7. Создаём Function с аннотацией fission-console/source-type: archive
  8. Создаём HTTPTrigger
  9. При ошибке на шаге 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 — алгоритм:

  1. Парсим multipart form (32MB лимит)
  2. r.FormFile("archive") → читаем байты (лимит 32MB)
  3. Получаем текущую Function из K8s
  4. Запоминаем oldPkgName (для удаления после замены)
  5. Генерируем newPkgName = name + "-pkg-" + base36(unixMs) — уникальное имя
  6. Загружаем архив в storagesvc → создаём новый Package
  7. Обновляем Function: spec.package.packageref.name = newPkgName
  8. Обновляем аннотации: source-type: archive, updated-at: now
  9. Обновляем spec.functionTimeout если передан timeout
  10. Update Function в K8s
  11. Удаляем старый 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