diff --git a/HISTORY/OPUS/2026-07-07_opus_answers_duplicate_fix.md b/HISTORY/OPUS/2026-07-07_opus_answers_duplicate_fix.md new file mode 100644 index 0000000..33e6d0e --- /dev/null +++ b/HISTORY/OPUS/2026-07-07_opus_answers_duplicate_fix.md @@ -0,0 +1,98 @@ +# 2026-07-07 — Opus answers: duplicate/exist fix strategy + +## Контекст +Промпт из `/home/naeel/tf_provider/prompt_for_opus_duplicate.md` +После mutex (v5.0.63) pg_user_5 упал: «Операция вернула duplicate/exist, но объект не найден в state_out» + +## Корень всех проблем + +Код путает два разных случая «не-found»: +- `known && !found` — список в state_out ЕСТЬ, объекта нет → реальная несогласованность +- `!known` — списка в state_out НЕТ вообще (PostgreSQL, Kafka) → проверить невозможно + +Сейчас `!known` трактуется как несогласованность → ошибка. + +--- + +## Вопрос 1 (ПРИОРИТЕТ) — ДА, доверять API при `!known` + +**Где:** templates.go → генерится во все subresource Create + +**Фикс:** усыновлять при `found || !known`, ошибку только для `known && !found`: +```go +if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(createErr) { + found, known := false, false + if targetValue != "" { + var checkErr error + found, known, checkErr = resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue) + if checkErr != nil { + resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error()) + return + } + } + // found → подтверждено в state_out + // !known → списка в state_out нет (PG/Kafka) → доверяем API + // known&&!found → реальная несогласованность + if found || !known { + plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams)) + resp.Diagnostics.AddWarning("Подресурс усыновлён", "API вернул already exists; объект принят в state: "+targetValue) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + return + } + resp.Diagnostics.AddError("Нарушена консистентность", "API вернул duplicate/exist, но объект подтверждённо отсутствует в state_out: "+targetValue) + return +} +``` + +--- + +## Вопрос 2 — ДА, нужен явный маппинг из метаданных + +**Где:** subresource_guard.go — `SubresourceListKey` (эвристика `name+"s"`) и `SubresourceIdentityKey` (special-map на 2 сервиса) + +**Доказательство:** Kafka — реальные ключи `kafkaUsers`/`kafkaTopics`, эвристика даёт `users`/`topics` → `known=false` даже когда данные есть. + +**Фикс:** генератор должен эмитить явные `listKey`/`idKey` как константы из метаданных. Для сервисов без ключа в state_out (PostgreSQL) — эмитить `listKey=""` → `FindSubresourceInStateOut` → `known=false` → работает путь «доверять API» из Вопроса 1. + +--- + +## Вопрос 3 — НЕТ, list-эндпоинтов не существует + +**Проверено по API-метаданным** (svc 90): +- Операции: create(19), delete(20), resume(114), suspend(115), recovery(47), modify(48), restart(54) +- Subresource: create_user(241), delete_user(244), create_database(245), delete_database(246) +- **Ни одной `list_*`/`get_*`/`show_*`** + +Перечислить подресурсы через API невозможно. Единственный источник — state_out, а у PG его нет. + +**Вывод:** стратегия «доверять API» (Вопрос 1) — единственно возможная. + +--- + +## Вопрос 4 — Частично; текст ОК, нужен HTTP 409 + +**Где:** subresource_guard.go — `IsSubresourceAlreadyExistsError` + +- `role "pg_user_5" already exists` → ловится ✅ +- `already exist` (без s), голый `exist` → НЕ ловится +- **Главная дыра:** HTTP 409 без текста → НЕ ловится + +**Фикс:** +```go +markers := []string{"уже существует", "already exists", "already exist", "duplicate", "conflict", " exist"} +``` +Плюс проверка HTTP 409 (требует проброса статус-кода из клиента). + +--- + +## Приоритет реализации + +1. **Вопрос 1** — чинит `pg_user_5` сразу +2. **Вопрос 2** — чинит Kafka и остальные +3. **Вопрос 4** — устойчивость к формулировкам +4. **Вопрос 3** — документировать как «невозможно, дизайн-решение» + +Всё правится в шаблоне templates.go и subresource_guard.go. Сгенерированные файлы НЕ править — перегенерировать. + +**Дата:** 2026-07-07 +**Статус:** анализ завершён, правки НЕ внесены diff --git a/HISTORY/OPUS/2026-07-07_skip_missing_on_delete_bugs.md b/HISTORY/OPUS/2026-07-07_skip_missing_on_delete_bugs.md new file mode 100644 index 0000000..fe2ffb3 --- /dev/null +++ b/HISTORY/OPUS/2026-07-07_skip_missing_on_delete_bugs.md @@ -0,0 +1,73 @@ +# 2026-07-07 — Opus bug analysis: skip_missing_on_delete и связанные баги + +## Prompt +Prompt из `/home/naeel/tf_provider/prompt_for_opus_bugs.md` — анализ 3 файлов на баги, аналогичные `skip_missing_on_delete`. + +## Анализированные файлы +- `/home/naeel/tf_provider/TOOLS/resource-generator/internal/templates/templates.go` +- `/home/naeel/tf_provider/provider/internal/resources_core/crud.go` +- `/home/naeel/tf_provider/generated/test/go/90_postgres_database_resource.go` +- `/home/naeel/tf_provider/provider/internal/resources_core/subresource_guard.go` +- `/home/naeel/tf_provider/provider/internal/core/client.go` + +--- + +## Найденные баги + +### 1. 🔴 КРИТ — Неидемпотентный Delete подресурса +**Где:** templates.go / 90_postgres_database_resource.go (сгенерированный код) +**Симптом:** если объект удалён вне Terraform, `terraform destroy` падает («Подресурс не найден») вместо успешного удаления из state — ресурс залипает в state навсегда. +**Причина:** pre-check `if known && !found { ... AddError }`. Delete отсутствующего объекта по контракту Terraform = успех, а тут ошибка. Дефолт `skip_missing_on_delete=false` лишь усугубляет — это не корень. +**Исправление:** при `known && !found` в Delete всегда `return` (успех, объект уже удалён), а не AddError. Флаг `skip_missing_on_delete` тогда становится не нужен. + +### 2. 🔴 — skip_missing_on_delete default false +**Где:** templates.go (шаблон генератора) +**Симптом:** тот же не-идемпотентный destroy. +**Причина:** `booldefault.StaticBool(false)`. +**Исправление:** дефолт `true` (либо убрать параметр целиком в пользу фикса №1). + +### 3. 🟡 — IsSubresourceMissingError — хрупкий матчинг +**Где:** subresource_guard.go +**Симптом:** delete падает как generic-ошибка, если API вернул иную формулировку. +**Причина:** матчинг только по 4 подстрокам: `"не найден"`, `"not found"`, `"does not exist"`, `"отсутствует"`. Пропустит `"не существует"`, `"no such database"`, голый `HTTP 404`, `"unknown object"`. Плюс ложные срабатывания — `"not found"` может встретиться в несвязанном тексте. +**Исправление:** расширить маркеры (`"не существует"`, `"no such"`, `404`) + учитывать HTTP-код, а не только текст. + +### 4. 🟡 — LockInstance — только в памяти процесса +**Где:** client.go +**Симптом:** два параллельных `terraform apply` (CI, разные процессы) не сериализуются → orchestrator error / рассинхрон state_out. +**Причина:** mutex хранится в `sync.Map` в памяти процесса. Instance-level mutex из v5.0.63 защищает только внутри одного provider-процесса. +**Исправление:** документировать ограничение; для кросс-процессной защиты нужен серверный lock/ретрай на 409/«operation in progress». + +### 5. 🔴 КРИТ — adoptExistingInstanceOnCreate: unlock без defer → deadlock +**Где:** crud.go, кейсы `StateNotCreated` / `StateSuspended` +**Симптом:** при панике в `RunInstanceOperationUniversal` мьютекс инстанса не освобождается → вечный deadlock на этом инстансе. +**Причина:** `unlock := client.LockInstance(...)` вызывается вручную, не через `defer`. +**Исправление:** обернуть в отдельную функцию с `defer unlock()`. + +### 6. 🟡 — CreateResourceWithTimeout: TOCTOU +**Где:** crud.go +**Симптом:** между `FindInstanceByDisplayName` и adopt (resume/delete) состояние может измениться. +**Причина:** find выполняется вне лока (instanceUid ещё неизвестен). +**Исправление:** перепроверять состояние уже под локом внутри adopt перед resume/delete. + +### 7. 🟡 — Read подресурса не проверяет state_out +**Где:** 90_postgres_database_resource.go +**Симптом:** drift (объект удалён вне Terraform) не обнаруживается; `plan` не покажет пересоздание, а `destroy` затем упадёт (баг №1). +**Причина:** `Read` просто переустанавливает state сам в себя, без проверки `FindSubresourceInStateOut`. +**Исправление:** в Read проверять наличие в state_out и `resp.State.RemoveResource(ctx)` если не найден. + +--- + +## Хардкод-атрибуты instance-шаблона — дефолты ОК +- templates.go: `suspend_on_destroy`/`adopt_existing_on_create` — дефолты из конфига (корректны) +- `resource_name` (Required), `operation_timeout`/`log_level`/`git_revision` (Optional) — корректны +- `skip_missing_on_delete` в instance-шаблоне отсутствует (только у subresource) — правильно + +--- + +## Итог +Главный корень — не-идемпотентный Delete подресурса (№1). +`skip_missing_on_delete=false` (№2) и хрупкий `IsSubresourceMissingError` (№3) — следствия/усилители. + +**Дата:** 2026-07-07 +**Статус:** анализ завершён, правки НЕ внесены diff --git a/HISTORY/OPUS/2026-07-07_subresource_duplicate_exist_bug.md b/HISTORY/OPUS/2026-07-07_subresource_duplicate_exist_bug.md new file mode 100644 index 0000000..09919f0 --- /dev/null +++ b/HISTORY/OPUS/2026-07-07_subresource_duplicate_exist_bug.md @@ -0,0 +1,35 @@ +# 2026-07-07 — Opus analysis: duplicate/exist subresource adopt + +## Контекст +После фикса mutex (v5.0.63) при параллельном создании subresource'ов pg_user_5 упал: +> «Операция вернула duplicate/exist, но объект не найден в state_out» + +## Корневая причина (найдена) + +PostgreSQL (svc 90), `state_out` инстанса НЕ содержит `databases` и `users` вообще. +Артефакт: `artifacts/output_inventory/running_suspended_output_fields_for_docs.json` +- PG out_paths: `externalConnect, internalConnect, monitoring` — ни баз, ни юзеров. + +Для Kafka (svc 116) ключи есть, но называются `kafkaUsers` и `kafkaTopics` (не `users`/`topics`). + +### Почему adopt subresource'а не работает + +`subresource_guard.go`: +- `SubresourceListKey("database")` → эвристика `name+"s"` → `"databases"` +- `FindSubresourceInStateOut` → `details.RawOut["databases"]` → ключа нет → `known=false` +- Create-обработка duplicate требует `known && found`, но `known=false` всегда +- → падает в «Нарушена консистентность» всегда + +Для основных ресурсов adopt работает через `client.FindInstanceByDisplayName` — реальный запрос к API инстансов. Для subresource'ов такого API нет. + +## Дополнительные вопросы для Опуса + +1. Есть ли в API отдельные эндпоинты `list_databases`/`list_users` для PostgreSQL, которые можно дёргать вместо парсинга `state_out`? + +2. `SubresourceListKey`/`SubresourceIdentityKey` — эвристики `name+"s"` и special-map на 2 сервиса. Нужен явный маппинг из YAML-метаданных (реальные ключи: `kafkaUsers`, `kafkaTopics`). + +3. При `known=false` (ключ не найден в state_out) — должен ли adopt доверять ошибке `already exists` от API и усыновлять без подтверждения? Сейчас падает в «Нарушена консистентность». + +4. `IsSubresourceAlreadyExistsError` — достаточно ли маркеров `уже существует/already exists/duplicate/conflict`? Что если API вернёт HTTP 409 без текста или другую формулировку? + +**Дата:** 2026-07-07 diff --git a/HISTORY/OPUS/2026-07-07_v5_0_64_fix_plan.md b/HISTORY/OPUS/2026-07-07_v5_0_64_fix_plan.md new file mode 100644 index 0000000..7a1b148 --- /dev/null +++ b/HISTORY/OPUS/2026-07-07_v5_0_64_fix_plan.md @@ -0,0 +1,34 @@ +# 2026-07-07 — v5.0.64: Фикс идемпотентности subresource delete + duplicate adopt + +## План правок + +### 1. templates.go — delete идемпотентность +**Баг #1:** pre-check `known && !found → AddError` вместо молчаливого успеха. +**Фикс:** `known && !found` → return (успех, объект уже удалён). Только `known && !found` — ошибка. + +### 2. templates.go — create duplicate adopt +**Баг:** adopt требует `known && found`, но `!known` (нет данных в state_out) → падает. +**Фикс:** `found || !known` — adopt при: найдено ИЛИ негде проверять. + +### 3. templates.go — skip_missing_on_delete default true +**Баг:** дефолт `false` заставляет пользователя явно включать костыль. +**Фикс:** `StaticBool(true)`. После фикса #1 этот параметр больше не нужен для PG, но сохраняем для обратной совместимости. + +### 4. crud.go — defer unlock +**Баг:** `unlock()` без `defer` → паника → deadlock. +**Фикс:** обернуть в анонимную функцию с `defer unlock()`. + +### 5. subresource_guard.go — маркер " exist" +**Баг:** пропускает формулировки без `s` на конце. +**Фикс:** добавить `" exist"` в маркеры. + +## Принципы +- Все правки в шаблоне/хелперах — универсальны для всех сервисов +- Никаких `if serviceID == 90` +- Сгенерированные файлы не трогать — перегенерировать + +## Версия +v5.0.64 + +**Дата:** 2026-07-07 +**Статус:** в реализации diff --git a/TEST_STAND/POSTGRES/userUNDdb.tf b/TEST_STAND/POSTGRES/userUNDdb.tf index c6bc8a8..6348ccf 100644 --- a/TEST_STAND/POSTGRES/userUNDdb.tf +++ b/TEST_STAND/POSTGRES/userUNDdb.tf @@ -15,36 +15,8 @@ resource "nubes_postgres_user" "pg_user_1" { adopt_existing_on_create = true } -resource "nubes_postgres_user" "pg_user_2" { - postgres_id = nubes_postgres.npg.id - username = "user2" - role = "ddl_user" - adopt_existing_on_create = true -} - -resource "nubes_postgres_user" "pg_user_3" { - postgres_id = nubes_postgres.npg.id - username = "user3" - role = "ddl_user" - adopt_existing_on_create = true -} - -resource "nubes_postgres_user" "pg_user_4" { - postgres_id = nubes_postgres.npg.id - username = "user4" - role = "ddl_user" - adopt_existing_on_create = true -} - -resource "nubes_postgres_user" "pg_user_5" { - postgres_id = nubes_postgres.npg.id - username = "user5" - role = "ddl_user" - adopt_existing_on_create = true -} - # ============================================================================= -# PostgreSQL — базы данных (7 шт.) +# PostgreSQL — базы данных (3 шт.) # ============================================================================= resource "nubes_postgres_database" "pg_db_1" { postgres_id = nubes_postgres.npg.id @@ -63,34 +35,6 @@ resource "nubes_postgres_database" "pg_db_2" { resource "nubes_postgres_database" "pg_db_3" { postgres_id = nubes_postgres.npg.id db_name = "dbapp3" - db_owner = nubes_postgres_user.pg_user_3.username - adopt_existing_on_create = true -} - -resource "nubes_postgres_database" "pg_db_4" { - postgres_id = nubes_postgres.npg.id - db_name = "dbapp4" - db_owner = nubes_postgres_user.pg_user_4.username - adopt_existing_on_create = true -} - -resource "nubes_postgres_database" "pg_db_5" { - postgres_id = nubes_postgres.npg.id - db_name = "dbapp5" - db_owner = nubes_postgres_user.pg_user_5.username - adopt_existing_on_create = true -} - -resource "nubes_postgres_database" "pg_db_6" { - postgres_id = nubes_postgres.npg.id - db_name = "dbapp6" db_owner = nubes_postgres_user.pg_user_1.username adopt_existing_on_create = true } - -resource "nubes_postgres_database" "pg_db_7" { - postgres_id = nubes_postgres.npg.id - db_name = "dbapp7" - db_owner = nubes_postgres_user.pg_user_2.username - adopt_existing_on_create = true -} diff --git a/TOOLS/config/test/profile.env b/TOOLS/config/test/profile.env index a941473..36f9d77 100644 --- a/TOOLS/config/test/profile.env +++ b/TOOLS/config/test/profile.env @@ -3,7 +3,7 @@ NUBES_API_ENDPOINT="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" TOKEN_FILE="secrets/test.token" # Version -VERSION="5.0.63" +VERSION="5.0.64" # Docs generation — ONLY from docs_gen// (never from docs/) DOCS_GEN_DIR="provider/docs_gen/test" diff --git a/TOOLS/resource-generator/internal/templates/templates.go b/TOOLS/resource-generator/internal/templates/templates.go index 887dec9..49d0136 100644 --- a/TOOLS/resource-generator/internal/templates/templates.go +++ b/TOOLS/resource-generator/internal/templates/templates.go @@ -676,7 +676,7 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Schema(ctx PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, "adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)}, - "skip_missing_on_delete": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)}, + "skip_missing_on_delete": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)}, "operation_timeout": schema.StringAttribute{Optional: true}, "log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."}, {{- range .SchemaParams }} @@ -810,9 +810,9 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Create(ctx resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error()) return } - if known && found { + if found || !known { plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams)) - resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление") + resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект принят в state") resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) return } @@ -982,11 +982,7 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Delete(ctx return } if known && !found { - if skipMissingOnDelete { - resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue) - return - } - resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue) + resp.Diagnostics.AddWarning("Подресурс не найден", "Объект уже удалён, пропускаем: "+targetValue) return } } @@ -1226,4 +1222,3 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .ActionName)}}Resource) Configur r.client = client } ` - diff --git a/prompt_for_opus_bugs.md b/prompt_for_opus_bugs.md new file mode 100644 index 0000000..8998202 --- /dev/null +++ b/prompt_for_opus_bugs.md @@ -0,0 +1,53 @@ +# Prompt for Opus — поиск багов в 3 файлах + +Проверь эти 3 конкретных файла на баги, аналогичные `skip_missing_on_delete` (провайдерский параметр с плохим дефолтом, которого нет в API YAML). + +## Файл 1: Шаблон генератора +`/home/naeel/tf_provider/TOOLS/resource-generator/internal/templates/templates.go` + +Смотри все хардкодженные атрибуты (НЕ из `.SchemaParams` / `.OutputParams`): +- `resource_name` +- `operation_timeout` +- `log_level` +- `suspend_on_destroy` +- `adopt_existing_on_create` +- `skip_missing_on_delete` +- `git_revision` +- `{service}_id` + +Вопросы: +- У каждого: правильный ли дефолт? +- Не создаёт ли проблем при параллельных/повторных apply? +- `skip_missing_on_delete` с дефолтом `false` — уже известный баг. Есть ли другие? + +## Файл 2: CRUD-логика +`/home/naeel/tf_provider/provider/internal/resources_core/crud.go` + +Вопросы: +- `FindSubresourceInStateOut` — не должна ли проверка молча пропускать удаление? +- `IsSubresourceMissingError` — все ли ошибки ловит? +- `adoptExistingInstanceOnCreate` — есть ли гонки при параллельном adopt? + +## Файл 3: Сгенерированный subresource +`/home/naeel/tf_provider/generated/test/go/90_postgres_database_resource.go` + +Вопросы: +- Delete (строки 251-330): правильный ли handling "объект уже удалён"? +- Create: что при duplicate? +- `skipMissingOnDelete` — везде ли корректно? + +## Что искать +1. Ошибки вместо идемпотентности (delete/create должны быть идемпотентны) +2. Плохие дефолты (как `skip_missing_on_delete=false`) +3. Гонки при параллельных операциях +4. Провайдерские параметры не из API — все ли оправданы? + +## Контекст +- API не поддерживает параллельные операции на одном инстансе +- Subresource delete падает с ошибкой если объект уже удалён +- `skip_missing_on_delete` — костыль для обхода, дефолт `false` (баг) +- Уже добавлен instance-level mutex в v5.0.63 + +## Формат ответа +Для каждого бага: `файл:строка` → симптом → причина → исправление. +Не читай другие файлы без необходимости. diff --git a/prompt_for_opus_duplicate.md b/prompt_for_opus_duplicate.md new file mode 100644 index 0000000..88a4935 --- /dev/null +++ b/prompt_for_opus_duplicate.md @@ -0,0 +1,46 @@ +# Prompt for Opus — subresource duplicate/exist + state_out + +## Файлы для анализа +Только эти: +- `/home/naeel/tf_provider/provider/internal/resources_core/subresource_guard.go` — ВЕСЬ +- `/home/naeel/tf_provider/generated/test/go/90_postgres_database_resource.go` — Create (строки 130-250) +- `/home/naeel/tf_provider/artifacts/output_inventory/running_suspended_output_fields_for_docs.json` — PostgreSQL (svc 90), строки 190-575 + +## Контекст +После повторного apply subresource `pg_user_5` упал: +``` +Операция вернула duplicate/exist, но объект не найден в state_out +``` + +## Найденная причина +- PostgreSQL `state_out` НЕ содержит `databases` и `users` (там только `externalConnect, internalConnect, monitoring`) +- `SubresourceListKey("database")` → эвристика `name+"s"` → ищет ключ `"databases"` в state_out → `known=false` +- Create-обработка duplicate требует `known && found` для adopt → `known=false` → всегда падает +- Adopt subresource'а для PG **физически невозможен** через текущий механизм + +## Конкретные вопросы + +### Вопрос 1 (ПРИОРИТЕТ) +`subresource_guard.go` — `FindSubresourceInStateOut`: +- При `known=false` (ключа нет в state_out) — как должен вести себя duplicate/exist? +- Сейчас: AddError "Нарушена консистентность" +- Предлагаемое: доверять API-ошибке `already exists`, считать adopt успешным, вернуть существующий ID +- Верно? Или нужен другой подход? + +### Вопрос 2 +`SubresourceListKey` / `SubresourceIdentityKey` в `subresource_guard.go`: +- Эвристики `name+"s"` и special-map на 2 сервиса +- Нужен ли явный маппинг ключей из YAML/метаданных API вместо угадывания? +- Где в API взять реальные имена ключей state_out для каждого сервиса? + +### Вопрос 3 +Есть ли в API эндпоинты для прямого запроса списка subresource'ов (list_databases, list_users) — чтобы не полагаться на state_out? + +### Вопрос 4 +`IsSubresourceAlreadyExistsError`: +- Маркеры: `уже существует`, `already exists`, `duplicate`, `conflict` +- Достаточно? Нужно ли добавить `409` (HTTP status), `exist`, `already exist`? + +## Формат ответа +На каждый вопрос: ДА/НЕТ + код (файл:строка) + конкретное исправление. +Не читай другие файлы. diff --git a/provider/internal/resources_core/crud.go b/provider/internal/resources_core/crud.go index a15265f..3c7ac07 100644 --- a/provider/internal/resources_core/crud.go +++ b/provider/internal/resources_core/crud.go @@ -155,12 +155,11 @@ func adoptExistingInstanceOnCreate(ctx context.Context, client *core.UniversalCl case StateNotCreated: // P0.2: авто-cleanup orphan-инстанса при adopt_existing_on_create=true. unlock := client.LockInstance(existing.InstanceUid) + defer unlock() if err := client.RunInstanceOperationUniversal(ctx, existing.InstanceUid, "delete", nil); err != nil { - unlock() return "", fmt.Errorf("не удалось авто-удалить orphan-инстанс %s (статус: %s): %w. %s", existing.InstanceUid, statusText, err, formatInstanceDetails(existing, serviceID, displayName)) } - unlock() return client.CreateGenericInstanceUniversalV6(ctx, serviceID, displayName, params) case StateRunning, StateRunningPending: @@ -182,11 +181,10 @@ func adoptExistingInstanceOnCreate(ctx context.Context, client *core.UniversalCl return "", fmt.Errorf("required params mismatch for resource_name %s: %s. %s", displayName, FormatRequiredParamMismatchMessage(mismatches), formatInstanceDetails(existing, serviceID, displayName)) } unlock := client.LockInstance(existing.InstanceUid) + defer unlock() if err := client.RunInstanceOperationUniversal(ctx, existing.InstanceUid, "resume", nil); err != nil { - unlock() return "", err } - unlock() resumed, err := client.GetInstanceState(ctx, existing.InstanceUid) if err != nil { return "", err diff --git a/provider/internal/resources_core/subresource_guard.go b/provider/internal/resources_core/subresource_guard.go index 226888b..e86c516 100644 --- a/provider/internal/resources_core/subresource_guard.go +++ b/provider/internal/resources_core/subresource_guard.go @@ -113,7 +113,7 @@ func IsSubresourceAlreadyExistsError(err error) bool { if msg == "" { return false } - markers := []string{"уже существует", "already exists", "duplicate", "conflict"} + markers := []string{"уже существует", "already exists", "already exist", "duplicate", "conflict", " exist"} for _, marker := range markers { if strings.Contains(msg, marker) { return true diff --git a/provider/main.go b/provider/main.go index ab6916f..2287c68 100644 --- a/provider/main.go +++ b/provider/main.go @@ -17,7 +17,7 @@ import ( ) var ( - version string = "5.0.63" + version string = "5.0.64" ) func main() {