fix(provider): нормализация регистра UUID при отправке map-fixed JSON в API

- resources_core.BuildJSON оборачивает результат в jsonutil.LowercaseUUIDsInText:
  платформа сравнивает регистр UUID при create, а ресурсы отдают id в UPPERCASE
  (nsxtUid/vdcUid) -> без нормализации create Штурвала падал 'Edge не развёрнут
  в указанном vDC' (обнаружено на провайдере 2.0.23 из-под Windows).
- Одна точка покрывает все map-fixed-параметры (create/modify/redeploy),
  регенерация не требуется.
- Документация: HISTORY/2026-09-30, docs/60_strategy/terraform_case_sensitivity_fix.md §11,
  NOTES/30_analysis/ARCHITECTURE_NEW.md §6.5, docs/help/architecture-and-methods.md §7.

Не выпущено: версия не поднималась, релиз/регенерация не выполнялись.
This commit is contained in:
Repinoid
2026-09-30 08:34:04 +03:00
parent 09e38928d0
commit 9aed2dcdd9
5 changed files with 178 additions and 1 deletions
@@ -0,0 +1,87 @@
# 2026-09-30 — Регистр UUID: нормализация на ОТПРАВКЕ в API (create/modify/redeploy)
> Разбор: `docs/60_strategy/terraform_case_sensitivity_fix.md` §11 (главный документ по теме),
> `NOTES/30_analysis/ARCHITECTURE_NEW.md` §6.5.
## Что обнаружилось
Костыль `lower(...)` в конфиге стенда Штурвала — **не «просто проще», а обязателен**.
Без него `terraform apply` (create кластера) падает: платформа отвечает
«Edge не развёрнут в указанном vDC».
Обнаружено при запуске Terraform **из-под Windows**, на провайдере **2.0.23**
(то есть после всех «фиксов регистра», выпущенных 24.09).
Костыль живёт в примере (и в gitea `Nail/tf_examples`):
```hcl
# tf_examples/fullpipe_chain/shturval.tf:139-140
vdc_uid = lower(nubes_vc_vdc.vdc.id)
nsxt_uid = lower(nubes_vc_nsxt.edge.id)
```
## Почему прошлые фиксы не помогли (главная мысль)
Провайдер `2.0.23` нормализует регистр UUID **только при СРАВНЕНИИ**:
план vs state, adopt/suspend/resume, modifier-compare, диагностика
(`jsonutil.LowercaseUUIDsInText` → `JSONStringsEquivalent`, `JsonNormalize()`,
`ParamsMatchForResume`, `normalizeCompareValue`).
**Путь ОТПРАВКИ в API остался без нормализации.** Все map-fixed JSON-параметры
собираются одной функцией `resources_core.BuildJSON`
(`provider/internal/resources_core/helpers.go`), а её вызывает сгенерированный код
(`NestedJSONExpr`, шаблон `TOOLS/resource-generator/internal/templates/instance.go`,
ветки Create / Modify / Redeploy). `BuildJSON` берёт `ValueString()` подполей **как есть**.
Ресурс `nubes_vc_nsxt` отдаёт `id` в UPPERCASE (`2C37FED1-…`), платформа хранит
UUID в lowercase и **сравнивает регистр при create** → `startupConfiguration.nsxtUid`
в верхнем регистре отвергается.
Почему не спас `resolveRefSvcParamValues` (`core/refsvc.go`,
`core/refsvc_resolve.go`): он нормализует только **top-level** refSvc-параметры и
`s3.*uid` **внутри** map-fixed. `vdcUid`/`nsxtUid` — обычные строковые подполя
JSON, refSvcId у них нет, под шаблон `s3.*uid` они не подпадают.
## Что сделано
| Файл | Изменение |
|---|---|
| `provider/internal/resources_core/helpers.go` | `BuildJSON` оборачивает результат в `jsonutil.LowercaseUUIDsInText(...)` (+ импорт `core/jsonutil`, комментарий-обоснование) |
Одна точка → покрыты **все** map-fixed-параметры всех ресурсов на
create / modify / redeploy (19 сгенерированных ресурсов, `resources_gen/`).
Регенерация не требуется (логика сериализации одна).
## Оценка риска (почему это безопасно)
- `BuildJSON` используется **только для отправки** в API, не для построения state.
- Regex `uuidAnywhereRegex` = `[0-9a-f]{8}-xxxx-xxxx-xxxx-xxxxxxxxxxxx` — совпадает
только с UUID; пароли/имена/произвольные строки не задевает.
- Проверено по спекам: внутри map-fixed **нет** строковых секретных полей
(password/secret/token) — только `*Uid`-ссылки на ресурсы.
- Это **выравнивание** с уже принятым в провайдере правилом «регистр UUID незначим»
(то же приведение уже делается на сравнении), а не новое поведение.
Остаточный риск: если в map-fixed когда-нибудь появится строковое поле, где
пользователь хранит **свой** UUID, и регистр там семантически важен (не ссылка на
ресурс) — он будет приведён к lowercase. Сейчас таких полей нет.
## Следствия
- `lower(...)` в HCL становится **не нужен** — убирать в конфигах и в примере
(отдельной командой, после релиза провайдера).
- **Не выпущено**: версия не поднималась, релиз/заливка в реестр не выполнялись,
регенерация не запускалась.
- В локальных стендах костыля нет: `DEV_STAND/FPipeGmail/shturval.tf:125-126` и
`DEV_STAND/FullPipe/shturval.tf1:123` передают `nubes_vc_vdc.vdc.id` /
`nubes_vc_nsxt.edge.id` напрямую → на create у них тот же риск.
## Открытые вопросы (не закрыты)
1. Проверить на живом стенде: create кластера Штурвала **без** `lower(...)` на сборке
с этим фиксом — `apply` запускает только пользователь.
2. `core/params.go` → `normalizeUniversalValueV6`: скалярные UUID, попадающие в
дефолты create (`instance_create.go`) и в досылку modify (`operation_run.go`),
к lowercase не приводятся (вторично, нужен замер).
3. `resources_core/ref_validation.go` (`ValidateRefParamsOnAdopt`): ref-параметр
внутри JSON не валидируется при adopt (открыто с 24.09).