Commit Graph
618 Commits
Author SHA1 Message Date
Repinoid 39849bf459 docs(crud): раздел «Что пользователь задаёт сам» продублирован на странице сайта
Стендовый README и страница curated/crud/three_apps.md описывают одно и то же,
поэтому раздел перенесён в страницу сайта сразу после «Структура манифестов»:
- обязательные значения (api_token, realm, s3_name) и создание terraform.tfvars;
- имена с правилами уникальности (кластер и приложения — в пределах стенда,
  юзер/база — в пределах кластера, домены — в облаке) и объяснение через
  adopt_existing_on_create (crud.go:171, refsvc_find.go:47);
- что можно не задавать (дефолты pg/main.tf).
Из шагов 1 и 3 убраны дублирующие таблицы — вместо них ссылка на раздел.
Плюс выровнены отступы в блоке cp terraform.tfvars.example (в двух файлах было
по-разному).
Проверено: diff разделов — совпадает построчно, отличие только в разделителе «---»
(есть в README, не используется на странице); страница 228 строк, 18 строк с
блоками кода (чётно).
2026-10-02 08:08:35 +03:00
Repinoid 7b63f12347 docs(crud): раздел «Что пользователь задаёт сам» + правила уникальности имён
Замечание владельца: в README не было видно, какие параметры юзер задаёт СВОИМИ
значениями, и не сказано про уникальность домена и имени инстанса.
Добавлен раздел (сразу после «Как это устроено»):
- обязательные значения: api_token (pg+apps), realm (pg+apps, одно и то же),
  s3_name (pg) + команды создания terraform.tfvars в обеих папках;
- имена, которые нужно придумать: pg_resource_name и *_resource_name приложений —
  уникальны в пределах стенда; pg_username/pg_db_name — в пределах кластера
  (служебные admin/postgres/standby платформа не примет); *_domain — уникальны
  в облаке;
- почему: adopt_existing_on_create ищет инстанс по имени внутри сервиса
  (provider/internal/resources_core/crud.go:171, provider/internal/core/refsvc_find.go:47),
  поэтому при занятом имени провайдер не создаёт новый ресурс, а усыновляет
  существующий; без adopt — падает с «уже существует». Пример: tflucee/tfflask/
  tfnodejs заняты старыми инстансами (apps/locals.tf:45,57,68);
- что можно не задавать: перечислены дефолты pg/main.tf и размеры в apps/locals.tf.
Из шагов 1 и 3 убраны дублирующие таблицы — теперь ссылка на раздел, комментарии
в командах поправлены («см. таблицу ниже» больше не существует).
Проверено: 190 строк, 14 строк с блоками кода (чётно), заголовки на месте.
2026-10-02 08:07:58 +03:00
Repinoid d0c20f42a0 docs(history): запись о правках документации CRUD + оглавление обновлено
HISTORY/50_docs/2026-10-02_crud_docs_page_and_manual_pages_pipeline.md:
- правки README стенда (5fdbc31 раздел «Как это устроено» без натянутых терминов,
  6ef003b apply+destroy, 5674b86/b8f5238 справка по выходам pg и перенос в конец);
- переписанная страница сайта curated/crud/three_apps.md (ab9f7d2): таблица «было -> стало»;
- разбор пайплайна: что копируется в docs_dir (04:157-173), запрет использовать docs/
  целиком (04:94), docs_dir = generated/<стенд>/docs (04:95-98), nav/exclude_docs
  (mkdocs.yml:52 и 4-15), подстановка плейсхолдеров (04:182-198), публикация (04:344);
  два способа публиковать ручные страницы: A (в docs/curated + nav — сделано) и
  B (добавить копирование в 04 — НЕ сделано, ждёт команды);
- раздел «Мои ошибки»: неверный термин decoupling, однобокая формулировка про destroy,
  скрытый факт про ручной creds.json.
HISTORY/README.md: строка новой записи в 50_docs, счётчики 9->10 и 75->76.
Проверено: в 50_docs 10 файлов, всего 76 файлов (кроме README), счётчики совпали.
2026-10-02 08:05:12 +03:00
Repinoid ab9f7d2962 docs(curated): страница CRUD-стенда приведена к схеме pg/ + apps/
Страница curated/crud/three_apps.md описывала старую схему: всё в одной папке,
два apply и пароль из vault_secrets кластера через try(). Это уже не так.
Переписана по TEST_STAND/CRUD/README.md:
- два каталога = два state, apply/destroy в каждом меняет только своё;
- три шага: pg/ -> terraform output -json > ../apps/creds.json -> apps/;
- пароль берётся из выхода подресурса nubes_postgres_user (не из vault_secrets);
- раздел «Структура манифестов» и новый раздел «Справка: выходные параметры pg/»
  (состав выходов, вид JSON {sensitive,type,value}, соответствие env-переменным
  Flask/Node.js/Lucee);
- имена приведены к текущим: lucee-crud / flask-crud / nodejs-crud, пути в
  apps/locals.tf, добавлен keep_on_destroy;
- снято непроверенное «состояние на 2026-10-01, все 6 ресурсов running» и строка
  про DEV_STAND/CRUD как аналог — там осталась старая плоская схема, сказано прямо.
