Files
tf_provider/HISTORY/2026-07-06_architectural_refactoring.md
T

9.2 KiB
Raw Blame History

2026-07-06 — Архитектурный рефакторинг tf_provider

Контекст

После анализа Claude Opus (HISTORY/OPUS/2026-07-06_architectural_analysis.md) выявлены архитектурные проблемы и выполнены исправления.

Выполненные изменения

1. Реструктуризация проекта

Было:                          Стало:
devops/                        TOOLS/scripts/        (все .sh)
devops/profiles/               TOOLS/config/         (profile.env, services_list, timeouts)
devops/ARCHITECTURE.md         TOOLS/ARCHITECTURE.md
devops/config/                 УДАЛЕНО (дубликат profiles)
provider/resources_yaml/       УДАЛЕНО (сгенерированное → generated/)
provider/internal/resources_gen/ УДАЛЕНО (сгенерированное → generated/)
                               generated/{test,prod,dev}/  (вывод пайплайна)

2. Независимость генераторов

  • yaml-generator — требует NUBES_OUTPUT_DIR, без default в provider/
  • resource-generator — требует NUBES_RESOURCES_DIR + NUBES_RESOURCES_GEN_DIR
  • docs-generator — требует --resources, --docs, --version флаги
  • Удалён detectVersion() — больше не читает provider/main.go
  • Удалён detectRoot(), pickPath() — нет хардкод-путей

3. Общая библиотека типов

Создан TOOLS/lib/ — единый YAML-контракт. Модуль: tf-tools/lib.

Подключены:

  • yaml-generator → type alias ParamSpec = lib.ParamSpec и др.
  • resource-generator → type alias OutputParam, OperationSpec, ParamSpec

НЕ подключён:

  • docs-generator — будет заменён на LLM-генератор, не трогаем

При добавлении поля в YAML — править lib/types.go, несогласованность ловится компилятором.

4. Синхронизация версий

  • provider/main.go: 5.0.75 → 5.0.60 (соответствует последней сборке)
  • profile.env: три переменные → одна VERSION
  • 03_build, 04_publish: ${PROVIDER_VERSION:-${RELEASE_VERSION:-}}${VERSION}

5. Автосборка бинарников

01_generate_yamls.sh: если исходники новее бинарника — пересборка.

6. Удалённый мусор

  • devops/config/ — дубликат profiles
  • 02_generate_resources_and_docs.sh + _template.sh — legacy, заменены v2
  • cloud-dashboard/, tools/ (root), internal/ (root), universal_rebuild/
  • 18 одноразовых файлов (check_ops.py, s.sh, ...)

7. Слияние ops-generator → docs-generator

--ops флаг. Три генератора вместо четырёх.

8. Документация LLM

docs/LLM_DOCS_GENERATION.md — подход, промпт, тест на Postgres (gpt-oss-120b, 9/10).

Текущее состояние

tf_provider/
├── TOOLS/            — всё для генерации (код + скрипты + настройки)
│   ├── yaml-generator/
│   ├── resource-generator/
│   ├── docs-generator/
│   ├── lib/          — общие YAML-типы
│   ├── scripts/
│   ├── config/{test,prod,dev}/
│   └── ARCHITECTURE.md
├── provider/         — только исходники
├── generated/        — вывод пайплайна (gitignored)
└── docs/             — документация

Осталось

  • yaml-generator → lib
  • resource-generator → lib
  • docs-generator → lib

⚠️ BUG: modify падает с 500 — GET ?fields=cfsParams вызывает getResourceRealmConfig

Симптом: terraform apply при modify возвращает:

не удалось получить детали операции: ошибка API 500: Invalid call of the function [getResourceRealmConfig], first Argument [resourceRealm] is of invalid type, Cannot cast Object type [Struct] to a value of type [string]

Причина: RunInstanceOperationUniversalWithDefaults (client.go:384) делает GET /instanceOperations/{opUid}?fields=cfsParams. Бэкенд (ColdFusion) при вычислении cfsParams вызывает getResourceRealmConfig через expression_parser.cfc:184. Если resourceRealm в контексте инстанса — Struct (объект), а не string, функция падает.

