- doc/progress.md: обновлена версия до operator v0.1.18 / provider v0.1.11, добавлен блок source_dir + fix destroy cleanup (2026-03-09), уточнены комментарии к контроллерам и trigger_resource - doc/errors/log.md: добавлены две записи — source_dir/hashicorp/archive, destroy route cleanup bug (три причины + решения) - .gitignore: добавлены examples/*/dist/ и terraform.tfstate.*.backup - examples/: удалены dist/ zip и tfstate.*.backup из трекинга
450 lines
27 KiB
Markdown
450 lines
27 KiB
Markdown
# Ошибки и решения
|
||
|
||
> Сюда записываем проблемы с которыми столкнулись и как их решили.
|
||
|
||
## Шаблон записи
|
||
|
||
```
|
||
## YYYY-MM-DD — Короткое описание проблемы
|
||
|
||
**Проблема:** ...
|
||
|
||
**Причина:** ...
|
||
|
||
**Решение:** ...
|
||
```
|
||
|
||
## 2026-03-07 — kaniko: unsupported protocol scheme ""
|
||
|
||
**Проблема:** kaniko Job падает с `unsupported protocol scheme ""` при доступе к S3.
|
||
|
||
**Причина:** В env `S3_ENDPOINT` передавался хост без схемы (`s3.msk-1.ngcloud.ru`), kaniko ожидает полный URL.
|
||
|
||
**Решение:** В `builder.go` добавить `"https://" + b.s3Endpoint` при формировании env для kaniko. Также добавить `S3_FORCE_PATH_STYLE=true` — Ceph требует path-style URLs.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Бесконечный цикл сборки (94 job'а)
|
||
|
||
**Проблема:** После upload оператор создавал сотни kaniko Job'ов.
|
||
|
||
**Причина:** Upload handler вызывал `Status().Update(phase=Pending)` ПОСЛЕ того как контроллер уже выставил `Building`. Контроллер видел `Pending` → создавал новый Job, upload снова сбрасывал → цикл.
|
||
|
||
**Решение:**
|
||
1. `startBuild()` СНАЧАЛА ставит аннотацию `last-built-s3key = spec.S3Key` (idempotency guard), затем обновляет статус.
|
||
2. Reconcile запускает сборку только если `spec.S3Key != annotations["last-built-s3key"]`.
|
||
3. Upload handler убран `Status().Update()` — контроллер сам управляет фазой.
|
||
4. `IsAlreadyExists` при создании Job не считается ошибкой (parallel reconcile).
|
||
|
||
---
|
||
|
||
## 2026-03-07 — upload.go: "object has been modified; please apply your changes"
|
||
|
||
**Проблема:** `terraform apply` падал при попытке upload кода функции:
|
||
```
|
||
status 500: {"error":"update function: Operation cannot be fulfilled on functions.sless.kube5s.ru "pg-query": the object has been modified; please apply your changes to the latest version and try again"}
|
||
```
|
||
|
||
**Причина:** В `upload.go` использовался `h.K8s.Update(ctx, fn)`. Между `Get()` и `Update()` контроллер успевал изменить объект (выставить статус/аннотации) — `resourceVersion` в памяти устаревал, API-сервер отклонял обновление.
|
||
|
||
**Решение:** Заменить `Update` на `Patch` (strategic merge patch):
|
||
```go
|
||
patch := client.MergeFrom(fn.DeepCopy())
|
||
fn.Spec.S3Key = s3Key
|
||
fn.Spec.S3Bucket = h.S3.Bucket()
|
||
h.K8s.Patch(ctx, fn, patch)
|
||
```
|
||
`MergePatch` передаёт только изменённые поля и не требует точного `resourceVersion`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Terraform Provider: "provider produced inconsistent result" для schedule
|
||
|
||
**Проблема:** При создании `sless_trigger` с `type=http` (без поля `schedule`) terraform падал:
|
||
```
|
||
.schedule: was null, but now cty.StringVal("")
|
||
```
|
||
|
||
**Причина:** В `trToModel()` провайдера `Schedule` всегда возвращался как `types.StringValue(tr.Schedule)`. Если schedule не задан, API возвращает пустую строку `""`, провайдер сохранял `""` в state. Terraform видел расхождение: в плане было `null` (поле не задано в HCL), в state после apply стало `""`.
|
||
|
||
**Решение:** В `trToModel()` возвращать `types.StringNull()` когда schedule пустая строка:
|
||
```go
|
||
schedule := types.StringNull()
|
||
if tr.Schedule != "" {
|
||
schedule = types.StringValue(tr.Schedule)
|
||
}
|
||
```
|
||
Правило: для `Optional` computed-полей пустая строка из API → `null` в state.
|
||
|
||
---
|
||
|
||
## 2026-03-09 — source_dir: hashicorp/archive недоступен без VPN
|
||
|
||
**Проблема:** С VPN недоступен `kube5s.ru` (registry провайдера), без VPN — `registry.terraform.io` (hashicorp/archive). Примеры не работали ни с VPN ни без.
|
||
|
||
**Причина:** Зависимость от внешнего провайдера `hashicorp/archive` для создания zip.
|
||
|
||
**Решение:** Добавить атрибут `source_dir` в `sless_function`. Провайдер сам упаковывает директорию в zip (`archive/zip` in-memory), считает SHA256 (`crypto/sha256`). Файлы сортируются для детерминированного хэша. `hashicorp/archive` убран из всех примеров.
|
||
|
||
**Provider:** v0.1.10.
|
||
|
||
---
|
||
|
||
## 2026-03-09 — Destroy route cleanup bug: endpoint 502 после terraform destroy
|
||
|
||
**Проблема:** После `terraform destroy` публичный HTTP-эндпоинт продолжал отвечать — сначала 200, затем 502 `function unreachable`. Тестовый скрипт ждал 120 секунд и падал.
|
||
|
||
**Причина:** Три независимые проблемы в связке:
|
||
1. `trigger_controller.go` `handleTriggerDeletion` намеренно не удалял Service и Ingress (комментарий «оставляем — могут быть нужны другим триггерам»). Неверно: имена Service/Ingress совпадают с именем функции, а не триггера.
|
||
2. `function_controller.go` `handleDeletion` удалял только Deployment, не трогал Service и Ingress.
|
||
3. `invoke.go` при DNS NXDOMAIN (`no such host`) возвращал 502, а тестовый скрипт ждёт 404. Итог: Ingress висел вечно → 502 висел 120с.
|
||
|
||
**Решение:**
|
||
1. `handleTriggerDeletion`: для HTTP-триггеров явно удалять `Service` и `Ingress` с именем `fn.Spec.FunctionRef` в `sless-fn-{ns}`.
|
||
2. `handleDeletion`: добавить удаление `Service` и `Ingress` с именем `fn.Name` в `sless-fn-{ns}` (импорт `netv1`).
|
||
3. `trigger_resource.go` Delete: polling `GetTrigger` каждые 3с до 90с — провайдер не возвращает успех раньше завершения cleanup.
|
||
4. `invoke.go`: `no such host` → HTTP 404 вместо 502 — Service удалён, эндпоинт мёртв.
|
||
|
||
**Operator:** v0.1.17 (контроллеры), v0.1.18 (invoke.go). **Provider:** v0.1.11.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Terraform Provider: "Resource Import Not Implemented"
|
||
|
||
**Проблема:** После failed `terraform apply` (Function создалась, Trigger упал) state не содержал созданных ресурсов. Повторный apply падал с `status 409: function already exists`. `terraform import` тоже не работал:
|
||
```
|
||
This resource does not support import. Please contact the provider developer
|
||
```
|
||
|
||
**Причина:** В провайдере не реализован `ImportState` для ресурса `sless_function`.
|
||
|
||
**Решение (краткосрочное):** Удалить ресурсы из кластера вручную (`kubectl delete`) и повторить apply с чистым state.
|
||
|
||
**Решение (долгосрочное / TODO):** Реализовать `ImportState` для `sless_function` и `sless_trigger`:
|
||
```go
|
||
func (r *FunctionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||
// ID формат: "namespace/name"
|
||
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-03-07 — handler.py: column "started_at" does not exist
|
||
|
||
**Проблема:** При вызове функции pg-query в логах пода:
|
||
```
|
||
psycopg2.errors.UndefinedColumn: column "started_at" does not exist
|
||
```
|
||
|
||
**Причина:** В `examples/pg-query/handler.py` использовалось имя колонки `started_at`, но в схеме `migrations/001_initial.sql` таблицы `invocations` колонка называется `created_at`.
|
||
|
||
**Решение:** Исправить `select` и `dict()` в handler.py: `started_at` → `created_at`.
|
||
|
||
**Урок:** При написании примеров всегда сверяться со схемой в `migrations/`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Deployment не перезапустил pod после re-build образа
|
||
|
||
**Проблема:** После `terraform apply` (re-upload + kaniko пересборка) pod продолжал использовать старый код — `curl` возвращал ошибку `started_at`.
|
||
|
||
**Причина:** Образ тегируется как `:latest`. Kubernetes не перезапускает pod автоматически если тег не изменился — даже если образ на DockerHub обновился. `imagePullPolicy` по умолчанию `IfNotPresent` для non-digest образов, только `Always` гарантирует pull при каждом запуске.
|
||
|
||
**Решение (краткосрочное):** Явный `kubectl rollout restart deployment/pg-query -n sless-fn-default`.
|
||
|
||
**Решение (долгосрочное / TODO):** В `function_controller.go` после успешной сборки добавить `rollout restart` аннотацию:
|
||
```go
|
||
// Форсируем rollout через аннотацию kubectl.kubernetes.io/restartedAt
|
||
deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
|
||
```
|
||
Либо использовать версионированные теги образов вместо `:latest` (`:v{timestamp}`).
|
||
|
||
---
|
||
|
||
## 2026-03-08 — output_md5 archive провайдера не обновляется при изменении кода
|
||
|
||
**Проблема:** `terraform plan` показывал "No changes" даже после изменения JS-файла функции. Функция отдавала старый код.
|
||
|
||
**Причина:** `code_hash = data.archive_file.handler_http.output_md5` — атрибут `output_md5` в `hashicorp/archive v2.7.x` возвращает MD5 от **предыдущей** версии zip (баг порядка вычисления). `output_sha` и `output_sha256` от того же файла обновлялись корректно.
|
||
|
||
**Решение:** Заменить `output_md5` на `filesha256("${path.module}/code/handler.js")` — хэшируется исходный файл напрямую, минуя archive провайдер.
|
||
|
||
**Урок:** Не использовать `output_md5` из `archive_file`. Использовать `filesha256(source_file)`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — FunctionJob зависал в Running навсегда
|
||
|
||
**Проблема:** `terraform apply` висел на `sless_job.hello_run: Still creating...` бесконечно. k8s Job давно завершился (Complete), но FunctionJob оставался в `Running`.
|
||
|
||
**Причина:** `SetupWithManager` использовал `Owns(&batchv1.Job{})` для watch событий Job. Но Job создавался в namespace `sless-fn-default`, а FunctionJob в `default` — cross-namespace OwnerReference не работают в k8s. Reconcile после завершения Job никогда не вызывался.
|
||
|
||
**Решение:** Убрать `Owns(&batchv1.Job{})`. В `syncJobStatus` добавить `return ctrl.Result{RequeueAfter: 5 * time.Second}` когда Job ещё выполняется — polling каждые 5 сек.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — ImagePullBackOff: sless-registry-auth не копировался в sless-fn-*
|
||
|
||
**Проблема:** Pod функции падал с `ImagePullBackOff` — не мог скачать образ из registry.
|
||
|
||
**Причина:** `buildDeployment` в `function_controller.go` не устанавливал `imagePullSecrets` в PodSpec. Секрет `sless-registry-auth` существовал только в namespace `sless`, но не копировался в `sless-fn-default`.
|
||
|
||
**Решение:** Добавить `ensureRegistrySecret()` — копирует секрет из `sless` в `sless-fn-*` при каждом reconcile. В `buildDeployment` добавить `ImagePullSecrets`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — CrashLoopBackOff: Cannot find module handler.js
|
||
|
||
**Проблема:** Pod функции падал: `Cannot find module '/app/function/handler.js'`. Entrypoint был `handler-http.handle` (файл `handler-http.js`), а server.js искал хардкоженный `handler.js`.
|
||
|
||
**Причина:** `server.js` и `server.py` runtime-образов хардкодили имя модуля `handler`. Поле `SLESS_ENTRYPOINT` env var не использовалось.
|
||
|
||
**Решение:** `server.js` и `server.py` переписаны: парсят `SLESS_ENTRYPOINT` (формат `"module.funcName"`), динамически загружают нужный модуль. Runtime пересобран как `v0.1.1`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — Kaniko cache: пересобирал образ со старым server.js
|
||
|
||
**Проблема:** После исправления `server.js` kaniko продолжал собирать образы с **старым** server.js из кэша.
|
||
|
||
**Причина:** Base image тегирован как `:latest`. Kaniko кэшировал слои — `FROM naeel/sless-runtime-nodejs20:latest` не перечитывал обновлённый образ.
|
||
|
||
**Решение:** Новый тег базового образа `v0.1.1`. В `upload.go` изменить `runtimeBaseImage("nodejs20")` → `naeel/sless-runtime-nodejs20:v0.1.1`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — RBAC: secrets is forbidden: cannot list
|
||
|
||
**Проблема:** Оператор падал: `secrets is forbidden: User "system:serviceaccount:sless:sless-operator" cannot list resource "secrets"`.
|
||
|
||
**Причина:** В `rbac.yaml` для secrets были только `get;create`, нужны также `list;watch` для controller-runtime.
|
||
|
||
**Решение:** Добавить `list;watch` в ClusterRole для `resources: ["secrets"]`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — Harbor registry нестабилен (504 Gateway Timeout)
|
||
|
||
**Проблема:** Kaniko падал: `GET https://pearlharbor.registryk8s.services.ngcloud.ru/v2/: unexpected status code 504 Gateway Timeout`. Периодически `/v2/` зависал на 10+ секунд или не отвечал вовсе.
|
||
|
||
**Причина:** Harbor — внешний сервис облачного провайдера, нестабилен по независящим от нас причинам. Поведение не зависит от VPN.
|
||
|
||
**Решение:** Переключить `REGISTRY_HOST` с Harbor на DockerHub (`naeel`). Образы функций теперь пушатся как `naeel/sless-default-{namespace}-{name}:latest`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — Provider: inconsistent result — image_ref изменился после apply
|
||
|
||
**Проблема:** После `terraform apply` ошибка:
|
||
```
|
||
Provider produced inconsistent result after apply
|
||
.image_ref: was cty.StringVal("pearlharbor..."), but now cty.StringVal("naeel/...")
|
||
```
|
||
|
||
**Причина:** `image_ref` в схеме провайдера имел `UseStateForUnknown()`. При plan terraform брал значение из state (старый Harbor путь). После apply API возвращал новый DockerHub путь. Расхождение план vs результат.
|
||
|
||
**Решение:** Убрать `UseStateForUnknown()` с `image_ref`. Теперь `image_ref` = `(known after apply)` при каждом apply — честно отражает что значение неизвестно до сборки. Провайдер `v0.1.5`.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — Provider: inconsistent result — phase Failed→Ready
|
||
|
||
**Проблема:**
|
||
```
|
||
Provider produced inconsistent result after apply
|
||
.phase: was cty.StringVal("Failed"), but now cty.StringVal("Ready")
|
||
```
|
||
|
||
**Причина:** При ошибке `WaitReady` (build failed) → `AddError` → `return`. `resp.State.Set` не вызывался → state не обновлялся → в state оставалось старое значение. Затем оператор замечал новый код, пересобирал → функция становилась Ready. Следующий `Read` возвращал `phase=Ready`, terraform видел расхождение.
|
||
|
||
**Решение:** Убрать `UseStateForUnknown()` с `image_ref` (связанный фикс). Для `phase` — `UseStateForUnknown()` оставлен, т.к. при нормальном flow `Ready→Ready` предсказуем.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — Стейл-сервис hello-node блокировал proxy запросы
|
||
|
||
**Проблема:** `GET /fn/default/hello-node` возвращал `operation not permitted` — `dial tcp 10.100.77.255:8080: connect: operation not permitted`.
|
||
|
||
**Причина:** Сервис `hello-node` в `sless-fn-default` существовал 26h с ClusterIP `10.100.77.255` но без Deployment/Pod. iptables дропал пакеты на несуществующий endpoint.
|
||
|
||
**Решение:** `kubectl delete svc hello-node -n sless-fn-default`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Docker credStore: exec "docker-credential-desktop.exe"
|
||
|
||
**Проблема:** `docker push` падал с `exec: "docker-credential-desktop.exe": executable file not found`.
|
||
|
||
**Причина:** В `~/.docker/config.json` осталась запись `"credsStore": "desktop.exe"` от Docker Desktop.
|
||
|
||
**Решение:** Удалить поле `credsStore` из `~/.docker/config.json`, оставить только `auths` с base64 credentials.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — controller-gen v0.11.1 паникует с Go 1.23
|
||
|
||
**Проблема:** `operator-sdk create api` падает с panic в `controller-gen` при `make generate`.
|
||
|
||
**Причина:** controller-tools v0.11.1 несовместим с Go 1.23 (nil pointer dereference в go/types).
|
||
|
||
**Решение:** В `Makefile` заменить `CONTROLLER_TOOLS_VERSION ?= v0.11.1` на `v0.14.0`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Dockerfile: operator не находит migrations/ при старте
|
||
|
||
**Проблема:** Оператор в кластере сразу падает в CrashLoopBackOff:
|
||
```
|
||
level=ERROR msg="read migration file" err="open migrations/001_initial.sql: no such file or directory"
|
||
```
|
||
**Причина:** Оригинальный `Dockerfile` копировал только `api/`, `controllers/`, `main.go`. Директории `internal/` и `migrations/` в образ не попадали.
|
||
**Решение:** Добавить в оба stage Dockerfile:
|
||
```dockerfile
|
||
COPY internal/ internal/ # в builder stage
|
||
COPY migrations/ migrations/ # и в builder, и в финальный alpine stage
|
||
```
|
||
`migrations/` нужен в финальном образе — оператор читает SQL-файлы в runtime при каждом старте.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — Dockerfile: golang:1.23 несовместим с go.mod (требует 1.25)
|
||
|
||
**Проблема:** `docker build` падает на `go mod download`:
|
||
```
|
||
go: go.mod requires go >= 1.25 (running go 1.23.12; GOTOOLCHAIN=local)
|
||
```
|
||
**Причина:** В `go.mod` указан `go 1.25`, Dockerfile использовал `golang:1.23-alpine`.
|
||
**Решение:** `FROM golang:1.23-alpine` → `FROM golang:1.25-alpine`.
|
||
|
||
---
|
||
|
||
## 2026-03-07 — go.mod: прямые зависимости помечены как indirect
|
||
|
||
**Проблема:** gopls показывал ошибки в `go.mod`:
|
||
```
|
||
github.com/gorilla/mux should be direct
|
||
github.com/lib/pq should be direct
|
||
github.com/minio/minio-go/v7 should be direct
|
||
k8s.io/api should be direct
|
||
```
|
||
**Причина:** Пакеты получили `// indirect` несмотря на прямые импорты — побочный эффект scaffold-генерации operator-sdk.
|
||
**Решение:** `go mod tidy` автоматически расставил правильные аннотации.
|
||
|
||
---
|
||
|
||
## 2026-03-08 — После kaniko rebuild функция возвращает старый код
|
||
|
||
**Проблема:** `terraform apply` с изменённым кодом успешно завершал kaniko build, но функция продолжала возвращать старую версию кода.
|
||
|
||
**Причина:** `imagePullPolicy: IfNotPresent` (default в k8s) + `:latest` тег — после успешной сборки оператор обновлял `.spec.template.spec.containers[0].image`, но image tag не менялся (всегда `:latest`). Kubernetes видел что образ уже есть на ноде и не пул-ил новый. Pod оставался запущенным со старым образом.
|
||
|
||
**Решение:** В `controllers/function_controller.go`, функция `ensureDeployment`, после обновления image добавлена аннотация:
|
||
```go
|
||
existing.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = fn.Status.LastBuiltAt.Time.Format(time.RFC3339)
|
||
```
|
||
Аннотация меняется при каждой новой сборке (значение = `lastBuiltAt`) → Kubernetes делает rolling restart → kubelet пул-ит свежий образ.
|
||
Оператор: **v0.1.11**.
|
||
|
||
---
|
||
|
||
## 2026-03-09 — Негативные тесты API: найденные баги валидации
|
||
|
||
### Методология тестирования
|
||
Тесты запускались через прямые POST-запросы к API (`https://sless-api.kube5s.ru`)
|
||
со специально некорректными параметрами. Скрипт: `/tmp/sless_negative_tests.sh`.
|
||
|
||
### Результаты
|
||
|
||
| # | Тест | Ожидание | Факт | Статус |
|
||
|---|---|---|---|---|
|
||
| T1 | `runtime="python999"` | 400 ошибка | `Unsupported value: "python999"` | ✅ |
|
||
| T2 | `memory_mb=-10` | 400 ошибка | **201 Created** — принято без ошибки | ❌ БАГ |
|
||
| T3 | `name=""` | 400 ошибка | `name and runtime are required` | ✅ |
|
||
| T4 | `entrypoint=""` (не передан) | 400 ошибка | **201 Created** с `entrypoint:""` | ❌ БАГ |
|
||
| T5 | trigger → несуществующая fn | 404/400 | `name, type and function are required` | ❌ Неверный текст ошибки |
|
||
| T6 | job → несуществующая fn | 404/400 | **Создался** (fn не проверяется при создании job) | ⚠️ |
|
||
| T7 | trigger `type="rabbitmq"` | 400 ошибка | `name, type and function are required` | ❌ Неверный текст ошибки |
|
||
| T8 | `run_id=0` | job создаётся, не запускается | 201, `run_id:0` | ✅ |
|
||
| T9 | несуществующий namespace | 404 | `namespaces "nonexistent-ns" not found` | ✅ |
|
||
| T10 | `memory_mb=999999` | 400 или ограничение | **201, вернул memory_mb=128** (молча обрезан) | ❌ БАГ |
|
||
|
||
### Баги для исправления
|
||
|
||
**БАГ-1 (API handler/function):** `memory_mb <= 0` не валидируется. Нужно: `if req.MemoryMB <= 0 { return 400 }`.
|
||
|
||
**БАГ-2 (API handler/function):** `entrypoint == ""` не валидируется. Нужно: проверять непустой entrypoint.
|
||
|
||
**БАГ-3 (API handler/trigger):** При передаче `type="rabbitmq"` или несуществующей `functionRef` ошибка говорит `"name, type and function are required"` — неверный текст. Скорее всего handler проверяет поле `function_ref` (snake_case) а принимает `functionRef` (camelCase) — маппинг не работает.
|
||
|
||
**БАГ-4 (API handler/function):** `memory_mb=999999` принято, но возвращён `memory_mb=128` — молчаливое изменение без ошибки.
|
||
|
||
### Дополнительно: валидация в Terraform провайдере (plan-time)
|
||
Провайдер не имел schema-level валидаторов — все ошибки проявлялись только на `apply`.
|
||
Добавлены validators в `function_resource.go`, `trigger_resource.go`, `job_resource.go`:
|
||
- `runtime` — только допустимые значения
|
||
- `memory_mb` — диапазон 64-4096
|
||
- `timeout_sec` — диапазон 1-900
|
||
- `trigger.type` — только `http`/`cron`
|
||
- `run_id` — >= 0
|
||
|
||
|
||
---
|
||
|
||
## Исправления (2026-03-08): v0.1.13 оператор + v0.1.7 провайдер
|
||
|
||
### API исправления (internal/api/handler/)
|
||
|
||
**БАГ-1 FIXED (functions.go):** Добавлена валидация `memory_mb`:
|
||
```
|
||
if req.MemoryMB <= 0 || req.MemoryMB > 4096 → 400 "memory_mb must be between 1 and 4096"
|
||
```
|
||
|
||
**БАГ-2 FIXED (functions.go):** Добавлена валидация `entrypoint`:
|
||
```
|
||
if req.Entrypoint == "" → 400 "entrypoint is required"
|
||
```
|
||
|
||
**БАГ-3 FIXED (triggers.go):** Добавлена валидация `type`:
|
||
```
|
||
if req.Type != "http" && req.Type != "cron" → 400 "type must be \"http\" or \"cron\""
|
||
```
|
||
|
||
**БАГ-4 FIXED (functions.go):** Покрыт случаем T10 — `memory_mb=999999` теперь возвращает 400.
|
||
|
||
### Провайдер план-валидаторы (terraform/provider/)
|
||
|
||
Добавлен пакет `terraform-plugin-framework-validators v0.19.0`.
|
||
|
||
`function_resource.go`:
|
||
- `runtime` — `stringvalidator.OneOf("nodejs20", "python3.11", "go1.21")`
|
||
- `memory_mb` — `int64validator.Between(1, 4096)`
|
||
- `timeout_sec` — `int64validator.Between(1, 900)`
|
||
|
||
`trigger_resource.go`:
|
||
- `type` — `stringvalidator.OneOf("http", "cron")`
|
||
|
||
`job_resource.go`:
|
||
- `run_id` — `int64validator.AtLeast(0)`
|
||
|
||
### Подтверждение повторными тестами (API)
|
||
|
||
| Тест | Ожидание | Результат |
|
||
|------|----------|----------|
|
||
| T2: `memory_mb=-10` | 400 | ✅ `{"error":"memory_mb must be between 1 and 4096"}` |
|
||
| T4: `entrypoint=""` | 400 | ✅ `{"error":"entrypoint is required"}` |
|
||
| T7: `type="rabbitmq"` | 400 + правильное сообщение | ✅ `{"error":"type must be \"http\" or \"cron\""}` |
|
||
| T10: `memory_mb=999999` | 400 | ✅ `{"error":"memory_mb must be between 1 and 4096"}` |
|
||
|
||
### Подтверждение план-валидаторов (Terraform)
|
||
|
||
Тест: `runtime = "python2.7"` в http.tf →
|
||
```
|
||
Error: Invalid Attribute Value Match
|
||
Attribute runtime value must be one of: ["nodejs20" "python3.11" "go1.21"], got: "python2.7"
|
||
```
|
||
|
||
### Версии
|
||
- Оператор: `naeel/sless-operator:v0.1.13`
|
||
- Провайдер: `terra.k8c.ru/naeel/sless v0.1.7`
|