Проверено: 191 строка, 16 открывающих/закрывающих блоков кода (чётно).
2026-10-02 08:02:28 +03:00
Repinoid b8f5238f05 docs(crud): справка по выходам pg перенесена в конец файла
Из шага 2 убран развёрнутый блок (он мешал последовательности трёх шагов),
вместо него одна ссылка. Сам блок стал последним разделом файла
«## Справка: выходные параметры pg/» (уровень заголовка 4 -> 2, т.к. теперь
это самостоятельный раздел, а не подпункт шага 2). Содержимое не менялось.
Проверено: grep заголовков (112: ## Справка), состав файла — 151 строка.
2026-10-02 07:59:06 +03:00
Repinoid 5674b86d58 docs(crud): справка по выходным параметрам pg — состав, вид JSON и как их читают приложения
Шаг 2 теперь поясняет не только команду выгрузки, но и что именно выгружается:
- таблица шести выходов pg/outputs.tf (pg_host/pg_port/pg_username/pg_db_name/
  pg_password/pg_ssl_mode) с указанием источника каждого;
- что terraform output -json кладёт объекты {sensitive, type, value}, а не голые
  значения, поэтому в apps/locals.tf обращение идёт через .value;
- таблица соответствия: выход pg/ -> переменная окружения в Flask/Node.js/Lucee
  (PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE/PGSSLMODE, у Lucee ещё testds_* и DATABASE_URL).
Все факты сверены чтением файлов: pg/outputs.tf, apps/locals.tf, apps/flask.tf,
apps/nodejs.tf, apps/lucee.tf.
2026-10-02 07:56:47 +03:00
Repinoid 6ef003bc58 docs(crud): покрыты и apply, и destroy — про удаление было сказано однобоко
В разделе «Как это устроено» первая строка говорила только про destroy,
про изменения (apply) не было ни слова. Теперь явно: apply и destroy
в apps/ меняют только приложения, apply и destroy в pg/ — только базу.
Проверено: чтением файла после правки.
2026-10-02 07:54:04 +03:00
Repinoid 5fdbc316c2 docs(crud): раздел «Как это устроено» — без натянутых терминов
Убрано «(разделение ответственности, decoupling)» — это про модули кода, не про
состояния Terraform. Написано прямо: два отдельных файла состояния.
Добавлен факт, который был скрыт: связь pg -> apps идёт через apps/creds.json,
после смены хоста или пароля файл нужно обновить и повторить apply в apps/.
Проверено: чтением файла после правки.
2026-10-02 07:52:55 +03:00
Repinoid 669672af27 docs(history): README с описанием каждой папки и каждого файла
HISTORY/README.md:
- назначение архива и приказ владельца документировать всё, включая ошибки;
- соглашения: имя файла YYYY-MM-DD_<тема>.md, нумерация папок шагом 10 как в NOTES/,
  куда писать новую запись, почему диалоги с LLM держатся авторскими подпапками;
- таблица «папка -> о чём -> сколько файлов» (75 файлов);
- по каждому файлу строка с темой: 10_reviews (2), 20_releases (8), 30_provider (6),
  40_generator (3), 50_docs (9), 60_stands (7), 70_infra (4), 90_llm (36:
  gemini 2, OPUS 17, SONNET 17);
- помечены ⛔ четыре файла Opus от 2026-09-22 («ложный путь», отменено 2026-09-24);
- раздел «Известные особенности»: упоминания несуществующих 3006_0.md/3006_1.md
  и ссылка на старый внешний репозиторий tf_registry/HISTORY/HOWTO-UPLOAD.md;
- раздел «Реорганизация»: перенос 2026-10-02 и путь к резервной копии.

Проверено: все 75 файлов с диска присутствуют в таблицах README, лишних нет
(кроме намеренно упомянутого несуществующего имени), все ссылки-папки существуют.
2026-10-02 07:36:51 +03:00
Repinoid 2aa2946700 docs(history): обновлены перекрёстные ссылки на файлы HISTORY после переноса
Перенос в тематические папки сломал бы все ссылки, поэтому обновлены пути:
- HISTORY/OPUS/ -> HISTORY/90_llm/OPUS/, HISTORY/SONNET/ -> HISTORY/90_llm/SONNET/;
- 15 целевых файлов из корня получили свой тематический префикс
  (HISTORY/<файл>.md -> HISTORY/<папка>/<файл>.md) — в 33 файлах репозитория.

Затронуто вне HISTORY: README.md, VERSIONS.md, HOW_TO/DEVOPS_BUILD_PIPELINE.md,
NOTES/README.md, NOTES/10_plans/, NOTES/20_prompts/, NOTES/30_analysis/,
NOTES/40_chat_summaries/, docs/curated/{crud,postgres}, docs/help/dev-reference/,
docs/ops/TESTING.md.

Проверки после правки:
- ссылок вида HISTORY/<дата> без тематической папки не осталось;
- все пути HISTORY/*.md из markdown-ссылок существуют (кроме трёх упоминаний,
  которые не были файлами и до переноса: HISTORY/90_llm/OPUS/3006_1.md,
  3006_0.md — планировавшиеся имена в старых транскриптах,
  и HISTORY/HOWTO-UPLOAD.md — ссылка на старый внешний репозиторий tf_registry);
- ссылки по «голому» имени внутри 90_llm/OPUS/ остались корректными (соседние файлы).
2026-10-02 07:36:09 +03:00
Repinoid 2c196e8cc8 docs(history): раскладка HISTORY по тематическим папкам (75 файлов)
Было: 41 файл в корне HISTORY/ + авторские папки OPUS/ и SONNET/ (34 файла).
Стало — тематическая нумерация в стиле NOTES/ (10_, 20_, …):

  10_reviews/    ревью кода и разборы от LLM (2)
  20_releases/   заливки версий в реестр, чистки реестра, нумерация версий (8)
  30_provider/   ядро провайдера: архитектура, модификаторы, UUID, nested (6)
  40_generator/  генератор YAML/спеки, формат MAN (3)
  50_docs/       пайплайн документации, навигация, публикация, хостинг S3 (9)
  60_stands/     стенды и примеры: CRUD, FullPipe, Штурвал, TEST_STAND (7)
  70_infra/      реестр, API Gateway, DDoS-Guard, VPN/213, зеркала (4)
  90_llm/        диалоги и промпты с LLM вне тематики: OPUS/, SONNET/, gemini/ (34)

OPUS/ и SONNET/ перенесены как есть в 90_llm/ — чтобы не рвать пары
«бриф → ответ» внутри диалогов. Все переносы — через git mv (история сохранена).
Перед правкой: TMP/backup_2026-10-02/HISTORY_before_restructure.tar.gz.
Перекрёстные ссылки обновляются следующим коммитом.
2026-10-02 07:35:32 +03:00
Repinoid 9cc7b3f260 docs(history): находки по хостингу документации на S3 static website + версии развития
Новый файл HISTORY/2026-10-02_s3_static_website_docs_hosting.md:
- итог живой проверки: у Nubes включён static website на Ceph RGW (Squid),
  сайт целиком работает из S3 без ВМ; незакрыт только домен/TLS (принадлежит Nubes);
- единственное изменение состояния: PUT Bucket website (IndexDocument=index.html)
  на бакете terraform-registry + команда отката; статус — оставлено, решения владельца нет;
- найдено уже настроенным у Nubes: NoSuchWebsiteConfiguration до PUT (значит API есть),
  wildcard DNS *.s3-website.msk-1.ngcloud.ru -> 89.169.61.167, валидный TLS,
  порт 80 закрыт, bucket policy PublicReadGetObject (Principal *);
- проверки: анонимные 200 с размерами, совпадающими с index.html (148758/139835/150021),
  фактическая раскладка ключей docs/{nubes,nubes-dev,nubes-test}/nubes/…,
  объектный эндпоинт каталоги не умеет (403) — резолвинг только на website-эндпоинте;
- зафиксирована моя ошибка: 403 приходили на НЕСУЩЕСТВУЮЩИЙ ключ docs/nubes/index.html;
  уроки — сначала list-objects-v2, потом URL; сверять размеры и проверять анонимно+подписанно;
- побочные находки: list-buckets видит бакеты других тенантов; в secrets/.s3cfg_registry
  лежат чужие ключи (tazet@narod.ru, ntazetdinov@nubes.ru);
- версии развития V0 (213+nginx) -> V1 (website-эндпоинт, проверена) -> V2 (origin их фронта),
  отклонённые V3a/V3b/V3c и чек-лист дальнейших изменений.
2026-10-02 07:31:58 +03:00
Repinoid 4e65c1e07a docs(release): перезаливка 1.0.0/2.0.0/3.0.0 после фикса unknown password
- VERSIONS.md: новые sha256 всех трёх сборок;
- HISTORY: разбор бага (Computed-атрибут оставался unknown в ветках усыновления),
  что правилось в 64328ab и 93784e4, проверки до заливки, а также зафиксированы
  ошибки исполнителя (правки без разрешения, лишняя перегенерация, неверная
  оценка в первом ревью).
2026-10-01 19:45:46 +03:00
Repinoid 93784e44a7 fix(subresource): диагностика чтения пароля + одна точка заполнения в Create
По итогам код-ревью (коммит 64328ab):
- ResolveUserPasswordFromVault теперь возвращает и текст предупреждения: если пароль
  прочитать не удалось (недоступен API/Vault, пустое имя, нет записи), в выводе apply
  появляется Warning. Раньше поле молча становилось null и причина была невидима.
  Отсутствие секретов у родителя ошибкой не считается (для части сервисов это норма).
- В шаблоне подресурса заполнение вынесено в одно замыкание applyPassword(),
  вызываемое перед каждым resp.State.Set в Create (4 сохранения — 4 вызова).
  Убирает четыре одинаковые строки и снижает риск забыть новую ветку выхода.
- Тест проверяет: замыкание есть, предупреждение есть, и вызовов applyPassword()
  не меньше, чем сохранений состояния в Create.

Проверено: generated/test/go/90_postgres_user_resource.go — 4 State.Set, 4 вызова
(плюс одно упоминание в комментарии); go test ./... ok; go build ./internal/... и
полная сборка провайдера во временной копии с новым generated — чисто.
2026-10-01 19:31:35 +03:00
Repinoid 64328abe55 fix(subresource): password заполняется во ВСЕХ ветках Create, а не только в успешной
Баг (воспроизведён на TEST 2026-10-01): при усыновлении уже существующего
пользователя БД Create выходит по раннему return (ветка 'Подресурс уже существует'
-> State.Set -> return), оставляя Computed-атрибут password в состоянии unknown.
Terraform отказывался: 'Provider returned invalid result object after apply: the
provider still indicated an unknown value for nubes_postgres_user.crud_user_0.password'.

Исправление:
- новая функция resources_core.ResolveUserPasswordFromVault(ctx, client, instanceUID,
  username, current): читает пароль из Vault родителя, иначе возвращает current,
  иначе типизированный null (null для Terraform — конкретное, known значение);
- в шаблоне подресурса password инициализируется null сразу после получения
  instanceUID, а перед КАЖДЫМ resp.State.Set в Create проставляется конкретным
  значением — включая все ветки усыновления;
- регрессионный тест усилен: считает сохранения состояния и вызовы заполнения в
  Create и падает, если хоть в одной ветке password останется unknown.

Проверено: generated/test/go/90_postgres_user_resource.go — 4 State.Set и 4
вызова заполнения; go test ./... ok; go build — чисто.
2026-10-01 19:26:34 +03:00
Repinoid 2c6998975f repo: убран случайный gitlink tfluceecrud
Каталог tfluceecrud был закоммичен как submodule-указатель (mode 160000) без
.gitmodules — из-за этого git считал его 'изменённым' после каждого коммита в
самом репозитории приложения и правило .gitignore:16 на него не действовало.

Теперь структура однородна: tfluceecrud, tfflaskcrud, tfnodejscrud — отдельные
репозитории, лежащие локальными клонами рядом с основным репо и не входящие в
него (все три перечислены в .gitignore, строки 16-18). Файлы на диске не тронуты.

Ветка gitlink'ов apps/iot-* не затронута — они к этой задаче не относятся.
2026-10-01 18:40:32 +03:00
Repinoid d0cbd050fe test_stand/crud(apps): новые домены flask-crud / lucee-crud / nodejs-crud
Прежние домены (tfflask / tflucee / tfnodejs) заняты старыми инстансами:
crud-flask suspended (с зависшей операцией), crud-lucee suspended, crud-nodejs running.
Создание новых сервисов с теми же доменами платформа отвергла бы как
'domain уже используется другим инстансом'.

Домен указан без точек, поэтому A-запись создаётся автоматически в служебной
DNS-зоне ресурсной платформы.

terraform validate — Success.
2026-10-01 18:39:29 +03:00
Repinoid 03b368a55a test_stand/crud(apps): новые имена ресурсов + SERVICE_NAME для колонки created_by
- locals.tf: crud-flask → flask-crud, crud-lucee → lucee-crud, crud-nodejs → nodejs-crud;
- json_env: SERVICE_NAME = flask/lucee/nodejs (без -crud) — это значение пишется
  приложением в колонку created_by таблицы crud_items;
- terraform validate — Success.

Код приложений (DDL created_at/created_by + INSERT + UI) — в репозиториях
tfflaskcrud/tfnodejscrud/tfluceecrud (origin переключён на terraform/*):
e5fce97, 23338cb, ffcd0c0.
2026-10-01 18:37:44 +03:00
Repinoid cc8f338969 docs(release): перезаливка 1.0.0/2.0.0/3.0.0 с выходом password у подресурсов
- старые x.0.0 удалены физически из реестра по команде владельца, затем залиты заново
  теми же номерами; sha256 всех трёх сверен с локальными сборками;
- VERSIONS.md: новые sha256 + пометка об удалении версий;
- HISTORY: зачем, что удалено, схема залитой сборки (password — новый выход у
  postgres_user/kafka_user/clickhouse_user/mongodb_user; у mariadb_user и pgadmin
  это прежние входные параметры), проверка стенда, состояние state.
2026-10-01 17:31:26 +03:00
Repinoid 066d6b472b test_stand/crud(pg): пароль берётся из выхода подресурса, а не из vault_secrets кластера
output pg_password = nubes_postgres_user.crud_user_0.password — значение известно в том
же apply, где создан пользователь, поэтому ни try(), ни двух apply в pg/ больше не
требуется. Обновлён комментарий про порядок вычисления.
2026-10-01 17:15:20 +03:00
Repinoid 58c519e712 gen(subresource): выход password для авто-генерируемого пароля пользователя
Проблема: у части сервисов пароль пользователя генерирует платформа и кладёт его в
секрет Vault РОДИТЕЛЬСКОГО инстанса. vault_secrets родителя — Computed и обновляется
только при его Read, поэтому внутри одного apply после create_user пароль недоступен
(Invalid index). Из-за этого в pg/outputs.tf приходилось читать vault_secrets кластера,
а стенду требовались два apply.

Решение: подресурс-пользователь отдаёт пароль СВОИМ выходом сразу после create_user.
- types.go: GenSubresource.VaultUserPassword (признак из данных спека);
- loader.go: признак = подресурс user + у сервиса есть vault-выходы + create_user
  принимает username и НЕ принимает password; имя сервиса нигде не проверяется;
- templates/subresource.go: поле модели + Computed/Sensitive атрибут password,
  чтение Vault родителя в Create (GetInstanceStateDetails + GetInstanceVaultSecrets)
  и перенос уже полученного пароля в Update (чтобы Computed-атрибут не стал unknown);
- resources_core/subresource_user_password.go: ExtractUserPassword — разбор
  {"<username>":{"password":"..."}} с безопасным возвратом пустой строки;
- writers: регрессионный тест «фича включена/выключена».

Проверено генерацией и сборкой test-стенда: выход получили 4 подресурса
(postgres_user, kafka_user, clickhouse_user, mongodb_user); mariadb_user НЕ затронут
(там пароль входной); k8s_*_user и vc_org_user не затронуты (пользователь
адресуется не через username). go test ./... — ok, go build — чисто.
2026-10-01 17:15:14 +03:00
Repinoid 1a049efa44 docs(arch): принцип секретов подресурсов — в TOOLS/ARCHITECTURE.md
Записал в ОБЩИЙ файл архитектуры (TOOLS/ARCHITECTURE.md, PRIMARY SOURCE OF TRUTH):
- раздел «Subresource-born secrets» в Subresource Resources — проблема, принцип,
  правило «service-specific DATA, never logic», факт postgres vs mariadb;
- пункт 3 в Exception Registry — как объявлять признак в YAML-спеке и прокидывать
  через types.go + loader.go, без svc.Name == "..." в шаблоне.

В provider_philosophy.md оставлена короткая ссылка на ARCHITECTURE.md (источник один).
2026-10-01 17:08:30 +03:00
Repinoid 57e7d4d077 docs(arch): принцип секретов подресурсов + чем postgres отличается от mariadb
В раздел 6 (Подресурсы) добавлен подраздел про секреты, рождаемые операцией
подресурса:
- проблема: vault_secrets родителя — Computed, обновляется только в Read, поэтому
  после create_user пароль недоступен в том же apply (Invalid index);
- принцип: пароль должен быть выходом самого подресурса, не читаться из родителя;
- как привязано к данным, а не хардкодом: признак в YAML-спеке (по аналогии с
  suspend_on_destroy_default), условный блок в общем шаблоне;
- факт из спеков: postgres = пароль генерит платформа (нужен выход), mariadb =
  пароль задаёт пользователь на входе (выход не нужен).
2026-10-01 17:06:52 +03:00
Repinoid ed92de1544 test_stand/crud: из README и комментариев убрана вся история (try, «раньше»)
README читает человек впервые — ему нужны структура, шаги запуска и операции,
а не разбор прошлых костылей. Удалено:
- раздел «Почему нельзя создать всё одним apply» с историей про try() и PGPASSWORD;
- упоминания try() в apps/lucee.tf, apps/flask.tf, apps/nodejs.tf;
- «data-source/backend» из apps/locals.tf и путь к удалённому разделу из pg/outputs.tf.

Заодно удалён старый TEST_STAND/CRUD/terraform.tfvars (не читается, значения
перенесены в pg/terraform.tfvars и apps/terraform.tfvars).

Проверено: terraform validate — Success в pg/ и apps/ (apps — с временным creds.json).
2026-10-01 16:42:46 +03:00
Repinoid 4834e992f3 test_stand/crud: разделение на pg/ (общая БД) и apps/ (потребители)
Было: одна папка, один state — destroy убивал и БД, и приложения; пароль БД
приходилось вытаскивать костылём try() в одном apply, приложения поднимались
с пустым PGPASSWORD.

Стало:
- pg/  — кластер PostgreSQL + пользователь + база + outputs (хост/порт/юзер/база/пароль);
  destroy здесь переводит кластер в Suspend, а пользователь и база не удаляются
  (keep_on_destroy = true + adopt_existing_on_create = true);
- apps/ — три приложения-потребителя; креды БД читаются из creds.json (выгрузка
  outputs папки pg/, т.к. state раздельные, а data-source у провайдера нет);
  try() убран полностью — пароль к моменту этого apply уже существует;
- apps/.gitignore — creds.json, terraform.tfvars, state/lock;
- README.md переписан: структура, «зачем разделено», пошаговые 3 шага запуска,
  повседневные операции (destroy приложений не трогает БД), про destroy базы.

Проверено: terraform init+validate в обеих папках — Success;
plan в pg/ — 3 to add (кластер, пользователь, база) + 6 outputs.
2026-10-01 16:35:14 +03:00
Repinoid db69965520 docs(release): перезаливка 1.0.0/2.0.0/3.0.0 с keep_on_destroy + разбор грабель
- VERSIONS.md: три строки обновлены, добавлены первые 16 символов sha256 каждой сборки;
- HISTORY: что заливалось, как проверялось (схема 60/62 ресурсов, исключения —
  action-шаблон mongodb_rollback и рукописный service_operation), грабли
  checksum mismatch при той же версии + лечение, и найденный факт: state
  TEST_STAND/CRUD пуст (serial 38), инстансы в облаке помечены deleted.
2026-10-01 16:08:53 +03:00
Repinoid e8c03d8ded test_stand/crud: keep_on_destroy=true для пользователя и базы PostgreSQL
Инстанс PG при destroy уходит в Suspend (suspend_on_destroy_default: true в
90_postgres.yaml), а не удаляется. Без keep_on_destroy подресурсы (create_user/
create_database) удалялись бы из живого кластера — после resume приложения
работали бы с пустой БД, данные базы были бы потеряны.

keep_on_destroy=true → режим state_only: при destroy подресурс остаётся в облаке
и только убирается из state. В паре с adopt_existing_on_create=true следующий
apply усыновляет существующий объект, а не падает на 'уже существует'.

Проверено: terraform validate — Success (провайдером 3.0.0, где атрибут есть).
2026-10-01 16:08:30 +03:00
Repinoid 8f6e0965e9 test(resource-generator): регрессионный тест keep_on_destroy для подресурсов
Проверяет три части фичи в сгенерированном коде:
1) поле модели KeepOnDestroy с тэгом tfsdk:keep_on_destroy;
2) атрибут схемы Optional + Default=false (поведение по умолчанию не меняется);
3) в Delete проверка флага идёт РАНЬШЕ вызова операции удаления — иначе destroy
   всё равно удалял бы объект в облаке.

Вывод генератора перед сравнением нормализуется по пробелам: gofmt выравнивает
поля структур и ключи map, из-за чего поиск подстроки «как в шаблоне» не работает.

Запуск: cd TOOLS/resource-generator && go test ./internal/writers/... — ok.
2026-10-01 15:46:13 +03:00
Repinoid 81b85a4dea docs-generator: keep_on_destroy в документации (подресурсы + инстансы)
- у подресурсов появился раздел «Поведение при destroy» с таблицей флагов
  (keep_on_destroy / skip_missing_on_delete / adopt_existing_on_create) и предупреждением
  про расхождение state и облака;
- в блоке destroy у инстансовых ресурсов добавлен keep_on_destroy (раньше не документировался
  вообще ни на одной странице);
- в types добавлено поле Lifecycle.KeepOnDestroyDefault.

Проверено генерацией: keep_on_destroy упоминается на 49 страницах test-стенда,
включая postgres_user.md (подресурс) и postgres_params_create.md (инстанс).
2026-10-01 15:45:15 +03:00
Repinoid fefc2006b1 gen(subresource): keep_on_destroy — подресурс при destroy остаётся в облаке
Подресурсы (nubes_postgres_user/database и остальные 20) удалялись всегда, даже когда
родительский инстанс при destroy только приостанавливается. Из-за этого пользователь БД
удалялся, а следующий apply создавал его заново с НОВЫМ паролем.

Добавлен атрибут keep_on_destroy (как у инстансовых ресурсов, Default=false):
- поле KeepOnDestroy в модели;
- атрибут схемы (Optional+Computed, Default=false);
- ранний выход в Delete с предупреждением (режим state_only).

Проверено: 22 подресурса получили атрибут; сборка провайдера с перегенерённым кодом — BUILD_OK.
Дефолт false → поведение существующих конфигураций не меняется.
2026-10-01 15:44:14 +03:00
Repinoid 00bfe7a3ad docs(curated): страница CRUD-стенда + ссылки на git-репозитории приложений
Новая страница docs/curated/crud/three_apps.md (раздел «Проверенные примеры» в mkdocs.yml):
- что создаётся (6 ресурсов: PG + user + db + Lucee + Flask + Node.js);
- ссылки на git: gitea.services.ngcloud.ru/terraform/{tfluceecrud,tfflaskcrud,tfnodejscrud}
  (проверено HTTP 200), в манифестах — lucee_git_path/flask_git_path/nodejs_git_path;
- запуск: два apply и почему (пароль появляется только после create_user);
- доступ к БД: vault_secrets["users"] -> { "<username>": { "password" } }, и ловушка
  state_out.users (метаданные без пароля);
- особенности: adopt_existing_on_create, уникальные домены, camelCase в postgres_conf, realm;
- диагностика: ошибку смотреть в stages операции, а не в errorLog.

Проверено локальной сборкой mkdocs: страница собирается, битых ссылок нет,
все три ссылки на репозитории присутствуют в HTML.
2026-10-01 14:28:50 +03:00
Repinoid 34abade75d backup: FPipeGmail lock/vm (бэкапы от 2026-09-30) 2026-10-01 14:24:19 +03:00
Repinoid e6d2dcc2a1 stand(PROD): добавить FPipeGmail (полный pipe: vdc/edge/shturval/vm + modifiers)
terraформ-манифесты прод-стенда FPipeGmail. terraform.tfvars игнорируется (.gitignore *.tfvars),
токен не коммитится.
2026-10-01 14:24:19 +03:00
Repinoid c2c870bc34 stand(TEST/CRUD): terraform fmt — выравнивание access_configuration в postgres.tf 2026-10-01 14:24:19 +03:00
Repinoid 0353ff79e5 stand(TEST/CRUD): пароль БД через try() — первый apply больше не падает
Раньше locals читали пароль напрямую: jsondecode(vault_secrets["users"]).user4crudpg.password.
При первом apply пароля ещё нет (create_user выполняется после создания кластера), поэтому
Terraform падал с «Invalid index ... does not identify an element in this collection value».

Теперь через try(... = ""): на первом apply в env идёт пустая строка и прогон не роняет;
на втором apply пароль уже в Vault, try возвращает настоящее значение, приложения получают верный env.

Комментарии в lucee.tf/flask.tf/nodejs.tf объясняют зачем try и почему два apply.
Проверено: terraform validate — Success; plan — No changes; Exit 0.
2026-10-01 14:23:11 +03:00
Repinoid 61405dd3dd docs: пароль БД через vault_secrets["users"] — задокументировано, чтобы не искать
Проверено по API 2026-10-01 (pg4crud2, TEST):
GET /instances/<uid>/vault/users -> {"users":{"user4crudpg":{"password":"..."}}}.
Пароль ЕСТЬ; state.out.users — метаданные без пароля (их легко перепутать).

- README.md: строка навигации «Пароль БД / секреты Vault» + раздел «Грабли, на которые уже наступали»
  (Invalid index из-за одного apply; errorLog врёт — смотреть stages; лишний sensitive).
- docs/curated/postgres/pg_user_db.md: раздел «Пароль пользователя БД и секреты Vault» (ловушки,
  два apply, диагностика через /instanceOperations?fields=stages); исправлено утверждение «все 4 ресурса
  за один apply» — для приложений, читающих пароль, нужен второй apply.
- docs/30_registry/guides/getting-started.md: помечены устаревшие ключи adminUser/adminPass (сейчас 404).
- HISTORY/2026-10-01_...: дополнение с фактами и указанием, что первый разбор ошибся.
2026-10-01 14:06:06 +03:00
Repinoid 9c1acabb4e docs(HISTORY): разбор сбоев создания PG в TEST CRUD — только проверенные факты
Зафиксировано по журналу операции 181D3B1D (GET /instanceOperations/<uid>?fields=stages):
падение на шаге «1. Валидация» → «Проверка ёмкости рес платформы»:
«Невозможно развернуть приложение в данной ресурсной платформе».
errorLog «Invalid JSON String» сути не отражает.

Отдельно отмечено, что не доказано (snake_case в postgres_conf, причины операций
4D60A0B6 и 5AAF1E39 — их stages недоступны, API отдаёт 404) и что моё утверждение
в коммите e3182c3 было предположением, выданным за факт.
2026-10-01 12:36:28 +03:00
Repinoid e3182c3f98 stand(TEST/CRUD): postgres_conf — ключи camelCase и значение ''
Провайдер отправляет postgres_conf на платформу как есть (resources_core.FormatString
возвращает строку без перевода имён), поэтому ключи обязаны совпадать со спекой:
postgresConf.paramName / postgresConf.paramValue. С snake_case (param_name/param_value)
платформа падала с "Invalid JSON String" (операция 5AAF1E39-8812-4994-AD14-78D51ED2077D).

Значение paramValue взято '' — как в HAR/pgmodify.har и в TEST_STAND/PG/resources.tf.
План проходит: 6 to add, exit 0.
2026-10-01 12:28:52 +03:00
Repinoid 7f0931d035 stand: снять sensitive с realm и s3_uid — это не секреты
Из-за sensitive = true в plan вместо значений печаталось "(sensitive value)".
Убрано в: DEV_STAND/CRUD, DEV_STAND/POSTGRES, TEST_STAND/POSTGRES, TEST_STAND/PGwNewRegistry.
api_token остаётся sensitive везде — он действительно секрет.
2026-10-01 12:22:32 +03:00
Repinoid 4b31eca807 stand(TEST/CRUD): var.realm больше не sensitive
realm — не секрет, но из-за sensitive = true в plan вместо имени печаталось
"(sensitive value)". Теперь видно resource_realm = "k8s-3-sandbox-nubes-ru".
Плана это не меняло: plan по-прежнему 6 to add, exit 0.

Прочие sensitive оставлены как есть: api_token (секрет), vault_secrets (платформа).
2026-10-01 12:20:23 +03:00
Repinoid 5e0df46dc8 stand(DEV/CRUD): привести к варианту TEST — adopt_existing_on_create, без git_revision
- lucee/flask/nodejs: добавлен adopt_existing_on_create = true (усыновлять существующий
  инстанс вместо падения с «ресурс с таким именем уже существует»)
- удалены git_revision и одноимённые locals (в TEST их нет)
- git_path: Nail -> terraform (проверено по HTTP: /Nail/<repo> отдаёт 301 на /terraform/<repo>)
- terraform.tfvars.example: комментарий s3_user_uid = UUID S3-пользователя

Имена ресурсов и домены оставлены dev-своими (уникальными). realm dev = k8s-3-sandbox-nubes-ru.

Проверка API dev-стенда (/instances?isDeleted=false): 9 инстансов, ни одного под сервисы
89 flask / 94 lucee / 95 nodejs и PostgreSQL; поиск crud|pg4|lucee|flask|nodejs -> 0. Коллизий нет.

НЕ исправлено (не моя правка, есть и в HEAD): terraform validate падает на nubes_nodejs.json_env —
в спеке dev-стенда у сервиса 95 объявлен обязательный sub_param DB_PASS, поэтому схема требует
объект { db_pass }, а конфигурация передаёт строку jsonencode().
2026-10-01 10:45:15 +03:00
Repinoid 5faf33c43d chore(test-stand): закомментировать vm.tf — vApp (26) и ВМ (28) исключены из стенда
vm.tf целиком обёрнут в блочный комментарий /* … */ со пояснением причины:
в test инстанс vApp не прошёл валидацию схемы (нужен reconcile). Вернуть —
удалить обрамляющие строки.

Проверено: terraform fmt -check OK, validate Success,
plan = 1 to add (только Штурвал); инстансов vApp/ВМ стенда в тенанте нет (все deleted).
2026-10-01 09:52:13 +03:00
Repinoid 4c3039146f fix(test-stand): поднять квоту внешних IP 10 -> 12 + документация провалов apply
Первый apply в test: vdc/edge/org_ip/snat создались; упали:
- Штурвал: 'свободных Ip в тенанте WZ03709-iaas: 1, необходимо 2' — на NaeelOrg
  было count=10, часть адресов держит кластер iot-naeel. Поднято до 12.
- vApp: 'не удалось валидировать схему инстанса' -> not created (вероятно плавающая,
  при повторе — reconcile).

После destroy (владелец): fullpipe-vdc = suspended (suspend_on_destroy),
fullpipe-edge = running (keep_on_destroy), локальный state пуст.

Порядок: apply -target=nubes_vc_org_ip_allocation.org_ip -> полный apply.
HISTORY/2026-10-01_test_stand_fpipegmail.md дополнен разделом с ошибками и решением.
2026-10-01 08:45:50 +03:00
Repinoid f7d6eb4688 feat(test-stand): TEST_STAND/FPipeGmail — копия dev-примера под test-стенд
Создана папка (без .terraform/state/lock — это состояние dev). Изменены параметры:
- versions.tf: nubes-dev/nubes 2.0.1 -> nubes-test/nubes 3.0.0;
- variables.tf: api_endpoint -> lk-api-gateway-test.ngcloud.ru;
- terraform.tfvars: organization=NaeelOrg (найдена через API, svc 19), ip_count=10
  (в test на орге уже выделено 10 IP), storage SSD (в test vDC строят на SSD);
- shturval.tf: shturval-test1 / shturval-test-01 / workers-shturval-test.

Коллизии проверены: в test заняты naeel-vdc, 'VDC для кластера iot-naeel',
naeel_vc_nsxt, 'Edge для кластера IOT naeel', 'Кластер Kubernetes [iot-naeel]' —
наши имена (fullpipe-vdc/-edge/-vapp-02, web02, shturval-test1) свободны.

Проверено: terraform init (3.0.0), fmt -check OK, validate Success,
plan = 7 to add / 0 change / 0 destroy. apply НЕ запускался.

Документация: HISTORY/2026-10-01_test_stand_fpipegmail.md
2026-10-01 08:21:08 +03:00
Repinoid 8eae7f383d release: пересобраны и перезалиты X.0.0 по всем стендам (prod 1.0.0, dev 2.0.0, test 3.0.0)
Полный цикл 03 (01 YAML -> 02 Go/доки -> сборка 3 платформ -> GPG -> S3) по каждому стенду
начиная с YAML. Профиль dev: VERSION 2.0.1 -> 2.0.0 (db9d93e).

Проверено: sha256 залитого linux-бинарника == локальной сборке на всех трёх
(prod 9c20e0a7, test 9cdfa8b8, dev 86c667f7); по 5 объектов на версию; YAML 36/36/40.

Отличие от заливки 30.09: теперь в сборки вошли 8 коммитов правок ядра/генераторов
(core, resources_core, resource-generator, check_schema_names + вызов в 03).
Документация: HISTORY/2026-10-01_release_x_0_0_all_stands.md, VERSIONS.md.
2026-10-01 08:12:13 +03:00
Repinoid bf1b083775 docs(history): полное зеркалирование ~/TF на сервер 213
rsync -a --delete локально → vps:~/TF/: передано 4 430 файлов (341.7 МБ),
создано 3 987, удалено 139 объектов, 4.9 МБ/с, ~1 мин.
Было на 213: HEAD 33672e05 (19.09), 19 незакоммиченных файлов, 3 stash.
Стало: HEAD db9d93e (= локальный), 6 веток, 2 stash, 8 808 файлов = 8 808,
diff списков файлов с LC_ALL=C — 0 строк.
Слепок не делался — по указанию владельца; удалённые черновики 20.09 и stash перечислены.
2026-10-01 08:11:01 +03:00
Repinoid db9d93e1d0 chore(dev): VERSION 2.0.1 -> 2.0.0 перед перезаливкой X.0.0 2026-10-01 07:37:22 +03:00
Repinoid 058d0a6991 docs(history): ошибка операции Edge — сбой DNS платформы при init OpenTofu backend
jlib.tofu не смог зарезолвить provider vmware/vcd через terraform-mirror.yandexcloud.net:
'internal DNS 169.254.25.10:53: server misbehaving' -> 'Edge не удалось найти в Cloud Director'
(операция не выполнилась). Проверено извне: зеркало доступно (DNS резолвится, index.json HTTP 200,
0.88s) => проблема во внутреннем DNS/сети платформы, не в зеркале.
2026-09-30 22:12:46 +03:00
Repinoid 6d8383dbca docs(history): ALB на Edge не отключается из-за оставшихся NSX-T LB Pools
Ошибка модификации Edge nsx_WZ03709-saas-tbxiw8kt: FORBIDDEN 'Cannot disable load
balancer ... since there are Pools'. Пулы остались от неудалённых CAPvcd/Штурвала;
пока они есть — ALB не отключить и Edge не удалить. Клиентских путей нет,
нужна платформа (заявка расширена: CAPvcd + LB Pools + затем Edge).
2026-09-30 22:06:54 +03:00
Repinoid 0a6b39ad45 docs(history): повторный delete Штурвала shturval-dev1 падает с NPE бэкенда
Владельцу не удаётся удалить Edge: платформа блокирует из-за инстансов CAPvcd
(shturval-dev-01), хотя кластер удалён из ЛК 25.09.

Установлено (API dev): shturval-dev1 (svc 150, uid 05ae1dbc-…) — deleted, операций нет;
тенанта WZ03709-saas в dev нет (единственная организация — kontra).

Попытка доудаления (по указанию владельца):
POST /instanceOperations (svcOperationId=109, delete) -> 201, run -> 201,
результат: isSuccessful=false, errorLog="Cannot invoke \"String.length()\" because
\"text\" is null" — NPE бэкенда. Остатки CAPvcd не вычищены.

Вывод: только техподдержка (MAN vc_org прямо это предписывает).
2026-09-30 22:02:05 +03:00