Где проявляется:

  • client.go:420RunInstanceOperationUniversalWithDefaults (modify/create/delete с дефолтами)
  • client.go:1399RunInstanceOperationUniversalByCode (операции по code)
  • client.go:224CreateGenericInstanceUniversalV6 (CREATE flow) — работает на новых инстансах

Почему на одних инстансах падает, на других нет:

  • Новые инстансы: resourceRealm = string → OK
  • Старые/модифицированные: resourceRealm = Struct → 500

Исправление (v5.0.62):

  • crud.go:58: UpdateResourceWithTimeoutRunInstanceOperationUniversal (без GET)
  • GET не нужен для modify — все параметры уже известны из конфига

Статус: FIXED v5.0.62 Файлы:

  • /home/naeel/tf_provider/provider/internal/resources_core/crud.go:58
  • /home/naeel/tf_provider/provider/internal/core/client.go:384 (WithDefaults — не используется в modify)

Тесты (v5.0.62):

  • CREATE postgres — OK
  • MODIFY postgres (replicas, backup_config, access_config) — OK
  • CREATE/DELETE user — OK
  • CREATE/DELETE database — OK
  • Invalid role → 400 c понятным сообщением — OK
  • No-change plan (идемпотентность) — OK

Остаётся риск:

  • RunInstanceOperationUniversalByCode (client.go:1399) всё ещё делает GET — может упасть для service_operation_resource на проблемных инстансах
  • CreateGenericInstanceUniversalV6 (client.go:224) — CREATE на инстансах где resourceRealm = Struct может упасть

⚠️ BUG: Параллельные операции на одном инстансе ломаются

Симптом: При создании нескольких subresource'ов (user, database) на одном postgres-инстансе:

  • ошибка API 500: Cannot connect to the orchestrator — бэкенд не справляется с параллельными запросами
  • операция завершилась успешно, но объект не найден в state_out — user создался, но RefreshResourceState не видит его
  • экземпляр не готов: операция в ожидании — инстанс занят предыдущей операцией

Причина: Terraform по умолчанию параллелит до 10 ресурсов (-parallelism=10). Все операции на одном инстансе (5 user'ов + 7 database'ов) стартуют одновременно → гонка на бэкенде.

Пример: 2026-07-07 на инстансе 5111108D:

  • 5 user'ов создавались параллельно: user_2 ok, user_1+user_3 — orchestrator error, user_4+user_5 — state_out не найден
  • 7 database'ов упали: инстанс не готов

План исправления: mutex map в провайдере (стандартный подход как в AWS/GCP провайдерах):

// client.go — глобальная карта мьютексов
var instanceMutexes sync.Map  // key: instanceUid

func (c *UniversalClient) lockInstance(instanceUid string) func() {
    mu, _ := c.instanceMutexes.LoadOrStore(instanceUid, &sync.Mutex{})
    mu.(*sync.Mutex).Lock()
    return func() { mu.(*sync.Mutex).Unlock() }
}

Вызывать в каждом CRUD:

unlock := client.lockInstance(instanceID)
defer unlock()

Альтернативы (хуже):

  • depends_on цепочкой в конфиге — неудобно, требует ручной правки .tf
  • -parallelism=1 — замедляет ВСЕ ресурсы, не только subresource'ы

Затрагивает:

  • Все subresource-операции (postgres_user, postgres_database, mariadb_user, clickhouse_user, etc.)
  • Modify + delete на одном инстансе тоже могут столкнуться

Статус: ⚠️ OPEN — не исправлено Файлы для правки:

  • /home/naeel/tf_provider/provider/internal/core/client.go — добавить mutex map + lockInstance
  • /home/naeel/tf_provider/provider/internal/resources_core/crud.go — добавить lock/unlock в Create/Update/Delete
  • /home/naeel/tf_provider/provider/internal/resources_core/service_operation_resource.go — добавить lock/unlock