181 lines
7.5 KiB
Markdown
181 lines
7.5 KiB
Markdown
# Создание функции — текущий flow (май 2026)
|
||
|
||
## UI (браузер)
|
||
|
||
### Форма создания (`create-modal`)
|
||
|
||
Поля в `index.html`:
|
||
- `c-lang` — select (python / nodejs / go / ruby / php)
|
||
- `c-name` — имя функции
|
||
- `c-entry` — entrypoint (заполняется автоматически по языку из `LANG_TEMPLATES`)
|
||
- `c-route` — HTTP-путь
|
||
- `c-methods` — методы, через запятую
|
||
- `c-timeout` — таймаут в секундах
|
||
- `c-schedule-enabled` + `c-cron` — cron-расписание (опционально)
|
||
- `c-code` — **textarea** с исходным кодом функции (единственный способ передать код)
|
||
|
||
Кнопки рядом с `c-code`:
|
||
- `aiCheck(...)` — линтер (POST `/console/api/ai/check`)
|
||
- `aiExplain(...)` — объяснение через LLM (POST `/console/api/ai/ask`)
|
||
|
||
При нажатии «Создать» вызывается `submitCreate()` (`js/functions.js`):
|
||
|
||
```js
|
||
await requestJSON(API_BASE + '/functions', 'POST', {
|
||
name, language, entrypoint, route, methods, timeout,
|
||
code: document.getElementById('c-code').value // <-- всегда текст
|
||
});
|
||
```
|
||
|
||
### Форма редактирования (`edit-modal`)
|
||
|
||
Аналогична. `submitEdit()` шлёт:
|
||
|
||
```js
|
||
await requestJSON(API_BASE + '/functions/' + name + '/code', 'PUT', {
|
||
code: document.getElementById('e-code').value,
|
||
timeout: ...
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Backend
|
||
|
||
### POST /console/api/functions → `handleCreateFunction`
|
||
|
||
Структура запроса (`model.CreateFunctionRequest`):
|
||
```json
|
||
{ "name": "fn", "language": "python", "code": "def main(): ...",
|
||
"entrypoint": "main", "route": "/ns/fn", "methods": ["GET"],
|
||
"timeout": 60, "ttl": "" }
|
||
```
|
||
|
||
Обязательные: `name`, `language` (или `environment`), `code`.
|
||
|
||
**Путь для интерпретируемых языков** (python / nodejs / ruby / php):
|
||
```
|
||
code (строка)
|
||
→ buildDeployArchive(lang, code)
|
||
python/ruby/php: []byte(code) (raw, не zip)
|
||
nodejs: BuildJSDeployZip(code) → zip с package.json + main.js
|
||
→ buildDeploySpec(ctx, bytes)
|
||
→ uploadToStoragesvc() → multipart POST → storagesvc → archiveURL
|
||
→ fallback: base64 literal (если storagesvc недоступен)
|
||
→ Package.spec.deployment = { type: "url", url: "..." }
|
||
или = { type: "literal", literal: "base64..." }
|
||
```
|
||
|
||
**Путь для Go**:
|
||
```
|
||
code
|
||
→ BuildGoSourceZip(code) → zip с исходником
|
||
→ uploadToStoragesvc → archiveURL
|
||
→ Package.spec.source = { ... } (source package, компилируется builder job)
|
||
Package.spec.buildcommand = "build"
|
||
```
|
||
|
||
После создания Package → создаётся Function → HTTPTrigger.
|
||
При ошибке на любом шаге — откат (best-effort DELETE предыдущих объектов).
|
||
|
||
### PUT /console/api/functions/{name}/code → `handleUpdateFunctionCode`
|
||
|
||
```json
|
||
{ "code": "...", "timeout": 60 }
|
||
```
|
||
|
||
Обязателен: `code`.
|
||
|
||
Алгоритм:
|
||
1. Читает текущую Function → берёт `fission-console/language` из аннотации
|
||
2. `buildDeployArchive(lang, code)` → байты
|
||
3. Создаёт **новый Package** с именем `{name}-pkg-{timestamp_base36}`
|
||
4. Обновляет Function.spec.package.packageref на новый пакет
|
||
5. Удаляет старый Package (best-effort)
|
||
|
||
> Почему новый Package, а не обновление старого:
|
||
> executor кэширует function-service по `functionUid` — не видит изменений в том же Package.
|
||
> Новое имя гарантирует cache miss.
|
||
|
||
---
|
||
|
||
## Линтер (ai/check) и LLM (ai/ask)
|
||
|
||
### POST /console/api/ai/check
|
||
|
||
```json
|
||
{ "language": "python", "code": "def main(): ..." }
|
||
```
|
||
|
||
Работает **только со строкой кода**. Запускает:
|
||
- python: `python3 -m py_compile <tmpfile.py>`
|
||
- nodejs: `node --check <tmpfile.js>`
|
||
- ruby: `ruby -c <tmpfile.rb>`
|
||
- php: `php -l <tmpfile.php>`
|
||
- go: `go/parser.ParseFile(...)` прямо в процессе (без tmpfile)
|
||
|
||
Временный файл создаётся в `os.TempDir()`, удаляется через `defer`.
|
||
|
||
### POST /console/api/ai/ask
|
||
|
||
Отправляет код в LLM (объяснение, анализ). Детали в `ai_prompts.go`.
|
||
|
||
---
|
||
|
||
## Что сейчас НЕ поддерживается
|
||
|
||
- **Загрузка архива (.zip) из файла** — ни в UI, ни в API
|
||
- Архив можно только скачать (через `extractPackageSourceCode` → `fetchPackageArchive`)
|
||
|
||
---
|
||
|
||
## Planned: загрузка архива
|
||
|
||
### API
|
||
|
||
Добавить в `CreateFunctionRequest` и `UpdateCodeRequest` поле `archive_b64 string`.
|
||
Логика в handler: если `archive_b64 != ""` — пропустить `buildDeployArchive`,
|
||
сразу `uploadToStoragesvc(base64.Decode(archive_b64))`.
|
||
|
||
Валидация:
|
||
- magic bytes `PK\x03\x04` (zip)
|
||
- максимум 10MB (против 1MB для кода)
|
||
|
||
### UI
|
||
|
||
Раздельные режимы в форме — кнопки-переключатели «✏️ Код» / «📦 Архив»:
|
||
- режим «Код» — textarea (как сейчас)
|
||
- режим «Архив» — `<input type="file" accept=".zip">`, textarea скрыта
|
||
|
||
В `submitCreate` / `submitEdit` — `FileReader.readAsDataURL(file)` → base64 → поле `archive_b64`.
|
||
|
||
### Линтер и LLM для архива
|
||
|
||
Проблема: линтер (`ai/check`) и LLM (`ai/ask`) принимают `code` — строку.
|
||
Архив — zip из нескольких файлов. Варианты:
|
||
|
||
**Вариант A — на бэке, при загрузке**
|
||
После распаковки архива (уже есть `decodeArchiveBytesToSource` в `package.go`):
|
||
- Извлечь главный файл (entrypoint)
|
||
- Прогнать через линтер
|
||
- Результат вернуть в том же ответе на create/update (поле `lint_result`)
|
||
|
||
Плюсы: не меняет контракт линтера, нет доп. round-trip.
|
||
Минусы: линтится только один файл из архива.
|
||
|
||
**Вариант B — на фронте, до отправки**
|
||
`FileReader.readAsText(file)` — не работает для zip напрямую.
|
||
Можно использовать `JSZip` (npm/CDN) — распаковать в браузере → взять главный файл → передать в `ai/check`.
|
||
|
||
Плюсы: линтинг до загрузки, быстрая обратная связь.
|
||
Минусы: добавляется зависимость (JSZip ~100KB).
|
||
|
||
**Вариант C — гибрид (рекомендован)**
|
||
1. Фронт: при выборе архива — использовать JSZip, найти entrypoint-файл по языку,
|
||
показать его в readonly-textarea, кнопки линтера и LLM работают с этим текстом.
|
||
2. Отправка: `archive_b64` (весь zip) идёт на бэк как есть.
|
||
3. Бэк (опционально): после сохранения прогнать `decodeArchiveBytesToSource` → линтер,
|
||
вернуть предупреждение если нашёл ошибки.
|
||
|
||
Так пользователь видит и код и результат линтера до деплоя, и полный архив уходит без изменений.
|