# Аудит: Terraform Provider vs Fission Canonical CRD **Дата:** 2026-06-03 **Ветка:** `feat/provider-audit` **Предыдущая версия:** v0.2.4 (ветка `feat/console`) ## Методология Сравнение производилось по трём источникам: 1. **Наш код** — `/terraform/provider/internal/resources/*.go` и `/terraform/provider/internal/client/client.go` 2. **Fission CRD types.go** — `github.com/fission/fission/pkg/apis/core/v1/types.go` (канонические Go-структуры) 3. **Реальные CRD объекты в кластере** — `kubectl get` для environments/packages/functions/httptriggers (наши vs CLI-созданные) --- ## 1. ENVIRONMENT (fission_environment) ### 1.1 Что у нас ```go // environmentResourceModel ID, Name, Image, Version(default=3), PoolSize(default=3), Namespace, UID ``` `environmentToUnstructured` генерирует: ```json { "spec": { "version": 3, "runtime": { "image": "..." }, "poolsize": 3 } } ``` ### 1.2 Что делает Fission CLI (`fission env create`) Fission CLI (из `environment/create.go`) создает полный `EnvironmentSpec`: ```json { "spec": { "version": 3, "runtime": { "image": "ghcr.io/fission/python-env", "container": { "name": "env-name", "resources": {} }, "podspec": { "containers": [{"name": "env-name", "resources": {}}] } }, "builder": { "image": "ghcr.io/fission/go-builder", "command": "build", "container": { "name": "builder", "resources": {} }, "podspec": { "containers": [{"name": "builder", "resources": {}}] } }, "poolsize": 3, "resources": {}, "imagepullsecret": "", "keeparchive": false } } ``` ### 1.3 Реальное сравнение в кластере | Поле | Наш (tf-python-env) | CLI (python) | Вердикт | |------|---------------------|--------------|---------| | `spec.version` | 3 | 3 | ✅ OK | | `spec.runtime.image` | ✅ | ✅ | ✅ OK | | `spec.runtime.container` | ❌ отсутствует | `{name, resources}` | ⚠️ Fission заполняет defaults — не критично | | `spec.runtime.podspec` | ❌ отсутствует | `{containers}` | ⚠️ Fission заполняет defaults — не критично | | `spec.builder` | ❌ ОТСУТСТВУЕТ | `{image, command, container, podspec}` | 🔴 **КРИТИЧНО для Go** | | `spec.poolsize` | 3 | 3 | ✅ OK | | `spec.resources` | ❌ отсутствует | `{}` | ⚠️ Defaults — не критично | | `spec.imagepullsecret` | ❌ | `""` | ⚠️ Можно добавить позже | | `spec.keeparchive` | ❌ | `false` | ⚠️ Нужно для JVM — не критично сейчас | ### 1.4 Выводы по Environment **Критичный баг:** Невозможно создать environment с builder (нет полей `builder_image`, `builder_command`). Это блокирует Go, любой язык с build step. **Что добавить (приоритетно):** - `builder_image` (string, optional) → `spec.builder.image` - `builder_command` (string, optional) → `spec.builder.command` **Что можно добавить позже:** - `resources` (object) → `spec.resources` - `imagepullsecret` (string) → `spec.imagepullsecret` - `keeparchive` (bool) → `spec.keeparchive` - `runtime_container_name` — Fission автозаполняет, мы не ставим, k8s принимает без него **Что НЕ нужно (Fission автозаполняет):** - `spec.runtime.container`, `spec.runtime.podspec` — заливаются defaults на стороне сервера - `spec.builder.container`, `spec.builder.podspec` — аналогично --- ## 2. PACKAGE (fission_package) ### 2.1 Что у нас ```go // packageResourceModel ID, Name, Environment, SourceDir, CodePath, CodeHash, BuildCmd, Namespace, UID, BuildStatus, BuildLog ``` `packageToUnstructured` генерирует: ```json { "spec": { "deployment": { "type": "literal", "literal": "base64..." }, "environment": { "name": "...", "namespace": "..." }, "source": {} } } ``` ### 2.2 Что делает Fission CLI Для **deploy-only** (literal): ```json { "spec": { "deployment": { "type": "literal", "literal": "base64...", "checksum": {} }, "environment": { "name": "...", "namespace": "..." }, "source": { "checksum": {} } }, "status": { "buildstatus": "none" } } ``` Для **source-with-builder** (Go, Node с build): ```json { "spec": { "source": { "type": "literal", "literal": "base64-of-zip...", "checksum": {} }, "environment": { "name": "...", "namespace": "..." }, "buildcmd": "build" }, "status": { "buildstatus": "pending" } } ``` Для **large archives** (>256KB): - Загрузка через StorageSvc `/v1/archive` (multipart POST) - В CRD сохраняется `type: "url"`, `url: "http://storagesvc/v1/archive?id=..."` ### 2.3 Реальное сравнение в кластере | Поле | Наш (tf-hello-pkg) | CLI (hello-*) | Вердикт | |------|---------------------|---------------|---------| | `spec.deployment.type` | `"literal"` | `"literal"` | ✅ OK | | `spec.deployment.literal` | ✅ base64 | ✅ base64 | ✅ OK | | `spec.deployment.checksum` | ❌ отсутствует | `{}` | ⚠️ K8s принимает без, но лучше добавить | | `spec.environment` | ✅ | ✅ | ✅ OK | | `spec.source` | `{}` (пустая map) | `{"checksum":{}}` | 🟡 **БАГ**: мы ставим пустой source — не мешает, но мусор | | `spec.buildcmd` | ✅ (если задан) | ✅ | ✅ OK | | `status.buildstatus` | `"none"` (от k8s default) | `"none"` | ✅ OK (k8s сам ставит) | ### 2.4 Что ОТСУТСТВУЕТ для builder pipeline (Go) Для Go-функций нужен **source** package (не deployment): 1. Код упаковывается в zip 2. zip кодируется в base64 → `spec.source.literal` (если <256KB) 3. `spec.source.type` = `"literal"` 4. `spec.deployment` = пусто 5. `spec.buildcmd` = `"build"` (или пользовательская) 6. `status.buildstatus` = `"pending"` → builder собирает → `"succeeded"`/`"failed"` 7. После build: `spec.deployment` заполняется builder'ом (url на StorageSvc) ### 2.5 Выводы по Package **Баг (некритичный):** Мы ВСЕГДА ставим `"source": {}` — пустой объект. Fission ставит `"source": {"checksum": {}}`. Оба варианта работают, но чистый вариант — не ставить source вообще если нет source archive. **Что добавить (приоритетно):** - **Режим source archive** — для Go и языков с build step. Нужно: - Флаг/переключатель: deployment-only vs source-with-build - Упаковка source_dir в zip → base64 → `spec.source.literal` - Проверка размера <256KB (лимит ArchiveLiteralSizeLimit) - Очистка `spec.deployment` при source mode - `status.buildstatus` = `"pending"` на create **Что можно добавить позже:** - StorageSvc загрузка для >256KB архивов - `spec.source.checksum` - Поддержка `type: "url"` (для уже загруженных архивов) --- ## 3. FUNCTION (fission_function) ### 3.1 Что у нас ```go // functionResourceModel ID, Name, Environment, PackageName, Entrypoint, Namespace, UID ``` `functionToUnstructured` генерирует: ```json { "spec": { "environment": { "name": "...", "namespace": "..." }, "InvokeStrategy": { "ExecutionStrategy": { "ExecutorType": "poolmgr" }, "StrategyType": "execution" }, "package": { "packageref": { "name": "...", "namespace": "..." }, "functionName": "main.main" } } } ``` ### 3.2 Что делает Fission CLI (`fission fn create`) ```json { "spec": { "environment": { "name": "...", "namespace": "..." }, "InvokeStrategy": { "ExecutionStrategy": { "ExecutorType": "poolmgr", "MaxScale": 0, "MinScale": 0, "SpecializationTimeout": 120, "TargetCPUPercent": 0 }, "StrategyType": "execution" }, "package": { "packageref": { "name": "...", "namespace": "...", "resourceversion": "6000598" }, "functionName": "" }, "functionTimeout": 60, "idletimeout": 120, "concurrency": 500, "requestsPerPod": 1, "resources": {} } } ``` ### 3.3 Реальное сравнение в кластере | Поле | Наш (tf-hello-fn) | CLI (fn-js-acc) | Вердикт | |------|---------------------|-----------------|---------| | `spec.environment` | ✅ | ✅ | ✅ OK | | `spec.InvokeStrategy.ExecutionStrategy.ExecutorType` | `"poolmgr"` | `"poolmgr"` | ✅ OK | | `spec.InvokeStrategy.ExecutionStrategy.MaxScale` | ❌ отсутствует | `0` | ⚠️ Defaults работают, но лучше ставить | | `spec.InvokeStrategy.ExecutionStrategy.MinScale` | ❌ | `0` | ⚠️ | | `spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout` | ❌ | `120` | ⚠️ | | `spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent` | ❌ | `0` | ⚠️ Не критично | | `spec.InvokeStrategy.StrategyType` | `"execution"` | `"execution"` | ✅ OK | | `spec.package.packageref.resourceversion` | ❌ отсутствует | ✅ | 🟡 CLI ставит для оптимизации, мы — нет | | `spec.package.functionName` | ✅ `"main.main"` | `""` (или функция) | ✅ OK | | `spec.functionTimeout` | ❌ | `60` | 🟡 Полезно для управления таймаутами | | `spec.idletimeout` | ❌ | `120` | 🟡 Полезно для scale-to-zero | | `spec.concurrency` | ❌ | `500` | ⚠️ | | `spec.requestsPerPod` | ❌ | `1` | ⚠️ | | `spec.resources` | ❌ | `{}` | ⚠️ | ### 3.4 Выводы по Function **Критичных багов нет.** Наши функции работают, потому что k8s/Fission подставляет defaults. НО: **Что добавить (приоритетно):** - `executor_type` (string, optional, default="poolmgr") → для newdeploy/container strategies - `function_timeout` (int, optional) → `spec.functionTimeout` — важно для долгих функций - `idle_timeout` (int, optional) → `spec.idletimeout` — управление scale-to-zero - `min_scale` / `max_scale` (int, optional) → ExecutionStrategy — для newdeploy **Что можно добавить позже:** - `concurrency` (int) → `spec.concurrency` - `requests_per_pod` (int) → `spec.requestsPerPod` - `specialization_timeout` (int) → `spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout` - `resources` (object) → CPU/MEM limits - `secrets`, `configmaps` (list) → volume mounts --- ## 4. HTTP TRIGGER (fission_http_trigger) ### 4.1 Что у нас ```go // httpTriggerResourceModel ID, Name, Function, URL, Methods, CreateIngress, Host, Namespace, UID ``` `httpTriggerToUnstructured` генерирует: ```json { "spec": { "relativeurl": "/tf-hello", "methods": ["GET"], "functionref": { "type": "name", "name": "tf-hello-fn" }, "createingress": false } } ``` ### 4.2 Что делает Fission CLI ```json { "spec": { "relativeurl": "/hello", "methods": ["GET"], "functionref": { "type": "name", "name": "hello", "functionweights": null }, "createingress": false, "host": "", "ingressconfig": { "annotations": null, "host": "*", "path": "/hello", "tls": "" }, "method": "", "prefix": "" } } ``` ### 4.3 Реальное сравнение в кластере | Поле | Наш (tf-hello-route) | CLI (hello-route) | Вердикт | |------|----------------------|-------------------|---------| | `spec.relativeurl` | ✅ | ✅ | ✅ OK | | `spec.methods` | ✅ | ✅ | ✅ OK | | `spec.functionref.type` | `"name"` | `"name"` | ✅ OK | | `spec.functionref.name` | ✅ | ✅ | ✅ OK | | `spec.functionref.functionweights` | ❌ | `null` | ✅ Не нужно | | `spec.createingress` | ✅ | ✅ | ✅ OK | | `spec.host` | ❌ (если пусто) | `""` | ✅ Не критично | | `spec.ingressconfig` | Частично (host) | Полный | ⚠️ IngressConfig неполный | | `spec.method` | ❌ | `""` | ✅ Legacy, не нужно | | `spec.prefix` | ❌ | `""` | ⚠️ Для prefix routing — добавить | ### 4.4 Выводы по HTTPTrigger **Багов нет.** Работает корректно. Мелкие расхождения не влияют. **Что можно добавить позже:** - `prefix` (string) → `spec.prefix` — для prefix-based routing - `keep_prefix` (bool) → `spec.keepPrefix` - Полный `ingressconfig` (annotations, path, tls) — при `create_ingress=true` - `function_weights` (map) → для canary deployments --- ## 5. CLIENT (client.go) ### 5.1 Оценка **Код корректный.** Чистый CRUD через `dynamic.Interface`: - 4 GVR определения (environments, packages, functions, httptriggers) - CRUD для каждого: Create/Get/Update/Delete - `IsNotFound()` для обработки 404 - `New()` строит config из kubeconfig + context **Расхождений с Fission нет** — это наш собственный low-level клиент для работы с CRD. --- ## 6. VALIDATION (validation_helpers.go, entrypoint validation) ### 6.1 Оценка - `ensureEnvironmentExists` — ✅ корректно (проверяет наличие env перед созданием pkg/fn) - `ensurePackageExists` — ✅ корректно - `validateEntrypointAgainstPackageSource` — ⚠️ Проверяет только Python `def funcname(`. Не проверяет: - Node: `module.exports` или `export function` - Go: plugin symbol - PHP: `function handler(` - Ruby: `def handler` **Это допустимо** — избыточная валидация может мешать. Лучше валидировать только точно известные паттерны. --- ## 7. СВОДНАЯ ТАБЛИЦА ПРИОРИТЕТОВ ### 🔴 Критично (блокирует функционал) | # | Ресурс | Проблема | Решение | |---|--------|----------|---------| | 1 | Environment | Нет builder support | Добавить `builder_image`, `builder_command` | | 2 | Package | Нет source archive mode | Добавить zip-упаковку source_dir → `spec.source.literal` | ### 🟡 Важно (улучшает пользовательский опыт) | # | Ресурс | Проблема | Решение | |---|--------|----------|---------| | 3 | Function | Захардкожен poolmgr | Добавить `executor_type` с optional default | | 4 | Function | Нет пользовательских таймаутов | Добавить `function_timeout`, `idle_timeout` | | 5 | Function | Нет min/max scale | Добавить `min_scale`, `max_scale` | | 6 | Package | Пустой `source: {}` мусор | Убрать пустой source из payload | ### ⚪ Не критично (можно позже) | # | Ресурс | Проблема | |---|--------|----------| | 7 | Environment | Нет resources, imagepullsecret, keeparchive | | 8 | Function | Нет concurrency, requestsPerPod, resources, secrets, configmaps | | 9 | HTTPTrigger | Нет prefix, keepPrefix, полного ingressconfig | | 10 | Package | Нет StorageSvc загрузки (>256KB) | | 11 | Package | Нет checksum | --- ## 8. ПЛАН РЕАЛИЗАЦИИ (предлагаемый) ### Этап 1: Builder support (Environment + Package) **environment_resource.go:** - Добавить поля `builder_image` и `builder_command` в модель и schema - Добавить `spec.builder` в `environmentToUnstructured` (если builder_image задан) - Обновить `unstructuredToEnvironmentModel` для чтения builder полей **package_resource.go:** - Добавить поле `deploy_type` (string: `"literal"` или `"source"`, default `"literal"`) - При `deploy_type = "source"`: zip source_dir → base64 → `spec.source.literal`, `spec.deployment` пустой - Убрать пустой `"source": {}` при deploy_type = "literal" - Добавить base64 size check (<256KB) при literal mode ### Этап 2: Function tuning **function_resource.go:** - Добавить optional поля: `executor_type`, `function_timeout`, `idle_timeout`, `min_scale`, `max_scale` - Обновить `functionToUnstructured` для заполнения ExecutionStrategy полностью - Обновить `unstructuredToFunctionModel` для чтения новых полей ### Этап 3: Мелкие улучшения - HTTPTrigger: prefix, keepPrefix - Package: checksum - Environment: resources, imagepullsecret --- ## 9. ВЫВОД Наш провайдер **работает корректно для основного сценария**: Python/Node/PHP/Ruby/Perl literal deployment + poolmgr executor. Все критические поля (version, runtime.image, poolsize, deployment.literal, functionName, relativeurl, methods) генерируются правильно. **Главные пробелы:** 1. Нет builder support → Go и любые compiled languages не работают через builder pipeline 2. Нет source archive → только deployment-only (literal из одного файла) 3. Function executor hardcoded to poolmgr → нет newdeploy/container strategy 4. Нет пользовательских таймаутов Ни один из пробелов не является **ошибкой** в существующем коде — это **недостающий функционал**. То, что есть, соответствует канону Fission.