364 Commits
Author SHA1 Message Date
Repinoid e72eb75d10 stand(FPipeGmail): свои имена Штурвала — shturval-dev1 / shturval-dev-01 (занятое имя кластера не подошло) 2026-09-25 13:31:33 +03:00
Repinoid 8d25de9e79 docs: страница «Как работает провайдер (отличия от Terraform)» в навигации + ссылка на страницу пайплайна vDC → Edge → IP → SNAT → Штурвал 2026-09-25 09:59:56 +03:00
Repinoid f8d64948c8 docs(curated): страница «vDC → Edge → IP → SNAT → Штурвал» (требования, заморозка, проверка результата) + nav + HISTORY 2026-09-25 09:47:33 +03:00
Repinoid e20c22e0cb stand(FPipeGmail): организация kontra (аккаунт tazetdinovn@gmail.com) вместо устаревшей kontora 2026-09-25 08:53:33 +03:00
Nail dbaadccc50 docs(resume): подробное резюме состояния Штурвал/freeze для нового чата + стенд DEV_STAND/FPipeGmail 2026-09-25 08:05:12 +03:00
Nail c808a3b345 docs: повышение читаемости provider-behavior.md (упрощены §1 списком, §2 модификаторы, §3 id/нормализация, §5 приоритет флагов, §6 ALB-константы) 2026-09-24 20:53:17 +03:00
Nail aaf87d966b docs: страница «Как работает провайдер: отличия от канонического Terraform» (freeze/destroy, пайплайн vDC→Edge→IP→SNAT→Штурвал, FAQ) + кейс UUID внутри JSON в case-sensitivity документе 2026-09-24 20:40:18 +03:00
Repinoid 10520670a5 1 2026-09-24 20:27:54 +03:00
Nail 208d97e2ce docs+release(dev): 2.0.23 — аудит регистра UUID (8 мест), фикс внутри JSON, залито в реестр 2026-09-24 20:22:39 +03:00
Nail 03fff05117 docs(gitignore/embed): operation_timeouts.json — исходник, а не артефакт; профильные значения подменяются при релизе 2026-09-24 20:22:11 +03:00
Nail 621280a530 fix(uuid-case): нормализация UUID внутри JSON — adopt suspended-инстанса больше не падает на регистре (jsonutil + JsonNormalize), тесты 2026-09-24 20:17:32 +03:00
Nail c9d73450b0 docs: проверен цикл destroy=заморозка на живом стенде (5 destroyed, кластер/vDC suspend, эдж/SNAT/квота не тронуты) 2026-09-24 19:44:26 +03:00
Nail 5fd64b68d0 gitignore: TMP/devbin и terraform-provider-nubes — локально собранные бинарники не в git 2026-09-24 19:24:49 +03:00
Nail 4bdf03a531 tmp: бэкапы файлов перед правками заморозки + terraformrc для dev_overrides 2026-09-24 19:23:39 +03:00
Nail eaff056d9c stand(FullPipe): провайдер переведён на 2.0.22 2026-09-24 19:23:39 +03:00
Nail 77de8cece6 docs: стенд FullPipe переведён на 2.0.22, plan без изменений, флаги заморозки зафиксированы в state 2026-09-24 19:22:37 +03:00
Nail cab606b90e docs: релиз dev-провайдера 2.0.22 зафиксирован (залит в реестр, VERSIONS.md обновлён) 2026-09-24 19:14:53 +03:00
Nail c29df2173f release(dev): 2.0.22 — keep_on_destroy (state_only) для всех instance-ресурсов + предупреждения в Delete 2026-09-24 19:14:39 +03:00
Nail ba3887fa69 docs: фиксирую реализацию freeze-on-destroy (генератор 22c6c83, конфиг стенда 40aef87) и порядок проверки через dev_overrides 2026-09-24 19:07:06 +03:00
Nail 40aef879e4 stand(FullPipe): режим «заморозки» на destroy — keep_on_destroy=true (эдж/SNAT/квота IP), adopt для эджа, явный suspend_on_destroy для кластера 2026-09-24 19:06:46 +03:00
Nail 22c6c83a0f generator: третий режим destroy keep_on_destroy (state_only) для всех instance-ресурсов + предупреждения «заморожен/оставлен как есть» 2026-09-24 19:06:40 +03:00
Nail 0de72e09f0 docs(history): запись за 2026-09-24 — adopt для кластера Штурвал, диагностика dev-00 и направление freeze-on-destroy 2026-09-24 19:00:12 +03:00
Nail 3df93ad07f docs(notes): диагностика Штурвал dev-00 (44/48 подов, мусор init-job) + разбор ошибки destroy по квоте IP и дизайн freeze-on-destroy через генератор 2026-09-24 18:59:53 +03:00
Nail 57abb7bfa4 stand(FullPipe): adopt_existing_on_create=true для кластера Штурвал — apply усыновляет существующий инстанс shturval-dev вместо ошибки «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)» 2026-09-24 18:18:57 +03:00
Nail 72d5771ffc stand(FullPipe): worker_configuration в camelCase (groupName/sizingPolicy/sizingDisk/labelDeck) — платформа падала на split() on null 2026-09-24 17:15:56 +03:00
Nail d98f6036d1 stand(FullPipe): добавлен Kubernetes кластер Штурвал — сервис 150 (nubes_k8s_sthutrval_cluster, vdc_uid+nsxt_uid), operation_timeout 60m 2026-09-24 16:51:24 +03:00
Nail 1a90c7737d stand(FullPipe): удалён Штурвал из конфига (был добавлен неверный сервис 148 вместо 150) 2026-09-24 16:45:18 +03:00
Nail b8adeb6582 stand(FullPipe): всё про Штурвал собрано в shturval.tf (переменные + ресурс); в variables.tf и terraform.tfvars ничего про Штурвал не осталось 2026-09-24 16:35:57 +03:00
Nail 5a2a5e7487 stand(FullPipe): добавлен Штурвал (nubes_vc_mgmt_sthutrval_cluster, 148) в конец цепочки vDC -> Edge -> IP -> SNAT 2026-09-24 16:27:57 +03:00
Nail 1611f7afa8 docs(curated): страницы примеров приведены к реальным файлам (versions/provider/variables/outputs), организация по имени, снята пометка «не проверено» 2026-09-24 16:15:15 +03:00
Repinoid 418b5645e5 docs: DEV 2.0.21 залит — ресурс аллокации принимает имя организации; стенд переведён на 2.0.21 2026-09-24 15:35:18 +03:00
Repinoid 5bd197f031 feat(provider): ресурс аллокации принимает имя организации (резолв в UUID через ResolveRefSvcParamValue, как в nubes_vc_vdc); конфиги и доки без org_uid 2026-09-24 15:28:13 +03:00
Repinoid bd5de0cead docs(pipeline): страница переписана как инструкция для пользователя (шаги, значения из ЛК, команды) 2026-09-24 15:21:41 +03:00
Repinoid c3b82cf074 docs(pipeline): ссылка на репозиторий примеров tf_examples и порядок клонирования 2026-09-24 15:00:07 +03:00
Repinoid 9590005914 docs: страница пайплайна vDC → Edge → внешние IP → SNAT (Ресурсы-модификаторы (IP организации, SNAT)) 2026-09-24 14:56:00 +03:00
Repinoid caa55d9ff8 chore(stand/FullPipe): провайдер 2.0.20 (проверено plan из реестра) 2026-09-24 14:16:05 +03:00
Repinoid d76418303a docs: DEV 2.0.20 залит (nubes-dev) — исправлен plan-modifier в nubes_vc_org_ip_allocation 2026-09-24 14:11:33 +03:00
Repinoid 6196a0119a docs(rules): добавить правило — при неясной команде переспросить и подтвердить, не гадать 2026-09-24 14:05:06 +03:00
Repinoid e25ef02a1a docs: убрать устаревшее «канонизация в plan-modifier» (совет Opus был неверен); план живого прогона FullPipe 2026-09-24 14:04:08 +03:00
Repinoid 807dfde287 fix(provider): убран plan-modifier, менявший пользовательское значение (Terraform: planned value must match config); сравнение аллокаций — смысловое в Read 2026-09-24 13:57:56 +03:00
Repinoid 721c3fcfab feat(stand/FullPipe): орг organ (org_uid) + ресурсы-модификаторы — аллокация IP после эджа, затем SNAT 2026-09-24 13:48:53 +03:00
Repinoid e6675be906 docs: DEV 2.0.19 залит (nubes-dev) — правки по ревью ресурсов-модификаторов 2026-09-24 10:57:10 +03:00
Repinoid ed4493c0ee docs: правки по ревью (канонизация, destroy-семантика) + статус выполнения 2026-09-24 10:52:51 +03:00
Repinoid 1236c59e18 test(provider): тесты канонизации vIPConfigure (jsonencode-форма, пробелы, [{}], невалидный JSON) 2026-09-24 10:52:36 +03:00
Repinoid 4b497e61db fix(provider): nsxt_snat — не писать null в Required-атрибут, ошибки API в Delete → error, валидация пустого ip_space_name 2026-09-24 10:52:36 +03:00
Repinoid ba6c4f5122 fix(provider): канонизирующий plan-modifier для vip_configure (jsonencode сортирует ключи → вечный diff); не писать null в Required; Delete: ошибки API → error 2026-09-24 10:52:36 +03:00
Repinoid 3374bf4e08 docs(prompts): ответ Opus на ревью кода ресурсов-модификаторов (блокеры: порядок ключей, Required+null) + список правок 2026-09-24 10:51:10 +03:00
Repinoid 648db99628 docs(prompts): промпт на ревью Opus — полный код двух ресурсов-модификаторов, известный баг и вопросы 2026-09-24 10:49:15 +03:00
Repinoid 9412106e3f docs: DEV 2.0.18 залит (nubes-dev) — ресурсы-модификаторы vc_org_ip_allocation и vc_nsxt_snat 2026-09-24 10:34:02 +03:00
Repinoid 6e6d223c22 docs(plans): §13 — статус работ (сделано/ждёт команды) 2026-09-24 10:26:36 +03:00
Repinoid 62abcd64f5 docs(providers): страница ресурсов-модификаторов (nubes_vc_org_ip_allocation, nubes_vc_nsxt_snat) + nav 2026-09-24 10:26:21 +03:00
Repinoid 3973f912fc chore(docs): удалить docs/TODO/what_not_in_terraform.md + убрать ссылки на него из комментариев 2026-09-24 10:24:13 +03:00
Repinoid 73a7459a38 feat(provider): регистрация ресурсов-модификаторов vc_org_ip_allocation и vc_nsxt_snat 2026-09-24 10:22:17 +03:00
Repinoid 80d82a145a feat(provider): ресурс nubes_vc_nsxt_snat (modify ipSpaceName, inverse no-needed) 2026-09-24 10:22:17 +03:00
Repinoid 22cf2595ee feat(provider): ресурс nubes_vc_org_ip_allocation (modify vIPConfigure, uid орги) + тесты нормализации 2026-09-24 10:22:17 +03:00
Repinoid 574e300476 docs(plans): §12 — орга делается руками в ЛК, в tf только uid; правка генератора не блокер, нужны только 2 ресурса 2026-09-24 10:10:55 +03:00
Repinoid cb8389c17f docs(plans): §11 — ответы Opus раунд 3 (критерий отбора = явный список в конфиге генератора, релиз A только (б), Deprecated вместо падения) 2026-09-24 10:04:25 +03:00
Repinoid 664f04eb49 docs(plans): §10 — вопрос Опусу про безопасность универсальной правки графа генератора (5 сервисов с modify-only) 2026-09-24 10:00:57 +03:00
Repinoid 602b27ee1a docs(plans): ревью Opus по плану — §9 (ответы на 5 вопросов, count строкой, обязательный follow-up по генератору) 2026-09-24 09:55:02 +03:00
Repinoid 97d5ca818e docs(plans): план двух ресурсов-модификаторов (nubes_vc_org_ip_allocation, nubes_vc_nsxt_snat) + вопросы на ревью 2026-09-24 09:32:55 +03:00
Repinoid bccf8f7320 docs(notes): раунд 2 Q&A с Opus (владелец параметра, массив vs элемент, keep_on_destroy, deprecated-переход, тип атрибута) 2026-09-24 09:31:10 +03:00
Repinoid 129dab97a0 docs(notes): Q&A с Opus по дизайну ресурсов-модификаторов + замечания к ответам 2026-09-24 09:29:22 +03:00
Repinoid 75700a92da docs(notes): исправлен ложный факт «схема только из create» в CHAT_RESUME_IAC (loader.go:96 мержит create+modify) 2026-09-24 08:48:54 +03:00
Repinoid 51ff9b3751 docs(notes): разбор fresh-create HAR — state после create (vIPConfigure=[{}], ipSpaceName только в modify) 2026-09-24 08:48:54 +03:00
Repinoid f7fffb9ed7 refactor: разложить рабочие материалы по NOTES/ и HOW_TO/, корневой README — карта проекта
- NOTES/: 10_plans, 20_prompts, 30_analysis, 40_chat_summaries, 60_reference + README в каждой папке
- HOW_TO/: все общие инструкции (сборка/заливка, DevOps-ранбук, добавление сервиса, миграция, генерация доков) + индекс «что нужно -> какой файл»
- новый README.md: карта проекта, пайплайн, стенды, реестр, запреты/грабли
- HOWTO-UPLOAD.md: исправлена легаси-схема версий (prod=1.*, dev=2.*, test=3.*)
- DEVOPS_BUILD_PIPELINE.md: пути скриптов -> TOOLS/scripts, universal_rebuild/main.go -> provider/main.go
- howitwasdone.md / MIGRATION_PLAN_FOR_AGENT.md: пометки о соответствии старых путей
- внутри перенесённых файлов обновлены ссылки на новые пути
2026-09-24 07:51:38 +03:00
Repinoid 2d8e435dd4 docs: пометить отменённый заход модификаторов как LEGACY + исправить ложные факты
- баннеры «ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО» на 4 файла HISTORY/OPUS/2026-09-22_modifier_* и docs/60_strategy/modifier_resources_ideology_and_specification.md
- vIPConfigure: replace-семантика, НЕ накопительная (по тесту docs/ORG_IP_MODIFIER_TEST_2026-09-22.md)
- обновлены ссылки на перенесённые материалы (docs/... -> NOTES/..., HOW_TO/...)
2026-09-24 07:51:25 +03:00
Repinoid d93ff66482 chore: save current changes 2026-09-24 07:25:27 +03:00
Repinoid a96e38fb1e chore: save current changes 2026-09-23 19:23:31 +03:00
Repinoid d78573de45 refactor(fullpipe): убрать зависимость от модификаторов (org_ips.tf, edge_network.tf) — чистые ресурсы vdc+nsxt 2026-09-23 08:55:26 +03:00
Repinoid eaa4593622 chore(fullpipe): bump провайдера 2.0.16 -> 2.0.17 (чистая генерация без модификаторов) 2026-09-23 08:51:01 +03:00
Repinoid a5a5f83730 chore(dev): bump провайдера 2.0.16 -> 2.0.17 (чистая генерация без модификаторов) 2026-09-23 08:46:32 +03:00
Repinoid 20ea79a489 docs(modifier): анализ inverse-архитектуры + промпты Опусу (глобальная архитектура оверлея) 2026-09-23 08:43:56 +03:00
Repinoid d5dc1212ba refactor(yaml-gen): убрать вплетение модификаторов — YAML = чистая полная выгрузка из API 2026-09-23 08:42:01 +03:00
Repinoid 016b7d246a chore(fullpipe): bump провайдера 2.0.15 -> 2.0.16 2026-09-23 07:11:12 +03:00
Repinoid 200bf457ce fix(gen): Update без modify перечитывает read-back поля через RefreshResourceState (устраняет устаревший state_params/ложный дрейф) 2026-09-23 07:04:02 +03:00
Repinoid 60bf218532 chore(fullpipe): bump провайдера 2.0.14 -> 2.0.15 2026-09-22 22:40:16 +03:00
Repinoid b9fa18164c fix(core,gen): UseStateForUnknown для Optional+Computed + live-dосылка без тихого fallback (R1+R3+A/B) 2026-09-22 22:35:45 +03:00
Repinoid 0473f80f69 chore(fullpipe): bump провайдера 2.0.13 -> 2.0.14 2026-09-22 22:10:28 +03:00
Repinoid f62a094043 fix(fullpipe): ALB/VS/qos задаются в модификаторе vc_nsxt_network (resource vc_nsxt Update — no-op) 2026-09-22 22:07:14 +03:00
Repinoid a011358660 fix(core): досылать незаданные modify-params из live state.params (а не paramValue формы) — устраняет сброс needEnableAVI/ALB 2026-09-22 22:04:09 +03:00
Repinoid d33673a567 chore(fullpipe): bump провайдера 2.0.12 -> 2.0.13 2026-09-22 21:53:36 +03:00
Repinoid e0ad8bb472 chore(dev): bump провайдера 2.0.12 -> 2.0.13 2026-09-22 21:44:59 +03:00
Repinoid 94c4c44ba4 fix(core): ShouldRemoveFromState читает deleted/404 через GetInstanceStateRaw без падения 2026-09-22 21:44:16 +03:00
Repinoid 4220f4f483 chore(fullpipe): bump провайдера 2.0.11 -> 2.0.12 2026-09-22 21:32:36 +03:00
Repinoid c634a1c51b docs: move root prompts 2026-09-22 21:26:26 +03:00
Repinoid 73357d0e0f fix(gen): bt как функция в шаблоне modifier + обновить описание 2.0.12 2026-09-22 21:22:56 +03:00
Repinoid bd46d11155 test(core,gen): unit-тесты modifierDesiredEqualsCurrent и ValidateSpec modifier 2026-09-22 21:20:43 +03:00
Repinoid 6a722e49cf feat(yaml): реестр исключений модификаторов (delete_strategy/idempotency/delete_params) 2026-09-22 21:17:09 +03:00
Repinoid 6b0378cc0d refactor(gen): шаблон modifier — reconcile(override), Delete стратегия, idempotency-вызов 2026-09-22 21:17:09 +03:00
Repinoid 25e988886d feat(core): modifierDesiredEqualsCurrent + RunInstanceOperationUniversalByIdempotent + RunOperationByCodeIdempotent 2026-09-22 21:13:56 +03:00
Repinoid 17a803836a refactor(core): вынести JSON-эквивалентность в core/jsonutil (+реэкспорт в resources_core) 2026-09-22 21:10:13 +03:00
Repinoid 8e24f8107e feat(gen): GenModifier расширение (DeleteStrategy/Idempotency/DeleteParams) + LoadSpecs + ValidateSpec 2026-09-22 21:07:15 +03:00
Repinoid 6e297cc649 feat(lib): delete_strategy/idempotency/delete_params в OperationSpec (+DeleteParam) 2026-09-22 21:03:11 +03:00
Repinoid 2933c57a4b docs(plan): внести правки ревью Опуса в план (порядок, ID-миграция, override, array-map-fixed) 2026-09-22 20:57:54 +03:00
Repinoid e06a2c0b11 docs(plan): финализировать план + запрос на ревью Опуса 2026-09-22 20:52:15 +03:00
Repinoid fba4cbda37 docs(plan): детальный план редизайна модификаторов (10 шагов + коммиты) 2026-09-22 20:50:37 +03:00
Repinoid c9c69a3d11 docs(opus): ответы №3 (граница lib/GenModifier, pre-check в core, частичный inverse) 2026-09-22 20:44:39 +03:00
Repinoid e55ca14d5e docs(opus): вопросы по расхождениям архитектуры модификаторов с кодом 2026-09-22 20:26:54 +03:00
Repinoid c3683cbe65 docs(opus): дописать ответы №2 (inverse/schema/current/idempotency/skip) 2026-09-22 20:19:04 +03:00
Repinoid 3b3cfc85c0 docs(opus): задокументировать архитектуру модификаторов + открытые вопросы 2026-09-22 20:15:59 +03:00
Repinoid bea39508f5 docs(opus): исчерпывающий запрос — спроектировать архитектуру модификаторов с нуля (все кейсы) 2026-09-22 20:11:14 +03:00
Repinoid 3d722fb7ee refactor(core): разбить client.go (1678 строк) на 15 мелких модулей по зонам ответственности 2026-09-22 20:06:49 +03:00
Repinoid c2deff1a58 chore(dev): bump провайдера 2.0.11 -> 2.0.12; закрепить 2.0.11 в FullPipe 2026-09-22 19:49:41 +03:00
Repinoid 3236203366 fix(core): не досылать modify-params без live-значения/дефолта — избежать синтетического 0 (integer > 0) 2026-09-22 19:49:04 +03:00
Repinoid 3a2a93b32e chore(dev): bump версии провайдера 2.0.10 -> 2.0.11 2026-09-22 19:26:17 +03:00
Repinoid 261809ba99 fix(generator): is_modifiable=true → параметр НЕ create-only (канон: меняется в UI → меняется в Terraform) 2026-09-22 19:21:42 +03:00
Repinoid d6520138f6 docs(opus): prompt — спроектировать простую модель изменяемости параметров (CreateOnly vs modifier) 2026-09-22 19:14:59 +03:00
Repinoid 3d0fc2be93 fix(fullpipe): убрать дубликат переменной nsxt_qos_profile 2026-09-22 19:10:36 +03:00
Repinoid 6cd9a3184b fix(fullpipe): убрать костыль — ALB/VS на create (true/3), модификатор только SNAT (ядро 2.0.10 досылает live) 2026-09-22 19:07:28 +03:00
Repinoid 8b0228cfd1 fix(fullpipe): Edge create ALB=false (неизменяемо), ALB/VS включаются только модификатором 2026-09-22 19:05:55 +03:00
Repinoid 307ef13a4e chore(fullpipe): закрепить провайдер nubes-dev 2.0.10 2026-09-22 19:03:02 +03:00
Repinoid 7e2ad415b4 chore(dev): bump версии провайдера 2.0.9 -> 2.0.10 2026-09-22 18:56:45 +03:00
Repinoid 57bf79d85b docs(todo): план разбиения client.go (1678 строк) на модули 2026-09-22 18:56:11 +03:00
Repinoid aca15e8f69 docs(opus): задокументировать разбор бага сброса create-полей modify 2026-09-22 18:55:27 +03:00
Repinoid c420ea0e70 fix(core): дозаполнять ВСЕ незаданные params modify их live-значением (по коду), иначе модификатор сбрасывает create-поля в дефолт 2026-09-22 18:54:51 +03:00
Repinoid 4f7edc1200 docs(opus): prompt по багу — modify-модификатор сбрасывает create-поля в дефолт 2026-09-22 18:51:00 +03:00
Repinoid 815ace7d2c fix(fullpipe): модификатор edge_net шлёт ALB/VS/qos целиком — иначе modify с null сбрасывает needEnableAVI в false 2026-09-22 18:48:46 +03:00
Repinoid 5129d2726d fix(fullpipe): ALB=true и VS=3 при создании Edge, модификатор только SNAT, раскомментировать всё 2026-09-22 18:37:21 +03:00
Repinoid 34c8e72e46 test(fullpipe): закомментировать Edge/edge_net/org_ips — оставить только VDC для destroy-проверки 2026-09-22 18:30:28 +03:00
Repinoid 7a6e3bd1e5 fix(fullpipe): ALB/VS меняются только через модификатор (edge create неизменяем) 2026-09-22 18:23:07 +03:00
Repinoid ca928b1e4b fix(fullpipe): параметры под Штурвал — ALB=true, VS=3, внешних IP=3 2026-09-22 18:20:54 +03:00
Repinoid 779f74b68c fix(fullpipe): org_ip_count default 1 (нужен свободный IP для SNAT) 2026-09-22 18:11:00 +03:00
Repinoid e0604fd9f9 feat(fullpipe): добавить модификатор vc_nsxt network (SNAT с внешним IP из vc_org) 2026-09-22 18:10:12 +03:00
Repinoid 099494eb63 test(fullpipe): org_ip_count default 0 после проверки modify 1→0 2026-09-22 17:46:23 +03:00
Repinoid 5ec3936235 chore(fullpipe): закрепить провайдер nubes-dev 2.0.9 2026-09-22 17:46:23 +03:00
Repinoid 788073f1ea docs(fullpipe): задокументировать проверку модификатора vc_org ip_space (modify 1↔2↔0, идемпотентность) 2026-09-22 17:46:23 +03:00
Repinoid 9107ba00fa chore(dev): bump версии провайдера 2.0.8 -> 2.0.9 2026-09-22 16:49:55 +03:00
Repinoid b1cea8a930 fix(core): fallback на /instanceOperations/default/{opId} при 500 getResourceRealmConfig в modify 2026-09-22 16:49:36 +03:00
Repinoid 947887a6ea docs(opus): задокументировать код-ревью модификаторов 2026-09-22 16:42:58 +03:00
Repinoid 423de314dd docs(opus): prompt код-ревью модификаторов 2026-09-22 16:40:20 +03:00
Repinoid 4bfbce4f44 feat(fullpipe): добавить модификатор vc_org ip_space (выделение внешних IP) 2026-09-22 16:40:20 +03:00
Repinoid eb85a3dc72 docs(har): пометить ошибки сломанного Edge как неподтверждённые наблюдения, не правила 2026-09-22 15:01:23 +03:00
Repinoid bcbc49dc52 docs(har): Insufficient rule blocks = нельзя снять ALB при живых VS (needEnableAVI forward-only) 2026-09-22 14:59:57 +03:00
Repinoid 19ad60b485 docs(har): ошибки операций EDGE — ipSpace '' невалиден, Insufficient rule blocks, delete FORBIDDEN при VS 2026-09-22 14:52:57 +03:00
Repinoid c7c80fd1bd fix(yaml-generator): сортировать subParams по ID — детерминированный порядок вложенных параметров 2026-09-22 14:36:04 +03:00
Repinoid 21b4da12a1 docs(har): имя ipSpace — выбор из списка (динамический); де-аллокация пока заблокирована 2026-09-22 14:27:58 +03:00
Repinoid ca4a246c8c docs(har): org2.har — де-аллокация = меньший count в vIPConfigure; операция pending (блок детей) 2026-09-22 14:04:31 +03:00
Repinoid d218bffad7 docs(har): ограничение де-аллокации IP в vcOrg (нельзя при дочерних инстансах) + порядок destroy 2026-09-22 14:01:00 +03:00
Repinoid 08108ba62b docs(har): no-needed — каноническое значение выключенного SNAT (подтверждено UI) 2026-09-22 14:00:08 +03:00
Repinoid a1b6ac8f23 docs(har): разбор SNAT/ipSpace модификаций — payload-и, no-needed, reverse SNAT 2026-09-22 13:54:00 +03:00
Repinoid 78f9dfbcfb refactor(build): эфемерные generated-копии — dev-materialize по стенду вместо постоянного дубля 2026-09-22 13:48:36 +03:00
Repinoid c14f7de7bc docs: раздел «Реестр исключений» + диалог код-ревью opus/astra 2026-09-22 08:31:22 +03:00
Repinoid dc321b5e3a refactor(generator): реестры исключений (данные) вместо хардкодов svc.ID==N / ServiceID==N 2026-09-22 08:31:22 +03:00
Repinoid 05f56c5e00 fix(build): гейт дрейфа сгенерированного кода + запрет прямой сборки из provider/ 2026-09-22 08:31:22 +03:00
Repinoid 7eab45ed71 docs(opus): промпт на код-ревью roadmap (vcOrg modify IP, vcNsxt SNAT, k8sShturval) 2026-09-22 07:21:16 +03:00
Repinoid a424e4e319 docs: резюме для старта новой сессии (состояние 2.0.8, правила, файлы, открытые вопросы) 2026-09-22 07:09:23 +03:00
Repinoid 13beb9c142 release(dev): 2.0.8 2026-09-21 21:56:42 +03:00
Repinoid 4b34cc7e63 fix(core): гарантия known для read-back полей (unknown -> null), чтобы Computed без Default не ломал apply 2026-09-21 21:48:54 +03:00
Repinoid 3c0157a1af fix(generator): read-back параметры без Default -> Optional+Computed (универсальное правило ShouldBeOptionalComputed) 2026-09-21 21:48:54 +03:00
Repinoid 25080b7379 release(dev): 2.0.7 2026-09-21 21:18:03 +03:00
Repinoid 724f5f7efb fix(generator): убрать create-time проверку существования из ModifyPlan (ломал tainted-replace и terraform destroy) 2026-09-21 20:54:57 +03:00
Repinoid 14ada09335 docs(flash): ТЗ на фикс tainted-replace - убрать create-time проверку из ModifyPlan 2026-09-21 20:52:51 +03:00
Repinoid e8da976a7b docs(instructions): copilot-instructions.md1 -> copilot-instructions.md 2026-09-21 20:41:58 +03:00
Repinoid 67c4d2f190 tool(scripts): validate_docs_examples.sh — terraform validate по примерам из сгенерированных доков 2026-09-21 20:41:13 +03:00
Repinoid 69808bdf09 release(dev): 2.0.6 2026-09-21 20:30:59 +03:00
Repinoid c6715e81be fix(generator): передавать supportsSuspend в diagnostics; не генерировать suspend_on_destroy для сервисов без suspend 2026-09-21 20:26:41 +03:00
Repinoid 8d405ba695 fix(core): подсказки при конфликте имени учитывают отсутствие suspend/resume (no adopt) + supportsSuspend в сигнатурах 2026-09-21 20:26:41 +03:00
Repinoid 3ca0752df8 fix(docs-generator): строковые дефолты в кавычках (default 81.22.46.22 ломал HCL-парсер: Invalid number literal) 2026-09-21 20:26:41 +03:00
Repinoid 7c2cc673cc fix(docs-generator): array-map-fixed = jsonencode([...]) (StringAttribute, не объект); классификация по data_type, а не is_json 2026-09-21 20:22:27 +03:00
Repinoid af2e10b1d5 fix(docs-generator): вложенные map-fixed как аргумент '= {' (а не блок), array-map-fixed через jsonencode 2026-09-21 20:19:42 +03:00
Repinoid 54b0baa718 release(dev): 2.0.5 2026-09-21 20:08:55 +03:00
Repinoid 61c7e207ba docs(HISTORY): сессия 2026-09-21 - фиксы генератора, refSvc, FullPipe (vDC+Edge) 2026-09-21 20:08:40 +03:00
Repinoid bffe3d9950 fix(generator): refSvc-поля без Computed (unset = null, а не unknown) 2026-09-21 20:05:02 +03:00
Repinoid 92e04daea5 docs(TODO): баг docs-generator - вложенный map-fixed как блок вместо = {} 2026-09-21 19:57:48 +03:00
Repinoid d608fba338 stand(FullPipe): vc_nsxt (edge.tf), storage_config fast->SATA, provider 2.0.4 2026-09-21 19:57:48 +03:00
Repinoid 140100378f release(dev): 2.0.4 2026-09-21 19:57:48 +03:00
Repinoid 2286d34499 fix(generator): destroy-guard в ModifyPlan + универсальный refSvc (имя или UUID) 2026-09-21 19:57:48 +03:00
Repinoid 7ecd2aaf44 Fix generator rebuild and release pipeline 2026-09-21 18:08:26 +03:00
Repinoid 1ec6a0fedc Fix VDC flow and FullPipe example 2026-09-21 13:02:25 +03:00
Repinoid 7d446977a5 Fix FullPipe VDC example placeholders 2026-09-21 12:07:06 +03:00
Repinoid 308f92bc37 fix(core): graceful fallback for cfsParams 500 error in createInstanceWithContext
- In provider/internal/core/client.go:
  - If GET /instanceOperations/{opUid}?fields=cfsParams fails with 500 or JSON unmarshal error,
    check whether provided parameters contain unresolved names via hasUnresolvedParams.
  - If all parameters are already resolved (UUIDs/numbers/booleans/JSON), proceed to send
    parameters via POST /instanceOperationCfsParams without hard-failing.
  - If unresolved names remain, fail immediately with original error.
- Bumped DEV provider version to 2.0.2 in TOOLS/config/dev/profile.env, VERSIONS.md, and DEV_STAND/FullPipe/versions.tf.
- Built, signed, and published provider 2.0.2 to DEV registry bucket nubes-terraform-registry.
2026-09-21 09:59:15 +03:00
Repinoid 7ff98f8edc feat(stand): add FullPipe DEV stand for vc_vdc and document 500 error fallback plan
- Added DEV_STAND/FullPipe with modular configuration for vc_vdc resource creation:
  - versions.tf: Terraform >= 1.5.0, provider nubes 2.0.1
  - provider.tf: nubes provider config with DEV gateway endpoint
  - variables.tf: variables for vdc (cpu=8, mem=32, guaranteed=0, fast storage=200GB)
  - vdc.tf: nubes_vc_vdc resource definition supporting org name or UUID
  - outputs.tf: vdc_id, vdc_name, vdc_state_params
  - terraform.tfvars.example: example values without secrets
- Added docs/DEBUG_REPORT_VC_VDC_500.md documenting API 500 issue in Lucee backend:
  - Error: getResourceRealmConfig fails casting Struct to string on GET /instanceOperations/{opUid}?fields=cfsParams
  - Planned changes in provider/internal/core/client.go:
    Implement graceful fallback in createInstanceWithContext to skip hard-failing
    when GET ?fields=cfsParams returns 500, since vc_vdc ref parameters (organization_uid)
    are already resolved to UUID and remaining parameters are literals/numbers.
2026-09-21 09:46:43 +03:00
Repinoid 4b218fbe33 fix: publish providers to registry bucket 2026-09-20 21:42:07 +03:00
Repinoid f32159b3af fix: publish generated providers to working registry 2026-09-20 20:00:34 +03:00
Repinoid a44894d877 1 2026-09-20 18:15:17 +03:00
Repinoid 8f552ecacc fix: harden modifier resource lifecycle
Validate required modifier parameters, refresh modifier state from parent state_params, preserve operation timeout and log level during update, and normalize nested modifier payloads as JSON. Document the modifier contract, vcOrg/vcNsxt usage, and the intentionally unsupported rollback semantics.
2026-09-20 18:11:17 +03:00
Repinoid a48b658d78 feat: add generated parent modify resources
Introduce the modifier YAML kind for delayed parent-level modify operations and generate dedicated Terraform resources with typed parameters. Keep modifier parameters out of the ordinary instance CRUD resource, register modifiers separately, and leave delete as a no-op until an inverse API payload is confirmed. Mark vcOrg and vcNsxt modify operations during YAML generation so the contract survives regeneration.
2026-09-20 18:05:24 +03:00
Repinoid 33672e05bf docs: add modifier resources ideology and architecture specification 2026-09-19 08:32:28 +03:00
Repinoid 0464a30642 docs(strategy): оркестрация взаимозависимых ресурсов и паттерн привязок
- complex_provisioning_workflow.md — цепочка vcOrg -> vcVdc -> vcNsxt -> Штурвал,
  включая modify-шаги и пошаговые модификации
- shturval_dev_provisioning_spec.md — точные параметры и ID операций DEV-стенда
  для цепочки развёртывания k8s_sthutrval_cluster
- terraform_association_pattern_and_pipeline.md — Resource Association Pattern
  (разделение сущности и ресурсов-привязок/модификаций)
2026-09-18 18:44:36 +03:00
Repinoid 106ddbe092 stand: switch every stand to actual provider versions (prod=1.0.0, dev=2.0.0, test=3.0.0)
Легаси-версии (5.0.x/5.1.x/3.1.x/2.1.x) в реестре отсутствуют (404), стенды не могли
пройти terraform init. Приведены к новой схеме нумерации от 2026-09-03.

- DEV  (nubes-dev):  CRUD, POSTGRES, SHTURVAL_MGMT -> 2.0.0
- TEST (nubes-test): CRUD, PG, POSTGRES, MARIA_DB, buck0, kuber, IOT_RMQ_DEMO -> 3.0.0
                     (DEV_STAND/IOT_KAFKA_DEMO тоже nubes-test -> 3.0.0)
- PROD (nubes):      PG1, POSTGRES, RABBIT -> 1.0.0
- README стендов TEST_STAND/buck0, TEST_STAND/PG: версии приведены к 3.0.0
- getting-started: убрана versioned-ссылка на доки (2.1.7) -> актуальный домен без версии
- .gitignore: откатано правило TMP/ (на remote TMP/init-test-*/main.tf трекаются)
2026-09-16 16:34:40 +03:00
Repinoid dbaeea5c89 stand(PGwNewRegistry): pin provider to 3.0.0 (new TEST scheme) 2026-09-16 16:33:27 +03:00
Repinoid 7eb8eb8859 chore(git): ignore TMP/ (local terraform init scratch) 2026-09-16 16:33:27 +03:00
“Naeel” 7a25ad8fc9 docs: show current environment on index 2026-09-03 17:58:13 +03:00
“Naeel” a222a0dace fix(docs): publish cross-stand index links 2026-09-03 17:39:24 +03:00
“Naeel” bba6b47dc2 docs: link documentation environments 2026-09-03 17:26:20 +03:00
“Naeel” ccb458a167 docs: record test and prod publication 2026-09-03 16:56:51 +03:00
“Naeel” aa0f7f6402 fix(docs): remove stand-specific hardcodes 2026-09-03 16:14:28 +03:00
“Naeel” 6bf514e03a docs: describe stand-agnostic docs pipeline 2026-09-03 12:05:21 +03:00
“Naeel” 8d5bbd368d docs: link verified documentation upload guide 2026-09-03 12:03:58 +03:00
“Naeel” 423c74d3f1 docs: record verified documentation upload pipeline 2026-09-03 12:03:12 +03:00
“Naeel” 085a310720 test: add terraform init configs for all stands 2026-09-03 11:52:47 +03:00
“Naeel” b76e0d1086 fix(generator): inherit nested schema across operation params 2026-09-03 10:54:59 +03:00
“Naeel” 9ebe5b19d6 docs: remove S3 credentials from release plans 2026-09-03 10:53:36 +03:00
“Naeel” 62d8d7b45d docs: record universal dev generator fix plan and Sol review prompt 2026-09-03 10:36:45 +03:00
“Naeel” 02b7d7b701 fix(docs): per-stand getting-started injection (source namespace, version, api_endpoint) after copy into docs_dir; placeholders {{NAMESPACE}} 2026-09-03 09:19:56 +03:00
“Naeel” 9090488731 reversion: prod=1.*, dev=2.*, test=3.*; cleanup registry; test 3.0.0 + prod 1.0.0 uploaded (dev skipped: jsonEnv nested bug) 2026-09-03 09:07:05 +03:00
“Naeel” 9e02b696ba fix(docs-pipeline): default DOCS_GEN_DIR -> generated/<stand>/docs; document env setup + S3 via VM 2026-09-03 08:06:59 +03:00
“Naeel” 9fd7334a60 feat(docs): version badge v0.1 in header right corner (manual bump on changes) 2026-09-03 07:32:59 +03:00
“Naeel” 72a8a491c6 docs: актуализация DOCS_PIPELINE/README (без версий, новый хост); старый -> legacy 2026-09-03 07:27:58 +03:00
“Naeel” dc469c6dce feat: publish docs without version (mc mirror overwrite) + site_url per stand 2026-09-02 18:36:55 +03:00
“Naeel” 211143980c fix: restore scripts/publish-docs.sh (docs upload to S3 terraform-registry) 2026-09-02 14:40:53 +03:00
“Naeel” 2414647337 docs: record full docs pipeline analysis 2026-09-02 11:42:37 +03:00
“Naeel” 97fd77c2e9 docs: update provider version to 5.0.5 in getting-started guide 2026-09-02 08:09:29 +03:00
“Naeel” b3342bc0c5 docs: add DOCS_PIPELINE — инструкция по генерации и заливке MkDocs-документации 2026-09-01 08:43:55 +03:00
“Naeel” ed568a867a Record stand configuration and remove exposed secret 2026-08-31 20:20:18 +03:00
“Naeel” 86871498a2 Fix provider review findings 2026-08-31 20:19:31 +03:00
“Naeel” 75c868e0b5 chore: bump test docs version to 5.0.7 2026-08-31 17:04:08 +03:00
“Naeel” d61c8d5cb7 docs: fix registry URL in getting started guide 2026-08-31 16:49:10 +03:00
“Naeel” 05694a3446 stand(CRUD): refactor resource names, drop git_revision, add adopt_existing, provider 5.0.5 2026-08-13 12:49:46 +04:00
“Naeel” 657157527b fix: curated PG example — use S3 name not UUID, comment every parameter 2026-08-10 21:42:41 +04:00
“Naeel” a21cbd9cd2 fix: remove s3-bucket-notifications (unverified), clean deck-api from getting-started 2026-08-10 19:33:24 +04:00
“Naeel” 5a718a3ec2 feat: curated examples in sidebar nav via WriteNavFragment — PG+user+DB example 2026-08-10 19:25:58 +04:00
“Naeel” 25047c7607 feat: curated/ directory — agent-managed examples (PG + user + DB + S3) 2026-08-10 19:19:45 +04:00
“Naeel” af5431d6db feat: LLM enrichment (GPT-120) — 137 files, 1 failure (postgres_params_create timeout, copied original) 2026-08-10 18:22:16 +04:00
“Naeel” dc5bac376c docs: MAN format fix history + instructions (??? admonition, not details/div) 2026-08-10 17:16:54 +04:00
“Naeel” b97edf9f57 fix: MAN — use mkdocs-native ??? admonition, blank line after title, indent all lines 2026-08-10 17:07:29 +04:00
“Naeel” b8c1508140 fix: MAN format — remove div markdown=1, use clean Markdown in details 2026-08-10 17:02:59 +04:00
“Naeel” 2ee1eb98e8 docs: YAML vs docs audit — 37 services, all params match 1:1 2026-08-10 14:11:35 +04:00
“Naeel” c77e0327d0 feat(Phase 3): anchors for map-fixed, cloud keys in examples, russian headers, fix snapshot path 2026-08-10 13:52:28 +04:00
“Naeel” 04d9c4a191 feat(Phase 2): unified params table (Required col), MAN in details, danger admonition for lifecycle 2026-08-10 13:48:25 +04:00
“Naeel” 195f153860 feat(Phase 1): sidebar visible, version in header, index categories, MAN 0.78rem, nav injection in 04 script 2026-08-10 13:38:35 +04:00
“Naeel” 21b92f4631 docs: Q&A with Sonnet — UX analysis + nav merge strategy (Phase 1-3 plan) 2026-08-10 13:32:09 +04:00
“Naeel” 35aa50f76d fix: HTML tables → Markdown tables in docs-generator (mkdocs теперь рендерит таблицы корректно) 2026-08-10 13:12:17 +04:00
“Naeel” 8d53a4c499 fix: restore inline navigation in docs-generator writers.go 2026-08-10 12:43:10 +04:00
“Naeel” 2af2d2af16 chore: registry.kube5s.ru → tf-registry.containerk8s.services.ngcloud.ru
- All code/script/.tf defaults replaced
- Docs annotated with ⛔ LEGACY
2026-08-10 11:17:57 +04:00
“Naeel” 5f81e4a664 fix: ToSnake acronym bug, migrate compare script to Gateway, YAML regen all stands
- ToSnake: fix acronym splitting (CPU, TTL, CA, API, VIP, AVI)
- compare_yaml_vs_api.py: deck-api → lk-api-gateway
- YAML regenerated from API for all 3 stands
- Go resources regenerated
- Providers rebuilt 3.0.6/5.0.5/2.0.6
2026-08-10 09:25:53 +04:00
“Naeel” f6494d038e chore: inline nav removed, bump versions 3.0.5/5.0.4/2.0.5, rebuild 2026-08-09 22:06:38 +04:00
“Naeel” 40a95cc4a6 feat(P2+P3): docs-generator — убрать ID, value_list читаемый, двойной пример, _nav_fragment.yml, mkdocs breadcrumbs
- renderParamTable/renderModifyTable/renderNestedParams: убрать колонку ID
- collectConstraints: value_list → Допустимые значения
- buildExamplePage: минимальный пример + полный в <details>
- minimalExampleBlock: только required без default
- WriteNavFragment: генерация _nav_fragment.yml с категориями
- mkdocs.yml: navigation.path, navigation.footer, navigation.indexes
2026-08-09 21:56:40 +04:00
“Naeel” c9a881aa84 feat(P1): новый LLM-промпт для документации — правила A-E
- value_list → читаемый текст (Допустимые значения)
- regex → описание формата
- пустые описания → заполнять из MAN
- группы map-fixed → 1 предложение о содержимом
- операции без описания → шаблоны
- MAN-контекст для params/ops файлов
- max_tokens 4096 → 8192
- вывод в docs_llm/ вместо перезаписи docs/
2026-08-09 21:11:03 +04:00
“Naeel” ac2ce0ea53 chore: bump versions 3.0.4/5.0.3/2.0.3 + rebuild 2026-08-09 20:08:12 +04:00
“Naeel” 90c5418bb1 feat: tests for stages output + testability refactoring
- add PollInterval, StagesWriter, RetryBaseDelay to UniversalClient
- 5 unit tests for waitForOperationFinish (mock API, 0.25s total)
- Sonnet/DeepSeek/Opus comparison answers archived
2026-08-09 20:01:43 +04:00
“Naeel” 5ce7853817 chore: bump versions DEV 3.0.3, PROD 2.0.2 2026-08-09 19:16:47 +04:00
“Naeel” 52bd80ee36 chore: остатки 2026-08-09 18:23:54 +04:00
“Naeel” fda819cd81 feat: stages output + services_list sync
- stages always visible (remove log_level gating)
- fix tty resource leak (move out of poll loop)
- show current stage [..] in addition to completed [OK]/[FAIL]
- sync services_list.txt for all 3 stands with live UI
- add YAML vs API comparison script
- bump TEST version 5.0.1 -> 5.0.2
2026-08-09 18:18:35 +04:00
“Naeel” b6b25f7548 feat(test): провайдер 5.0.1 залит в nubes-test 2026-08-09 17:13:12 +04:00
“Naeel” bf36c5b42c fix: PGwNewRegistry переключён на TEST стенд (баг Script26 в DEV) + баг-репорт 2026-08-09 17:05:46 +04:00
“Naeel” 86b44cc141 fix(dev): версия повышена 3.0.1→3.0.2 (terraform init не подхватит старую) 2026-08-09 16:35:03 +04:00
“Naeel” 8b60145506 feat(dev): провайдер 3.0.1 собран из DEV API (YAML + Go + docs перегенерированы) 2026-08-09 16:05:15 +04:00
“Naeel” 3a4eddbfb9 fix: версии всех стендов сброшены на X.0.1 (2.0.1 PROD, 3.0.1 DEV, 5.0.1 TEST) 2026-08-09 16:01:45 +04:00
“Naeel” cfd1235d04 doc: HOWTO-UPLOAD.md переписан под новый пайплайн (3 шага, registry.env, profile.env) 2026-08-09 16:00:30 +04:00
“Naeel” 260766e0b2 refactor: единый registry.env + очистка profile.env от registry-параметров + синхронизация build-provider.sh 2026-08-09 15:56:51 +04:00
“Naeel” 621d8c2738 fix: DEV_STAND/PGwNewRegistry — source и version обновлены под новый реестр (3.0.1 dev) 2026-08-09 15:17:40 +04:00
“Naeel” 96594fae24 doc: HOWTO-UPLOAD.md — схема версий (2=PROD, 3=DEV, 5=TEST) + VERSIONS.md для трекинга 2026-08-09 15:14:59 +04:00
“Naeel” 08ffeedd66 doc: HOWTO-UPLOAD.md — инструкция по заливке провайдера в прод 2026-08-09 15:04:35 +04:00
“Naeel” f41446de02 fix: main.go — address обновлён на tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes 2026-08-09 15:03:26 +04:00
“Naeel” 16c7e05b4b Добавлены материалы подготовки к экзамену ФИЯР МГУ, PGwNewRegistry tf-конфиг, скрипт сравнения YAML/API, обновлены services_list 2026-08-07 21:51:11 +04:00
“Naeel” 18f954b7ab switch registry hostname to go-registry.containerk8s.dev.nubes.ru 2026-08-07 08:29:07 +04:00
“Naeel” 46e9169300 Фикс MONGO_URI, новые имена iot-cons/iot-dash, HISTORY, демо-схема, комментарии в коде 2026-07-23 08:25:12 +04:00
“Naeel” 5cec3ed55b Фикс MONGO_URI, новые имена consumer/dashboard, HISTORY-доки, схема 2026-07-22 11:15:40 +04:00
“Naeel” 5f502bcf3d feat: adopt_existing_on_create=true на всех ресурсах — destroy+apply без новых имён 2026-07-22 09:31:50 +04:00
“Naeel” 2b5964799a fix: submodules 2026-07-22 09:28:17 +04:00
“Naeel” 8b888666d7 rename: IOT_KAFKA_DEMO -> IOT_RMQ_DEMO (Kafka gone, RabbitMQ is the queue) 2026-07-22 09:28:06 +04:00
“Naeel” b72c1e0517 fix: remove accidental submodules 2026-07-22 09:27:38 +04:00
“Naeel” 25011eb783 docs: комментарий на КАЖДУЮ строку infra.tf 2026-07-22 09:27:26 +04:00
“Naeel” 3dac50b78a docs: все TF файлы с детальными комментариями на каждую строку 2026-07-22 09:26:20 +04:00
“Naeel” 837a663367 fix: remove accidental submodule 2026-07-21 21:58:13 +04:00
“Naeel” a37fc22bcd feat: IoT demo — RabbitMQ + Redis + MongoDB + Node.js producer plan (test stand cleanup) 2026-07-21 21:58:00 +04:00
“Naeel” 0adb379a54 feat: IoT demo final — 6 services (Kafka+Redis+MongoDB+2Flask+NodeJS), removed broken CH/Superset/AKHQ 2026-07-21 18:32:35 +04:00
“Naeel” 55d4109eee 11 2026-07-21 18:17:03 +04:00
“Naeel” e434a44fb5 feat: IoT demo — Kafka + Redis + MongoDB, 6 services, removed broken AKHQ/ClickHouse/Superset 2026-07-21 18:15:25 +04:00
“Naeel” 336e6e3795 feat: IoT Kafka demo — Terraform + history (test stand fail, moving to dev) 2026-07-21 17:33:03 +04:00
“Naeel” 399745c177 docs: update history — SSL, import, tf_examples, lessons learned 2026-07-21 16:09:43 +04:00
“Naeel” f0e01dad6e docs: point to tf_examples repo, add tf_examples to .gitignore 2026-07-21 09:06:13 +04:00
“Naeel” 5d338bbd02 fix: remove non-existent Download button mention 2026-07-21 09:03:28 +04:00
“Naeel” b093bff148 fix: download whole repo archive, no folder-level Download in Gitea 2026-07-21 09:03:02 +04:00
“Naeel” 3d831b16a0 docs: how to download just the CRUD folder (sparse checkout) 2026-07-21 08:59:50 +04:00
“Naeel” 1d1258a83b docs: fill + rename terraform.tfvars.example, not copy 2026-07-21 08:57:08 +04:00
“Naeel” 9ee16ce371 fix: s3_name — S3 instance name, not user 2026-07-21 08:54:54 +04:00
“Naeel” 052c412466 docs: history 2026-07-21 — CRUD stand, three apps, bugs fixed 2026-07-21 08:50:14 +04:00
“Naeel” 5ec3c8aceb fix: add PGSSLMODE to nodejs.tf (TEST + DEV), bump flask_git_revision 2026-07-21 08:41:59 +04:00
“Naeel” fb6df52a3b chore: update tfluceecrud ref + add DEV_STAND/SHTURVAL_MGMT 2026-07-21 08:39:29 +04:00
“Naeel” 89163e9609 bump nodejs_git_revision to 809d30b (fix startup + SSL) 2026-07-21 08:31:44 +04:00
“Naeel” eb36731d3e docs: explain why two applies — PG user not ready for apps 2026-07-21 08:23:39 +04:00
“Naeel” ecace0311b docs: TEST_STAND README + clean terraform.tfvars.example 2026-07-21 08:18:48 +04:00
“Naeel” a4a3f8e63c fix: domain comments — correct .dev.nubes.ru suffix, add uniqueness note 2026-07-21 08:06:45 +04:00
“Naeel” 4ea4cc82b0 docs: TEST_STAND locals.tf — comments on every line 2026-07-21 08:03:29 +04:00
“Naeel” 6dfe156b39 add Node.js to DEV_STAND/CRUD, comment every line in locals.tf 2026-07-21 07:36:06 +04:00
“Naeel” 664ab51b0d fix: update lucee_git_revision (8268568) + flask_git_revision (54746e9) — no DROP, init_db at module level 2026-07-21 07:32:42 +04:00
“Naeel” f56c0c0cf4 add Node.js CRUD (tfnodejscrud) + nodejs.tf in TEST_STAND 2026-07-20 13:50:13 +04:00
“Naeel” 33c5844ede fix: DEV_STAND domains — tfluceedev, tfflaskdev 2026-07-20 13:02:26 +04:00
“Naeel” 6185a47a0e fix: DEV_STAND tfvars.example — only active fields, empty placeholder values 2026-07-20 12:54:01 +04:00
“Naeel” b1e7c6cce9 docs: DEV_STAND terraform.tfvars.example — match real values 2026-07-20 12:50:27 +04:00
“Naeel” 678f24346e add DEV_STAND/CRUD (nubes-dev, 3.1.13) 2026-07-20 12:31:18 +04:00
“Naeel” 4465180616 bump DEV 3.1.13 + PROD 2.1.12 (regenerated from fixed template) 2026-07-20 12:27:52 +04:00
“Naeel” 50778683a9 chore: add tfflaskcrud/ and gateway/ to .gitignore 2026-07-20 12:24:59 +04:00
“Naeel” 3a24690c3f docs: add comments to terraform.tfvars.example (s3_name/S3 UUID interchangeability) 2026-07-20 12:17:24 +04:00
“Naeel” b05969aad0 bump flask_git_revision to 34c030c (site/ structure) 2026-07-20 12:12:44 +04:00
“Naeel” 4c5a61cbbf refactor: LUCEE/ → CRUD/, npg_lucee → main_pg, add flask.tf 2026-07-20 11:05:39 +04:00
“Naeel” 757aa2de98 fix: modify only if hasServiceParamChanges (skip when git_revision only), bump TEST 5.1.16 2026-07-20 10:30:19 +04:00
“Naeel” d05456c4f9 bump TEST 5.1.15 2026-07-20 10:09:21 +04:00
“Naeel” 53edffd255 fix: redeploy template — merge modify-params into redeploy params (single apply), bump TEST 5.1.14 2026-07-20 09:49:18 +04:00
“Naeel” 67ce3e4ab9 fix: redeploy template — handle empty params + nil for Dummy 2026-07-20 07:51:46 +04:00
“Naeel” 4aa1c83bea chore: replace placeholder inputs/outputs with real TEST_STAND/LUCEE parameters 2026-07-20 07:51:07 +04:00
“Naeel” b137c93869 fix: redeploy now sends CFS params from YAML (RunRedeployOperation + template + loader) 2026-07-20 07:45:56 +04:00
“Naeel” 377542eb45 chore: add static SVG diagram and embed in SCHEMA.md 2026-07-20 07:43:50 +04:00
“Naeel” 9fb23af151 chore: simplify mermaid graph for stable render 2026-07-20 07:01:04 +04:00
“Naeel” 1208104f77 fix: mermaid labels - quote and use newline instead of <br/> 2026-07-20 06:55:00 +04:00
“Naeel” a99fac5a86 chore: add LUCEE mermaid schema 2026-07-20 06:51:54 +04:00
“Naeel” 196d55a945 LUCEE: updated lucee.tf with new git_path, TABLE_NAME, user/db subresources 2026-07-19 18:54:09 +04:00
“Naeel” 6660008af1 fix: ResolveS3UidInAllMapFixed exported + called in RequiredParamsMismatch 2026-07-19 13:36:40 +04:00
“Naeel” 4d14568852 fix: non-constant format string in fmt.Errorf 2026-07-19 13:11:46 +04:00
“Naeel” 38d278427e fix: move s3Uid resolve before early return when no refSvcId 2026-07-19 13:04:31 +04:00
“Naeel” dd61b1b08d fix: s3Uid resolve by JSON key pattern (no DataDescriptor dependency) 2026-07-19 11:37:52 +04:00
“Naeel” 65b78f2a78 fix: resolve s3Uid name→UUID for uuid sub-params inside map-fixed 2026-07-19 10:45:22 +04:00
“Naeel” 0a027c2ebf fix: map-fixed fallback builds JSON from DataDescriptor sub-param defaults 2026-07-19 09:34:26 +04:00
“Naeel” 35792091bd fix: nested attrs with default → Optional (no Computed), prevents unknown values 2026-07-18 23:12:51 +04:00
“Naeel” b6f5b481af bump versions: dev 3.1.3, test 5.1.5, prod 2.1.2 2026-07-18 23:01:14 +04:00
“Naeel” 92cb40e476 fix: nil-guard for nested map-fixed params in generated code 2026-07-18 22:51:43 +04:00
“Naeel” 7edea3f472 bump versions: dev 3.1.2, test 5.1.4, prod 2.1.1 2026-07-18 22:39:47 +04:00
“Naeel” 6f0e2a9b81 fix: resource-generator Merge — union SubParams вместо first-seen-wins 2026-07-18 22:36:39 +04:00
“Naeel” cfb546651b fix: zero-value fallback — integer→0, boolean→false; modify uses WithDefaults 2026-07-18 22:28:02 +04:00
“Naeel” 97bd580626 fix: 03_build script — VERSION from profile.env no longer overwritten, regex fixed 2026-07-18 16:02:43 +04:00
“Naeel” 84a3513f02 fix: terra.k8c.ru → registry.kube5s.ru everywhere 2026-07-18 15:48:22 +04:00
“Naeel” 8e2c3845f1 chore: правило — нестандартные команды только по приказу 2026-07-18 15:38:38 +04:00
Naeel 40f2714df6 chore: правило — сначала анализ, инструкция билда провайдера 2026-07-18 14:33:04 +03:00
Naeel b770d55d4c fix: разорвать симлинки services_list, запрет симлинков в правилах 2026-07-18 14:18:38 +03:00
Naeel b0555d625a fix: ldflags синтаксис для Go 1.26 (без пробела перед значением) 2026-07-18 13:52:29 +03:00
Naeel ab7ab7abf2 fix: S3 refSvcId=12 — разрешить имя наравне с UUID, версия 5.1.3 2026-07-18 13:33:59 +03:00
Naeel 0f82e08e81 fix: скрывать пустые секции, ★ красный/зелёный 2026-07-18 10:39:23 +03:00
Naeel 284f5b2c4f fix: таблицы — :has() разделяет 5/6 колонок, суммы 100% 2026-07-18 10:17:14 +03:00
Naeel ad3b986d89 feat: новый дизайн документации — шаблон, генератор, CSS, md_in_html, версия 5.1.2 2026-07-18 09:50:37 +03:00
Naeel 1276143973 chore: уточнение — генераторы по сервисам 2026-07-18 08:47:46 +03:00
Naeel d29f0fa98d chore: правило — .gitignore ДО генерации 2026-07-18 08:46:50 +03:00
Naeel 53dbcbe91c chore: удалить 73 сгенерированных Go-файла из git 2026-07-18 08:44:07 +03:00
Naeel e8f78155d8 chore: удалить generated/test/docs/ из git 2026-07-18 08:43:09 +03:00
Naeel 3476feaf61 chore: удалить 934 сгенерированных .md из git (docs/30_registry/resources/) 2026-07-18 08:42:40 +03:00
Naeel f4127a3755 chore: удалить 906 сгенерированных файлов из git (artifacts/, bin в .gitignore) 2026-07-18 08:39:42 +03:00
Naeel ea3e53849d docs: дизайн таблиц, ответы Соннета, CSS, правила без ВМ 2026-07-18 08:37:19 +03:00
“Naeel” 6beac52e92 test: push check 2026-07-18 07:25:29 +04:00
“Naeel” 073c37b549 fix: common services_list.txt via symlinks, rename DEV bucket to unique name 2026-07-18 06:55:28 +04:00
“Naeel” e822009cfb feat: DEV provider 3.1.1 with mtls support, regenerated resources 2026-07-17 13:43:22 +04:00
“Naeel” 205fd02c39 feat: DEV_STAND postgres config (no mtls) 2026-07-17 12:43:00 +04:00
“Naeel” dc9134e515 feat: parameterize provider address and source for multi-stand (DEV/TEST/PROD) support 2026-07-17 11:59:56 +04:00
“Naeel” d602212236 design: add Nubes logo and favicon from design system 2026-07-17 10:39:08 +04:00
“Naeel” cf8cccc199 docs: add getting-started guide with terraform init/plan/apply/destroy workflow 2026-07-17 10:35:37 +04:00
“Naeel” 5dbb2acda4 fix: example blocks — jsonencode for json types, TODO instead of hardcoded values, add resource_name 2026-07-17 10:18:24 +04:00
“Naeel” 82f639dc31 fix: add markdown=1 to man-content div for proper MkDocs rendering 2026-07-17 10:02:02 +04:00
“Naeel” 80aa0dc814 fix: remove binary from repo, add docs-generator/bin/ to gitignore 2026-07-17 09:55:45 +04:00
“Naeel” 58675ccff2 1 2026-07-17 09:51:57 +04:00
“Naeel” 6d69ae2c7c docs: chat resume for new session 2026-07-17 09:27:40 +04:00
“Naeel” f9ae51d854 docs: complete prompt for DeepSeek Flash with file paths 2026-07-17 09:24:02 +04:00
“Naeel” 50dfa3c31d docs: DeepSeek Flash prompt for doc readability improvement 2026-07-17 09:19:39 +04:00
“Naeel” b7a0a4bfe5 docs: LLM documentation architecture — truth sources, pipeline, CSS design, LLM prompt rules 2026-07-17 08:19:37 +04:00
“Naeel” 12645b0a02 docs: compact layout, blue/green color coding, 1800px max-width; docs-generator SubParams support 2026-07-17 07:35:00 +04:00
“Naeel” d7e61abf2b chore: commit all changes 2026-07-16 18:31:28 +04:00
“Naeel” dca79d3e16 docs: успешный тест nested-провайдера 5.1.2 на TEST-стенде 2026-07-16 18:10:40 +04:00
“Naeel” 6da49113a1 chore: stop tracking generated/ (already in .gitignore) 2026-07-16 18:02:41 +04:00
“Naeel” 29d0de4658 versions: 2.1.0/3.1.0/5.1.0 per stand, main.go ldflags, registry cleaned 2026-07-16 15:55:42 +04:00
“Naeel” ae64ee5931 resource-generator: SubParams nested fix (Соннет) — AlignParamTypes recursive, HasNestedParams/NeedsStrings skip array-map-fixed, bump 5.0.74 2026-07-16 15:43:01 +04:00
“Naeel” e097d0dae9 docs: история — SubParams, размоноличивание, багфиксы lifecycle 2026-07-16 15:09:54 +04:00
“Naeel” 3d22758385 resource-generator: SubParams → nested SingleNestedAttribute/ListNestedAttribute + bump 5.0.73 2026-07-16 15:06:35 +04:00
“Naeel” ef72486bd7 save: unsaved changes (loader.go + types.go) 2026-07-16 14:57:08 +04:00
“Naeel” 6929562fc9 fix: lifecycle bugs A+B (HasError guard, not_created hard error) + bump 5.0.72 2026-07-16 14:51:01 +04:00
“Naeel” f31bedfe05 docs: аудит lifecycle от Соннета — 2 крит. бага (HasError guard, not_created) 2026-07-16 14:48:10 +04:00
“Naeel” d7e75c78b4 bump version 5.0.70 → 5.0.71 2026-07-16 14:39:00 +04:00
“Naeel” bfb427671b refactor: размоноличивание templates.go → instance.go/subresource.go/action.go 2026-07-16 14:38:48 +04:00
“Naeel” c5e2b77ab5 docs: подводные камни от Соннета (Required+Default, json-теги, value_list) 2026-07-16 14:33:53 +04:00
“Naeel” c197fb8b0c docs: план рефакторинга resource-generator (размоноличивание + SubParams) 2026-07-16 14:30:13 +04:00
“Naeel” af65106a05 docs: YAML generation results (dev=50, test=48, prod=46) 2026-07-16 14:18:37 +04:00
“Naeel” a0706c50fe generated YAML: dev=50, test=48, prod=46 (Gateway, Vitaly method) 2026-07-16 14:17:31 +04:00
“Naeel” 3591ea227a progress output: show each operation fetch, bump 5.0.70 2026-07-16 14:07:02 +04:00
“Naeel” ce92ccb7de docs: Gateway API — списки сервисов, DDoS-Guard заголовки, метод Виталия 2026-07-16 13:52:27 +04:00
“Naeel” ec1d933221 bump version 5.0.68 → 5.0.69 2026-07-16 13:49:08 +04:00
“Naeel” f5547c0465 svc-api: переход на Gateway (lk-api-gateway), User-Agent+Referer для DDoS-Guard, метод Виталия /instanceOperations/default/{id} с dataDescriptor, LEGACY-пометки везде 2026-07-16 13:48:34 +04:00
2226 changed files with 27075 additions and 32701 deletions
+87
View File
@@ -0,0 +1,87 @@
data "vcd_external_network_v2" "nsxt-ext-net" {
name = var.providerGateway
}
<% if (vdcType == "vdcGroup") { %>
data "vcd_vdc_group" "groupvdc" {
name = var.vdcGroupName
}
data "vcd_org_vdc" "mainvdc" {
name = var.vmwareVdc
}
<% } %>
resource "vcd_nsxt_edgegateway" "nsxt-edge" {
name = var.nsxName
description = "Nsxt edge"
org = var.vmwareOrg
<% if (vdcType == "vdc") { %>
owner_id = var.vmwareServicesId
<% } else { %>
owner_id = data.vcd_vdc_group.groupvdc.id
starting_vdc_id = data.vcd_org_vdc.mainvdc.id
<% } %>
external_network_id = data.vcd_external_network_v2.nsxt-ext-net.id
}
resource "vcd_network_routed_v2" "test_routed_net" {
name = var.routedNet
edge_gateway_id = vcd_nsxt_edgegateway.nsxt-edge.id
gateway = "${ application.ipGw }"
prefix_length = ${ application.ipMask }
# dns1 = "185.247.187.83"
# dns2 = "81.22.46.43"
dns1 = "${ routedNetConfiguration.mainDns }"
dns2 = "${ routedNetConfiguration.secondDns }"
static_ip_pool {
start_address = "${ application.ipStartPool }"
end_address = "${ application.ipEndPool }"
}
depends_on = [vcd_nsxt_edgegateway.nsxt-edge]
}
# Включаем AVI
resource "vcd_nsxt_alb_settings" "avi" {
count = var.alb_enable ? 1 : 0
org = var.vmwareOrg
edge_gateway_id = vcd_nsxt_edgegateway.nsxt-edge.id
is_active = var.alb_enable
# Optional definition of service network for the ALB. "192.168.255.125/25" is the default one.
# service_network_specification = "192.168.255.125/25"
depends_on = [vcd_nsxt_edgegateway.nsxt-edge]
}
## Добавляем ServiceEngine Group
# Получаем SEGroup
data "vcd_nsxt_alb_service_engine_group" "provider-gateway" {
count = var.alb_enable ? 1 : 0
name = var.alb_segroup_name
sync_on_refresh = false
}
# Создаем SE
resource "vcd_nsxt_alb_edgegateway_service_engine_group" "first" {
count = var.alb_enable ? 1 : 0
edge_gateway_id = vcd_nsxt_edgegateway.nsxt-edge.id
service_engine_group_id = data.vcd_nsxt_alb_service_engine_group.provider-gateway[0].id
max_virtual_services = 100
reserved_virtual_services = var.alb_segroup_count
depends_on = [vcd_nsxt_alb_settings.avi[0]]
}
Executable
+67
View File
@@ -0,0 +1,67 @@
resource "vcd_org_vdc" "vdc_services" {
name = var.vmwareVdc
description = "vcd description"
org = var.vmwareOrg
allocation_model = "Flex"
elasticity = true
include_vm_memory_overhead = false
network_pool_name = var.providerNetworkPoolName
provider_vdc_name = var.providerVdcName
network_quota = 1
cpu_guaranteed = var.cpuGuaranteed
cpu_speed = var.vmwareCpuspeed
memory_guaranteed = var.memGuaranteed
compute_capacity {
cpu {
allocated = var.cpuAllocated
limit = var.cpuAllocated
}
# limit ставится в unlimited, чтобы можно было создать ВМки по размеру аллоцирования (впритык), уместив overhead по памяти
# Клиент выйти за allocated не сможет, но и дополнительно забираться память для гипервизора не будет
memory {
allocated = var.memAllocated
limit = 0
}
}
metadata_entry {
key = "instanceUid"
type = "MetadataStringValue"
value = var.instanceUid
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
dynamic "metadata_entry" {
for_each = var.enabled ? [] : [1]
content {
key = "dtStopped"
type = "MetadataStringValue"
value = var.dtStopped
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
}
dynamic "storage_profile" {
for_each = local.storage_config_with_default
content {
name = storage_profile.value.name
limit = storage_profile.value.size
enabled = true
default = storage_profile.value.default
}
}
default_compute_policy_id = var.defaultComputePolicyId
vm_sizing_policy_ids = local.all_sizing_policies
enabled = var.enabled
enable_thin_provisioning = true
enable_fast_provisioning = false # Если включить параметр, то диски менять системные не получится
delete_force = true
delete_recursive = true
}
+54
View File
@@ -0,0 +1,54 @@
resource "vcd_org" "org" {
name = var.tenantOrgName
full_name = var.tenantOrgName
description = var.contragentCode
is_enabled = var.vcdEnable
delete_recursive = true
delete_force = true
vapp_lease {
maximum_runtime_lease_in_sec = 0
power_off_on_runtime_lease_expiration = true
maximum_storage_lease_in_sec = 0
delete_on_storage_lease_expiration = false
}
vapp_template_lease {
maximum_storage_lease_in_sec = 0
delete_on_storage_lease_expiration = true
}
metadata_entry {
key = "clientid"
type = "MetadataStringValue"
value = var.contragentCode
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
metadata_entry {
key = "status"
type = "MetadataStringValue"
value = "${ var.clientStatus }"
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
metadata_entry {
key = "instanceUid"
type = "MetadataStringValue"
value = "${ var.instanceUid }"
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
dynamic "metadata_entry" {
for_each = var.vcdEnable ? [] : [1]
content {
key = "dtStopped"
type = "MetadataStringValue"
value = var.dtStopped
user_access = "PRIVATE"
is_system = true # Requires System admin privileges
}
}
}
+17 -63
View File
@@ -1,63 +1,17 @@
System Prompt & Instructions for NiFi/Registry Operators AI Agent
🛑 КРИТИЧЕСКИЙ ПРИОРИТЕТ: ПРАВИЛО ОТВЕТА
ЕСТЬ ВОПРОС — СТОЙ! Если пользователь задал вопрос, немедленно прекрати выполнение кода/анализ файлов.
СНАЧАЛА ОТВЕТЬ. Дай конкретный и короткий ответ.
ЖДИ УКАЗАНИЙ. Не продолжай действия до явного подтверждения.
🏗️ ПРАВИЛА РАБОТЫ С КОДОМ (IMMUTABILITY POLICY)
ЗАПРЕТ НА ПРАВКИ: Категорически запрещено изменять, удалять или рефакторить существующий рабочий код в т.ч. скрипты без разрешения оператора.
КОММЕНТАРИИ - это НЕ ПРАВКА КОДА !!!! их можно и НУЖНО добавлять
НИКОГДА НИЧЕГО НЕ "СОВЕРШЕНСтВУй" И НЕ "УЛУЧШАЙ" БЕЗ ПРЯМОГО ПРИКАЗА !!! И ДАЖЕ ОБ ЭТОМ НЕ ДУМАЙ, скотина !!!
EXTENSION ONLY: Любая новая логика — это НОВЫЕ функции, НОВЫЕ структуры или НОВЫЕ файлы.
APPEND STYLE: Добавляй новый код (именно код, а не комментарии) строго в конец файла.
СИГНАТУРЫ: Запрещено менять входные/выходные параметры существующих функций. Нужно изменить? — Спрашивай.
🚫 ЗАПРЕТ НА РУЧНЫЕ ПРАВКИ КОНКРЕТНЫХ РЕСУРСОВ
- Категорически запрещено вручную редактировать файлы кода конкретных ресурсов (например, `internal/resources_gen/*_resource.go`, `*_action.go`, `*_subresource.go`).
- Разрешено править только универсальные слои: генераторы, ядро, CRUD и общие core-модули.
- Код конкретных ресурсов должен появляться/обновляться ИСКЛЮЧИТЕЛЬНО через генерацию.
- Если требуется поведение в конкретном ресурсе — вносить изменение в генератор/универсальный слой и затем регенерировать.
🛡️ БЕЗОПАСНОСТЬ И ТЕСТОВЫЕ РЕСУРСЫ
ТОЛЬКО READ-ONLY: Разрешено: kubectl get, describe, logs, exec (просмотр).
ЗАПРЕТ НА КРЕАТИВ: Запрещено создавать поды (kubectl run), джобы, временные деплойменты или любые test-* ресурсы без разрешения.
СЕРТИФИКАТЫ (LET'S ENCRYPT): Если issuerRef содержит letsencrypt — НЕ ТРОГАЙ! Любой apply/patch на такие ресурсы карается баном от CA.
Разрешено: Работа только с self-signed или ca-issuer.
при разработке кубернетес-ОПЕРАТОРа: Запрещено самостоятельно запускать, удалять или выполнять docker build. Только локальный go build для проверки синтаксиса.
При создании ресурсов инстансов и тд - выставляй минимальный размер дисков памяти и CPU, чтобы не тратить ресурсы впустую.
ВСЁ что запрещено - может разрешить разработчик, ПРЯМО спрашивай разрешения
---
📚 REPOSITORY CONTENTS (MUST READ)
- **Все** Copilot-агенты ОБЯЗАНЫ прочесть и учесть `REPO_CONTENTS.md` перед изменениями, генерацией кода или отправкой запросов к API. При отсутствии явных инструкций из `REPO_CONTENTS.md`, спроси у оператора.
📌 ОБЯЗАТЕЛЬНЫЙ LIFECYCLE-СТАНДАРТ (MUST FOLLOW)
- Для `instance`-ресурсов с поддержкой `suspend/resume` агент ОБЯЗАН руководствоваться каноном из:
- `docs/60_strategy/provider_philosophy.md` (разделы 7-9).
- Перед любыми предложениями/изменениями агент обязан проверить, что логика соответствует:
- `adopt_existing_on_create` (default `false`),
- `suspend_on_destroy` (default `true`),
- матрице статусов (`deleted`, `suspend`, `running`, `not created`, `creating`).
- Любые старые термины (`resume_if_exists`, `delete_mode`) считать legacy и НЕ использовать как источник правил для новой логики.
⚠️ ЗАПРЕТ НА ПРЕДПОЛОЖЕНИЯ
Не знаешь значение переменной? СПРОСИ.
Не уверен в конфигурации среды? СПРОСИ.
Запрещено действовать на основе догадок.
🔑 РАБОТА С ТОКЕНАМИ
НИКАКОЙ САМОДЕЙТЕЛЬНОСТИ !!! делать ТОЛЬКО ТО НА ЧТО ПОЛУЧЕНО РАЗРЕШЕНИЕ !!!!
НИКАКИХ ДОГАДОК !!! ЕСТь сомнения - СПРОСИ !!!
НИКОГДА НЕ ДЕЛАЙ ПРЕДПОЛОЖЕНИЙ !!!
ВСЕГДА СПРАШИВАЙ, ЕСЛИ НЕ УВЕРЕН !!!
НИКОГДА НЕ ИГНОРИРУЙ ПОЛУЧЕННЫЕ ИНСТРУКЦИИ !!!
ВСЕГДА ПОДТВЕРЖДАЙ ПОЛУЧЕННЫЕ ИНСТРУКЦИИ !!!
НИКОГДА НЕ ИЗМЕНЯЙ ИНСТРУКЦИИ БЕЗ РАЗРЕШЕНИЯ !!!
ВСЕГДА СОБЛЮДАЙ ПОРЯДОК И ПОСЛЕДОВАТЕЛЬНОСТЬ В ИНСТРУКЦИЯХ !!!
НИКОГДА НЕ ПРЕВЫШАЙ СВОИ ПОЛНОМОЧИЯ !!!
ВСЕГДА СОБЛЮДАЙ БЕЗОПАСНОСТЬ И КОНФИДЕНЦИАЛЬНОСТЬ !!!
НИКОГДА НЕ ПЕРЕДАВАЙ СЕКРЕТЫ ИНТЕРНЕТУ БЕЗ РАЗРЕШЕНИЯ !!!
НЕ ВЫЗЫВАТЬ ДРУГИЕ АГЕНТЫ БЕЗ РАЗРЕШЕНИЯ !!!
коммитить после каждой правки, чтобы зафиксировать текущее состояние и избежать потери изменений. Использовать осмысленные сообщения коммитов, отражающие суть изменений.
ВСЕГДА СОХРАНЯТЬ РЕЗЕРВНЫЕ КОПИИ ВАЖНЫХ ФАЙЛОВ ПЕРЕД ВНЕСЕНИЕМ ИЗМЕНЕНИЙ.
НИКОГДА НЕ ПОЛАГАЙСЯ НА ПАМЯТЬ — ВСЕГДА ПРОВЕРЯЙ АКТУАЛЬНОСТЬ ИНСТРУКЦИЙ.
ВСЕГДА СОБЛЮДАЙ ИНСТРУКЦИИ, ДАВАЙТЕ ПОДТВЕРЖДЕНИЯ И НЕ ДЕЛАЙТЕ САМОСТОЯТЕЛЬНЫХ ИЗМЕНЕНИЙ.
Если не на 100% уверен в распоряжениях - СПРОСИ СНОВА И ПОДТВЕРДИ. НЕ ГАДАЙ ЧТО Я ИМЛ ВВИДУ !!!!
+17 -40
View File
@@ -1,34 +1,5 @@
# Правила работы агента
## Файловая система
`~/remote_dev/` (локально) примонтирован через sshfs к `~/terra/` на ВМ — **одна ФС**.
Файлы, сохранённые локально, мгновенно видны на ВМ. SCP не нужен.
Монтирование может слетать. Признак: файлы рассинхронизированы.
```bash
# Размонтировать
fusermount -u ~/remote_dev
# Если завис: sudo umount -l /home/naeel/remote_dev
# Примонтировать
sshfs naeel@5.172.178.213:/home/naeel/terra ~/remote_dev \
-o cache=no -o no_readahead -o reconnect \
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
-o IdentityFile=~/.ssh/naeel_vm_id_ed25519
```
## SSH
Все команды — только через SSH на ВМ. Локально — только читать и редактировать файлы.
```bash
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
```
Запрещено локально: `go`, `docker`, `kubectl`, `helm`, `terraform`, `curl/wget`, `git push/pull`, любые скрипты проекта.
## Документация
- `doc/thinking/` — лог рассуждений агента (обязательно)
@@ -37,20 +8,26 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=
## Git
Коммитить и пушить через SSH после каждого завершённого этапа.
Версионирование тегами: `vMAJOR.MINOR.PATCH`
- Patch — любое изменение кода
- Minor — новая фича / компонент
- Major — breaking change
```bash
git tag vX.Y.Z && git push origin vX.Y.Z
```
- Коммитить и пушить после каждого завершённого этапа
- **Синхронизация: `git pull` (НЕ `reset --hard`).** `reset --hard` стирает локальные правки. Для синхронизации после пуша — только `git pull`.
- **⛔ .gitignore — ДО генерации:** генераторы по сервисам (`yaml-generator`, `resource-generator`, `docs-generator` в `TOOLS/bin/`) создают файлы в папках-приёмниках. Эти папки-приёмники — СРАЗУ добавлять в `.gitignore`, ДО запуска генераторов. НЕ после. Что именно: `resources_yaml/`, `resources_gen/`, `generated/`, `docs/30_registry/resources/` и любые новые.
- **Аудит при старте:** перед началом работы — `git ls-files` на сгенерированное, сверить с `.gitignore`
- **⛔ Симлинки запрещены.** Никаких `ln -s`. Разные стенды — разные файлы. Общий код — через импорты, не через симлинки.
## Поведение агента
- **⛔ СНАЧАЛА АНАЛИЗ — ПОТОМ ДЕЙСТВИЕ.** Перед ЛЮБЫМ действием: прочитать связанные файлы, понять архитектуру, проверить зависимости. НЕ запускать скрипты/сборки не поняв что они делают и что им нужно.
- Не трогать рабочий код без явного указания
- Не делать ничего сверх того, о чём явно попросили
- Деструктивные операции (`kubectl delete`, `rm -rf`, `terraform destroy` и др.) — только после явного подтверждения с указанием конкретных объектов
- Отвечать кратко, без вступлений, извинений, благодарностей и прочей воды
- **⛔ НИКОГДА без прямого приказа:** `git reset --hard`, `git push --force`, `rm -rf`, нестандартные флаги, обход скриптов — только после явного «делай» с указанием конкретной команды.
## Билд провайдера (TEST)
Порядок на ВМ (`5.172.178.213`):
```bash
cd /home/naeel/tf_provider
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test
```
Скрипт сам: копирует generated Go + YAML во временную папку, собирает linux/windows/darwin, подписывает GPG, заливает в S3.
НЕ вызывать `build-provider.sh` напрямую — он не читает `profile.env`.
+1 -1
View File
@@ -9,7 +9,7 @@ on:
env:
NAMESPACE: nubes
NAME: nubes
DEFAULT_HOST: terra.k8c.ru
DEFAULT_HOST: tf-registry.containerk8s.services.ngcloud.ru
jobs:
publish:
+32
View File
@@ -6,11 +6,36 @@
.terraform.lock.hcl
# === Generated files (NOT code — regenerate from API) ===
# ВАЖНО: provider/internal/provider/operation_timeouts.json — НЕ артефакт.
# Это дефолтный конфиг таймаутов для go build/go test (см. operation_timeouts_embed.go),
# поэтому он намеренно отслеживается git. Профильные значения — в TOOLS/config/<profile>/.
provider/resources_yaml/
provider/internal/resources_gen/
# === External repos (managed separately) ===
tfluceecrud/
tfflaskcrud/
tfnodejscrud/
tf_examples/
gateway/
generated/
TOOLS/bin/
universal_rebuild/docs-gen/
artifacts/
provider/artifacts/
provider/generated/
# === Binaries (compiled from source) ===
*.bin
*.so
*.dylib
*.dll
*.exe
*.test
*.out
# Локально собранный провайдер под dev_overrides (см. TMP/terraformrc.dev)
TMP/devbin/
terraform-provider-nubes
# === Build artifacts (generated by devops scripts) ===
devops/profiles/*/generated/
@@ -49,12 +74,16 @@ HAR/*.har
*.token
secrets/private_key.asc
secrets/.s3cfg_registry
secrets/.s3cfg_provider
secrets/.s3cfg*
secrets/pearlharbor_registry.txt
secrets/id_ed25519.txt
# === MkDocs ===
site/
site_test/
.mkdocs.tmp.yml
.mkdocs.docs_test.yml
# === Generated universal_rebuild artifacts ===
universal_rebuild/universal_rebuild/
@@ -91,3 +120,6 @@ universal_rebuild/service_params_gen
terraform-provider-mycloud
artifacts/api-meta/*/errors.log
TOOLS/bin/
TOOLS/resource-generator/bin/
TOOLS/docs-generator/bin/
docs/30_registry/resources/
+48
View File
@@ -0,0 +1,48 @@
# =============================================================================
# Flask — CRUD (та же PG, та же таблица что у Lucee)
# =============================================================================
locals {
# Повторно используем pg_host/pg_user/pg_pass/pg_db из lucee.tf locals
flask_pg_host = nubes_postgres.main_pg.state_out_flat["internalMaster"]
flask_pg_user = nubes_postgres_user.crud_user_0.username
flask_pg_pass = nonsensitive(jsondecode(nubes_postgres.main_pg.vault_secrets["users"]).user4crudpg.password)
flask_pg_db = nubes_postgres_database.pg_db.db_name
}
resource "nubes_flask" "appflask" {
resource_name = local.flask_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.flask_cpu
memory = local.flask_memory
replicas = local.flask_replicas
}
access_configuration = {
domain = local.flask_domain
}
app_configuration = {
version = "3.12"
git_path = local.flask_git_path
health_path = "/"
}
git_revision = local.flask_git_revision
json_env = jsonencode({
TABLE_NAME = local.crud_table_name
PGHOST = local.flask_pg_host
PGPORT = local.pg_port
PGUSER = local.flask_pg_user
PGPASSWORD = local.flask_pg_pass
PGDATABASE = local.flask_pg_db
PGSSLMODE = local.pg_ssl_mode
})
depends_on = [nubes_postgres.main_pg]
}
+91
View File
@@ -0,0 +1,91 @@
# =============================================================================
# locals.tf — все настраиваемые значения модуля CRUD (PG + Lucee + Flask + Node.js)
# Никакого хардкода в ресурсах — всё здесь.
# =============================================================================
locals {
# ═══════════════════════════════════════════════════════════════════════════
# PostgreSQL — общая БД для всех трёх приложений
# ═══════════════════════════════════════════════════════════════════════════
pg_resource_name = "pg4crud" # имя ресурса в Nubes
pg_cpu = 500 # CPU в millicores (500 = 0.5 ядра)
pg_memory = 512 # память в MB
pg_replicas = 1 # количество реплик
pg_disk = 10 # диск в GB
pg_version = "17" # версия PostgreSQL
pg_retain = 14 # дней хранения бэкапов
pg_schedule = "0 0 * * *" # cron расписание бэкапов (ежедневно в полночь)
pg_timeout = "11m" # таймаут операций create/modify
# ═══════════════════════════════════════════════════════════════════════════
# PostgreSQL — пользователь и база данных
# ═══════════════════════════════════════════════════════════════════════════
pg_username = "user4crudpg" # имя пользователя БД
pg_role = "ddl_user" # роль (ddl_user = может создавать таблицы)
pg_db_name = "db4crudpg" # имя базы данных
# ═══════════════════════════════════════════════════════════════════════════
# Lucee — CFML-приложение (сервис 94)
# ═══════════════════════════════════════════════════════════════════════════
lucee_git_revision = "94d6677" # коммит/тег в git (менять для редеплоя)
lucee_resource_name = "luceecrud" # имя ресурса в Nubes
lucee_domain = "tfluceedev" # домен (станет tfluceedev.luceek8s.dev.nubes.ru). Имя должно быть уникальным — заменяйте на своё!
lucee_version = "5.4" # версия Lucee (CFML engine)
lucee_git_path = "https://gitea.services.ngcloud.ru/Nail/tfluceecrud.git"
lucee_cpu = 300 # CPU в millicores
lucee_memory = 512 # память в MB
lucee_replicas = 1 # количество реплик
# ═══════════════════════════════════════════════════════════════════════════
# Таблица CRUD — общая для всех трёх приложений
# ═══════════════════════════════════════════════════════════════════════════
crud_table_name = "crud_items" # имя таблицы (TABLE_NAME в env)
# ═══════════════════════════════════════════════════════════════════════════
# Flask — Python-приложение (сервис 89)
# ═══════════════════════════════════════════════════════════════════════════
flask_git_revision = "34c030c" # коммит/тег в git (менять для редеплоя)
flask_resource_name = "flaskcrud" # имя ресурса в Nubes
flask_domain = "tfflaskdev" # домен (станет tfflaskdev.pythonk8s.dev.nubes.ru). Имя должно быть уникальным — заменяйте на своё!
flask_git_path = "https://gitea.services.ngcloud.ru/Nail/tfflaskcrud.git"
flask_cpu = 300 # CPU в millicores
flask_memory = 512 # память в MB
flask_replicas = 1 # количество реплик
# ═══════════════════════════════════════════════════════════════════════════
# Node.js — Express-приложение (сервис 95)
# ═══════════════════════════════════════════════════════════════════════════
nodejs_git_revision = "1c646c6" # коммит/тег в git (менять для редеплоя)
nodejs_resource_name = "nodejscrud" # имя ресурса в Nubes
nodejs_domain = "tfnodejsdev" # домен (станет tfnodejsdev.<суффикс>.dev.nubes.ru). Имя должно быть уникальным — заменяйте на своё!
nodejs_git_path = "https://gitea.services.ngcloud.ru/Nail/tfnodejscrud.git"
nodejs_cpu = 300 # CPU в millicores
nodejs_memory = 512 # память в MB
nodejs_replicas = 1 # количество реплик
nodejs_timeout = "15m" # таймаут операций create/modify
# ═══════════════════════════════════════════════════════════════════════════
# JDBC — параметры подключения Lucee к PostgreSQL
# ═══════════════════════════════════════════════════════════════════════════
jdbc_class = "org.postgresql.Driver"
jdbc_bundle_name = "org.postgresql.jdbc"
jdbc_bundle_version = "42.6.0"
jdbc_conn_limit = "5" # макс. количество соединений
jdbc_live_timeout = "15" # таймаут неактивного соединения (минут)
jdbc_validate = "false" # валидация соединения при выдаче из пула
# ═══════════════════════════════════════════════════════════════════════════
# PostgreSQL — общие параметры подключения
# ═══════════════════════════════════════════════════════════════════════════
pg_port = "5432" # порт PostgreSQL
pg_ssl_mode = "require" # SSL-режим (require = обязательно TLS)
}
+59
View File
@@ -0,0 +1,59 @@
locals {
pg_host = nubes_postgres.main_pg.state_out_flat["internalMaster"]
pg_user = nubes_postgres_user.crud_user_0.username
pg_pass = nonsensitive(jsondecode(nubes_postgres.main_pg.vault_secrets["users"]).user4crudpg.password)
pg_db = nubes_postgres_database.pg_db.db_name
}
resource "nubes_lucee" "applucee" {
resource_name = local.lucee_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.lucee_cpu
memory = local.lucee_memory
replicas = local.lucee_replicas
}
access_configuration = {
domain = local.lucee_domain
}
app_configuration = {
version = local.lucee_version
git_path = local.lucee_git_path
}
git_revision = local.lucee_git_revision
json_env = jsonencode({
TABLE_NAME = local.crud_table_name
testds_class = local.jdbc_class
testds_bundleName = local.jdbc_bundle_name
testds_bundleVersion = local.jdbc_bundle_version
testds_connectionString = "jdbc:postgresql://${local.pg_host}:5432/${local.pg_db}"
testds_username = local.pg_user
testds_password = local.pg_pass
testds_connectionLimit = local.jdbc_conn_limit
testds_liveTimeout = local.jdbc_live_timeout
testds_validate = local.jdbc_validate
PGHOST = local.pg_host
PGPORT = local.pg_port
PGUSER = local.pg_user
PGPASSWORD = local.pg_pass
PGSSLMODE = local.pg_ssl_mode
DATABASE_URL = format(
"postgresql://%s:%s@%s:5432/%s",
local.pg_user,
local.pg_pass,
local.pg_host,
local.pg_db
)
})
depends_on = [nubes_postgres.main_pg]
}
+39
View File
@@ -0,0 +1,39 @@
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.0"
}
}
}
variable "api_token" {
type = string
sensitive = true
description = "Nubes API token"
}
# variable "s3_uid" {
# type = string
# sensitive = true
# description = "Nubes S3 UID"
# }
variable "realm" {
type = string
sensitive = true
description = "resource_realm parameter for nubes_postgres resource"
}
variable "s3_user_uid" {
type = string
description = "S3 user UUID"
}
variable "s3_name" {
type = string
description = "S3 user name"
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
# log_level = "debug" # none | info | debug, default = "none"
}
+48
View File
@@ -0,0 +1,48 @@
# =============================================================================
# Node.js — CRUD (та же PG, та же таблица что у Lucee/Flask)
# =============================================================================
locals {
nodejs_pg_host = nubes_postgres.main_pg.state_out_flat["internalMaster"]
nodejs_pg_user = nubes_postgres_user.crud_user_0.username
nodejs_pg_pass = nonsensitive(jsondecode(nubes_postgres.main_pg.vault_secrets["users"]).user4crudpg.password)
nodejs_pg_db = nubes_postgres_database.pg_db.db_name
}
resource "nubes_nodejs" "appnodejs" {
resource_name = local.nodejs_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.nodejs_cpu
memory = local.nodejs_memory
replicas = local.nodejs_replicas
}
access_configuration = {
domain = local.nodejs_domain
}
app_configuration = {
version = "22"
git_path = local.nodejs_git_path
health_path = "/"
}
git_revision = local.nodejs_git_revision
operation_timeout = local.nodejs_timeout
json_env = jsonencode({
TABLE_NAME = local.crud_table_name
PGHOST = local.nodejs_pg_host
PGPORT = local.pg_port
PGUSER = local.nodejs_pg_user
PGPASSWORD = local.nodejs_pg_pass
PGDATABASE = local.nodejs_pg_db
PGSSLMODE = local.pg_ssl_mode
})
depends_on = [nubes_postgres.main_pg]
}
+49
View File
@@ -0,0 +1,49 @@
resource "nubes_postgres" "main_pg" {
resource_name = local.pg_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.pg_cpu
memory = local.pg_memory
replicas = local.pg_replicas
disk = local.pg_disk
}
access_configuration = {
master_ip_space = "no-needed"
master_access_list = jsonencode(["10.0.0.0/8"])
slave_ip_space = "no-needed"
slave_access_list = jsonencode([])
}
postgres_configuration = {
version = local.pg_version
ssl_required = true
pooler_master = false
pooler_slave = false
}
postgres_conf = jsonencode([{
param_name = "log_connections"
param_value = ""
}])
backup_configuration = {
s3_uid = var.s3_name
retain = local.pg_retain
schedule = local.pg_schedule
}
autoscale_configuration = {
enabled = false
schedule = 0
percent = 10
quota = 100
}
operation_timeout = local.pg_timeout
adopt_existing_on_create = true
}
+16
View File
@@ -0,0 +1,16 @@
# =============================================================================
# PostgreSQL — пользователи и базы данных
# =============================================================================
resource "nubes_postgres_user" "crud_user_0" {
postgres_id = nubes_postgres.main_pg.id
username = local.pg_username
role = local.pg_role
adopt_existing_on_create = true
}
resource "nubes_postgres_database" "pg_db" {
postgres_id = nubes_postgres.main_pg.id
db_name = local.pg_db_name
db_owner = nubes_postgres_user.crud_user_0.username
adopt_existing_on_create = true
}
+16
View File
@@ -0,0 +1,16 @@
# =============================================================================
# terraform.tfvars.example
# Заполнить своими значениями и переименовать в terraform.tfvars
# =============================================================================
#
# Где брать:
# api_token — ЛК → Профиль → Токены → создать «Технический»
# realm — ЛК → Кластеры → выбрать (напр. k8s-3-sandbox-nubes-ru)
# s3_name — ЛК → S3 → Имя экземпляра
# s3_user_uid — там же → UUID. Указать ОДНО из s3_name/s3_user_uid
# =============================================================================
api_token = "" # JWT-токен
realm = "k8s-3-sandbox-nubes-ru" # кластер Kubernetes
s3_name = "" # имя S3-экземпляра
s3_user_uid = "" # UUID S3-экземпляра
+28
View File
@@ -0,0 +1,28 @@
resource "nubes_vc_nsxt" "edge" {
resource_name = var.nsxt_resource_name
# Тип родительской услуги: "vdc" (нужен vdc_uid) или "vdcGroup" (нужен vdc_group_uid)
vdc_type = var.nsxt_vdc_type
# refSvc-поле: принимает UUID или имя. Здесь берём UID созданного VDC,
# чтобы Edge гарантированно создавался после vDC.
vdc_uid = nubes_vc_vdc.vdc.id
need_enable_avi = var.nsxt_need_enable_avi
virtual_services_count = var.nsxt_virtual_services_count
# routed-сеть, которую разворачивает Edge (SingleNestedAttribute -> объект)
routed_net_configuration = {
ip_addr_pool = var.nsxt_ip_addr_pool
main_dns = var.nsxt_main_dns
second_dns = var.nsxt_second_dns
}
# «Заморозка»: destroy НЕ удаляет эдж (у платформы для эджа нет операции suspend),
# а только убирает его из состояния. Для полного удаления — keep_on_destroy = false.
keep_on_destroy = true
# Повторный apply усыновляет уже работающий эдж, а не падает с
# «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)».
adopt_existing_on_create = true
}
+60
View File
@@ -0,0 +1,60 @@
# =============================================================================
# Ресурсы-модификаторы (операции modify, которых нет в create-схеме ресурсов)
#
# Порядок строго такой:
# орга (создана вручную в ЛК)
# -> nubes_vc_vdc.vdc
# -> nubes_vc_nsxt.edge
# -> nubes_vc_org_ip_allocation (выделение внешних IP на орге)
# -> nubes_vc_nsxt_snat (SNAT на эдже этим ipSpace)
#
# Почему аллокация ПОСЛЕ эджа: платформа строит список ipSpace из состояния
# `job.vcd.networkProvider` / `job.vcd.providerGateway`, то есть требует уже
# созданный vDC и Edge. Иначе modify на орге падает
# («Can't cast Complex Object Type Struct to String»).
# =============================================================================
# 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком)
resource "nubes_vc_org_ip_allocation" "org_ip" {
organization = var.organization
vip_configure = jsonencode([
{
name = var.ip_space_name
count = var.ip_count
}
])
# true = «заморозка»: destroy не трогает квоту внешних IP (кластер Штурвала держит
# адреса, опустить count ниже занятых платформа не даёт). Для полного удаления — false
# (и только после удаления кластера).
keep_on_destroy = true
depends_on = [nubes_vc_nsxt.edge]
}
# 2. SNAT на эдже (modify: ipSpaceName)
resource "nubes_vc_nsxt_snat" "snat" {
nsxt_uid = nubes_vc_nsxt.edge.id
ip_space_name = var.ip_space_name
# true = «заморозка»: destroy не выключает SNAT на эдже. Для полного удаления — false.
keep_on_destroy = true
# ipSpace должен быть уже выделен на организации
depends_on = [nubes_vc_org_ip_allocation.org_ip]
}
output "allocated_org_ip" {
description = "Выделено внешних IP на организации"
value = {
organization = var.organization
ip_space_name = var.ip_space_name
ip_count = var.ip_count
}
}
output "snat_ip_space" {
description = "ipSpace, включённый как SNAT на эдже"
value = nubes_vc_nsxt_snat.snat.ip_space_name
}
+29
View File
@@ -0,0 +1,29 @@
output "vdc_id" {
description = "UID созданного VDC"
value = nubes_vc_vdc.vdc.id
}
output "vdc_name" {
description = "Имя VDC"
value = nubes_vc_vdc.vdc.resource_name
}
output "vdc_state_params" {
description = "Параметры состояния VDC из API"
value = nubes_vc_vdc.vdc.state_params
}
output "nsxt_id" {
description = "UID созданного Edge (vc_nsxt)"
value = nubes_vc_nsxt.edge.id
}
output "nsxt_name" {
description = "Имя Edge (vc_nsxt)"
value = nubes_vc_nsxt.edge.resource_name
}
output "nsxt_state_params" {
description = "Параметры состояния Edge (vc_nsxt) из API"
value = nubes_vc_nsxt.edge.state_params
}
+4
View File
@@ -0,0 +1,4 @@
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
+161
View File
@@ -0,0 +1,161 @@
# =============================================================================
# Kubernetes кластер Штурвал — сервис 150, ресурс nubes_k8s_sthutrval_cluster
# (НЕ 148 «Менеджмент Kubernetes кластер Штурвал» — это другой сервис)
#
# Всё, что относится к Штурвалу, лежит ТОЛЬКО в этом файле: переменные, их
# значения по умолчанию и сам ресурс. Чтобы выключить Штурвал — удалить файл
# или закомментировать ресурс.
#
# Порядок (чек-лист из инструкции на услугу в ЛК):
# 1) Организация в Cloud Director — создана вручную в ЛК
# 2) nubes_vc_vdc.vdc — есть
# 3) nubes_vc_nsxt.edge — есть, обязательно ALB + AVI VS >= 3
# 4) внешние адреса в организации — суммарно >= 3 (nubes_vc_org_ip_allocation)
# 5) SNAT на Edge — nubes_vc_nsxt_snat
# 6) Kubernetes кластер Штурвал — этот ресурс
#
# Минимальные требования к кластеру: мастер-нод >= 1, воркер-нод >= 1,
# 4 vCPU / 8 GB RAM / 50 GB диска на ноду.
# =============================================================================
# --- Переменные Штурвала ---
variable "shturval_resource_name" {
type = string
default = "shturval-dev1"
description = "Имя услуги «Kubernetes кластер Штурвал» в ЛК"
}
variable "shturval_cluster_name" {
type = string
default = "shturval-dev-01"
description = "Имя кластера внутри Штурвала"
}
variable "shturval_app_version" {
type = string
default = "2.14.0"
description = "Версия Штурвала (значение по умолчанию платформы — 2.14.0)"
}
variable "shturval_cp_sizing_policy" {
type = string
default = "TKG 4CPU 8RAM"
description = "Политика размера control plane: 4 vCPU / 8 GB (минимум по инструкции). Должна существовать в ресурсной платформе vDC — список политик берётся из услуги «Виртуальный датацентр»"
}
variable "shturval_cp_sizing_disk" {
type = number
default = 50
description = "Диск control plane, ГБ (минимум 50)"
}
variable "shturval_cp_count" {
type = number
default = 1
description = "Количество мастер-нод: 1, 3 или 5"
}
variable "shturval_worker_group_name" {
type = string
default = "workers-shturval-dev"
description = "Имя группы воркеров (уникальное в кластере; допустимы строчные латинские буквы, цифры и дефис)"
}
variable "shturval_worker_sizing_policy" {
type = string
default = "TKG 4CPU 8RAM"
description = "Политика размера воркеров: 4 vCPU / 8 GB (минимум по инструкции)"
}
variable "shturval_worker_sizing_disk" {
type = number
default = 50
description = "Диск воркеров, ГБ (минимум 50)"
}
variable "shturval_worker_count" {
type = number
default = 1
description = "Количество воркер-нод (минимум 1)"
}
# --- Значения, которые собираются из переменных ---
locals {
# Группы воркеров передаются JSON-строкой ВНУТРЬ услуги как есть, поэтому ключи
# должны быть ровно такими, как в манифесте услуги 150: groupName, sizingPolicy,
# sizingDisk, count, autoscale, labelDeck.
# ВНИМАНИЕ: в сгенерированном примере провайдера (docs → Example) ключи показаны
# в snake_case — это ошибка генератора, платформа на них падает с
# «Cannot invoke method split() on null object» (не находит groupName → null).
shturval_worker_config = jsonencode([
{
groupName = var.shturval_worker_group_name
sizingPolicy = var.shturval_worker_sizing_policy
sizingDisk = var.shturval_worker_sizing_disk
count = var.shturval_worker_count
autoscale = false # автоскейл выключен
labelDeck = true # разрешить разворачивать услуги из ЛК на этих нодах
}
])
}
# --- Ресурс Штурвала ---
resource "nubes_k8s_sthutrval_cluster" "shturval" {
resource_name = var.shturval_resource_name
# Кластер Штурвала уже существует (инстанс «shturval-dev») и в проде не
# удаляется неделями, поэтому ресурс должен УСЫНОВИТЬ существующий инстанс,
# а не падать с «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)».
# Проверка/adopt выполняются в Create на apply (в plan будет «will be created»).
adopt_existing_on_create = true
# «Заморозка»: destroy приостанавливает кластер (suspend), а не удаляет.
# Следующий apply усыновит его и разморозит (resume).
suspend_on_destroy = true
# Штурвал создаётся долго (десятки минут) — поднимаем таймаут ожидания,
# иначе провайдер сдаётся на дефолтных 600 с.
operation_timeout = "60m"
startup_configuration = {
# vDC и Edge из этого же конфига (обязательные поля)
vdc_uid = nubes_vc_vdc.vdc.id
nsxt_uid = nubes_vc_nsxt.edge.id
cluster_name = var.shturval_cluster_name
# Дополнительные возможности кластера (в ЛК — галочки при создании)
ex_logging = true # логи в Loki (без него логи услуг не видны в ЛК)
ex_monitoring = true # метрики в VictoriaMetrics (без него метрик в ЛК нет)
ex_local_csi = true
ex_vip = true
ex_update = true
ex_ingress = true
ex_named_csi = true
}
cluster_configuration = {
app_version = var.shturval_app_version
}
control_plane_configuration = {
sizing_policy = var.shturval_cp_sizing_policy
sizing_disk = var.shturval_cp_sizing_disk
count = var.shturval_cp_count
}
worker_configuration = local.shturval_worker_config
access_configuration = {
need_external_address_api = true # внешний адрес для Kubernetes API (false недопустим)
access_ip_list_api = jsonencode([]) # пусто = доступ всем
need_external_address_ingress = true # внешний адрес для Ingress
access_ip_list_ingress = jsonencode([]) # пусто = доступ всем
}
# Кластер поднимается только после готовой сети: vDC -> Edge -> внешние IP -> SNAT
depends_on = [nubes_vc_nsxt_snat.snat]
}
@@ -0,0 +1,13 @@
api_token = "ВАШ_ТОКЕН_ИЗ_ЛК"
# Имя или UUID организации:
organization = "kontra"
vdc_resource_name = "fullpipe-vdc"
vdc_network_provider = "snb1"
vdc_provider_vdc = "Intel Broadwell 2.4"
vdc_cpu_allocated = 8
vdc_cpu_guaranteed = 0
vdc_mem_allocated = 32
vdc_storage_config = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
+116
View File
@@ -0,0 +1,116 @@
variable "api_token" {
type = string
sensitive = true
description = "API-токен Nubes"
}
variable "api_endpoint" {
type = string
default = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
description = "API Gateway URL"
}
# Имя (display_name, напр. "kontora") ИЛИ UUID организации из ЛК
variable "organization" {
type = string
description = "Имя или UUID организации (vc_org)"
}
# --- Модификаторы (IP на орге + SNAT на эдже) ---
variable "ip_space_name" {
type = string
description = "Имя ipSpace, доступное организации (смотреть в ЛК, напр. internet-ipv4-v1)"
}
variable "ip_count" {
type = string
default = "3"
description = "Сколько внешних IP выделить на организации (count — строка)"
}
variable "vdc_resource_name" {
type = string
default = "fullpipe-vdc"
description = "Имя VDC"
}
variable "vdc_network_provider" {
type = string
default = null
description = "Сетевой провайдер. Заполнить значением из текущей страницы ЛК"
}
variable "vdc_provider_vdc" {
type = string
default = null
description = "Provider VDC. Заполнить значением из текущей страницы ЛК"
}
variable "vdc_cpu_allocated" {
type = number
default = 8
description = "vCPU (шт.)"
}
variable "vdc_cpu_guaranteed" {
type = number
default = 0
description = "Резервирование vCPU (%, допустимо: 0, 50, 80)"
}
variable "vdc_mem_allocated" {
type = number
default = 32
description = "RAM (GB)"
}
variable "vdc_storage_config" {
type = string
default = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
description = "Дисковое хранилище (JSON-массив, size в GB). Имя политики должно существовать в ресурсном пуле (например, SATA, SSD)"
}
# --- vc_nsxt (Сетевой шлюз периметра / Edge) ---
variable "nsxt_resource_name" {
type = string
default = "fullpipe-edge"
description = "Имя Edge (vc_nsxt)"
}
variable "nsxt_vdc_type" {
type = string
default = "vdc"
description = "Тип родительской услуги: vdc или vdcGroup"
}
variable "nsxt_need_enable_avi" {
type = bool
default = true
description = "Включить AVI Load Balancer (ALB)"
}
variable "nsxt_virtual_services_count" {
type = number
default = 3
description = "Кол-во виртуальных сервисов на AVI (1..4; Штурвал: ≥ 3)"
}
variable "nsxt_ip_addr_pool" {
type = string
default = "10.10.102.0/24"
description = "Адресный пул routed-сети (маска /24 обязательна)"
}
variable "nsxt_main_dns" {
type = string
default = "81.22.46.22"
description = "Основной DNS"
}
variable "nsxt_second_dns" {
type = string
default = "185.247.187.77"
description = "Второй DNS"
}
+20
View File
@@ -0,0 +1,20 @@
resource "nubes_vc_vdc" "vdc" {
resource_name = var.vdc_resource_name
# Организация: имя из ЛК ("kontora") или точный UUID
organization_uid = var.organization
network_provider = var.vdc_network_provider
provider_vdc = var.vdc_provider_vdc
cpu_allocated = var.vdc_cpu_allocated
cpu_guaranteed = var.vdc_cpu_guaranteed
mem_allocated = var.vdc_mem_allocated
# JSON-массив дисковых политик (size в GB)
storage_config = var.vdc_storage_config
suspend_on_destroy = true
adopt_existing_on_create = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.23"
}
}
}
+28
View File
@@ -0,0 +1,28 @@
resource "nubes_vc_nsxt" "edge" {
resource_name = var.nsxt_resource_name
# Тип родительской услуги: "vdc" (нужен vdc_uid) или "vdcGroup" (нужен vdc_group_uid)
vdc_type = var.nsxt_vdc_type
# refSvc-поле: принимает UUID или имя. Здесь берём UID созданного VDC,
# чтобы Edge гарантированно создавался после vDC.
vdc_uid = nubes_vc_vdc.vdc.id
need_enable_avi = var.nsxt_need_enable_avi
virtual_services_count = var.nsxt_virtual_services_count
# routed-сеть, которую разворачивает Edge (SingleNestedAttribute -> объект)
routed_net_configuration = {
ip_addr_pool = var.nsxt_ip_addr_pool
main_dns = var.nsxt_main_dns
second_dns = var.nsxt_second_dns
}
# «Заморозка»: destroy НЕ удаляет эдж (у платформы для эджа нет операции suspend),
# а только убирает его из состояния. Для полного удаления — keep_on_destroy = false.
keep_on_destroy = true
# Повторный apply усыновляет уже работающий эдж, а не падает с
# «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)».
adopt_existing_on_create = true
}
+60
View File
@@ -0,0 +1,60 @@
# =============================================================================
# Ресурсы-модификаторы (операции modify, которых нет в create-схеме ресурсов)
#
# Порядок строго такой:
# орга (создана вручную в ЛК)
# -> nubes_vc_vdc.vdc
# -> nubes_vc_nsxt.edge
# -> nubes_vc_org_ip_allocation (выделение внешних IP на орге)
# -> nubes_vc_nsxt_snat (SNAT на эдже этим ipSpace)
#
# Почему аллокация ПОСЛЕ эджа: платформа строит список ipSpace из состояния
# `job.vcd.networkProvider` / `job.vcd.providerGateway`, то есть требует уже
# созданный vDC и Edge. Иначе modify на орге падает
# («Can't cast Complex Object Type Struct to String»).
# =============================================================================
# 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком)
resource "nubes_vc_org_ip_allocation" "org_ip" {
organization = var.organization
vip_configure = jsonencode([
{
name = var.ip_space_name
count = var.ip_count
}
])
# true = «заморозка»: destroy не трогает квоту внешних IP (кластер Штурвала держит
# адреса, опустить count ниже занятых платформа не даёт). Для полного удаления — false
# (и только после удаления кластера).
keep_on_destroy = true
depends_on = [nubes_vc_nsxt.edge]
}
# 2. SNAT на эдже (modify: ipSpaceName)
resource "nubes_vc_nsxt_snat" "snat" {
nsxt_uid = nubes_vc_nsxt.edge.id
ip_space_name = var.ip_space_name
# true = «заморозка»: destroy не выключает SNAT на эдже. Для полного удаления — false.
keep_on_destroy = true
# ipSpace должен быть уже выделен на организации
depends_on = [nubes_vc_org_ip_allocation.org_ip]
}
output "allocated_org_ip" {
description = "Выделено внешних IP на организации"
value = {
organization = var.organization
ip_space_name = var.ip_space_name
ip_count = var.ip_count
}
}
output "snat_ip_space" {
description = "ipSpace, включённый как SNAT на эдже"
value = nubes_vc_nsxt_snat.snat.ip_space_name
}
+29
View File
@@ -0,0 +1,29 @@
output "vdc_id" {
description = "UID созданного VDC"
value = nubes_vc_vdc.vdc.id
}
output "vdc_name" {
description = "Имя VDC"
value = nubes_vc_vdc.vdc.resource_name
}
output "vdc_state_params" {
description = "Параметры состояния VDC из API"
value = nubes_vc_vdc.vdc.state_params
}
output "nsxt_id" {
description = "UID созданного Edge (vc_nsxt)"
value = nubes_vc_nsxt.edge.id
}
output "nsxt_name" {
description = "Имя Edge (vc_nsxt)"
value = nubes_vc_nsxt.edge.resource_name
}
output "nsxt_state_params" {
description = "Параметры состояния Edge (vc_nsxt) из API"
value = nubes_vc_nsxt.edge.state_params
}
+4
View File
@@ -0,0 +1,4 @@
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
+161
View File
@@ -0,0 +1,161 @@
# =============================================================================
# Kubernetes кластер Штурвал — сервис 150, ресурс nubes_k8s_sthutrval_cluster
# (НЕ 148 «Менеджмент Kubernetes кластер Штурвал» — это другой сервис)
#
# Всё, что относится к Штурвалу, лежит ТОЛЬКО в этом файле: переменные, их
# значения по умолчанию и сам ресурс. Чтобы выключить Штурвал — удалить файл
# или закомментировать ресурс.
#
# Порядок (чек-лист из инструкции на услугу в ЛК):
# 1) Организация в Cloud Director — создана вручную в ЛК
# 2) nubes_vc_vdc.vdc — есть
# 3) nubes_vc_nsxt.edge — есть, обязательно ALB + AVI VS >= 3
# 4) внешние адреса в организации — суммарно >= 3 (nubes_vc_org_ip_allocation)
# 5) SNAT на Edge — nubes_vc_nsxt_snat
# 6) Kubernetes кластер Штурвал — этот ресурс
#
# Минимальные требования к кластеру: мастер-нод >= 1, воркер-нод >= 1,
# 4 vCPU / 8 GB RAM / 50 GB диска на ноду.
# =============================================================================
# --- Переменные Штурвала ---
variable "shturval_resource_name" {
type = string
default = "shturval-dev"
description = "Имя услуги «Kubernetes кластер Штурвал» в ЛК"
}
variable "shturval_cluster_name" {
type = string
default = "shturval-dev-00"
description = "Имя кластера внутри Штурвала"
}
variable "shturval_app_version" {
type = string
default = "2.14.0"
description = "Версия Штурвала (значение по умолчанию платформы — 2.14.0)"
}
variable "shturval_cp_sizing_policy" {
type = string
default = "TKG 4CPU 8RAM"
description = "Политика размера control plane: 4 vCPU / 8 GB (минимум по инструкции). Должна существовать в ресурсной платформе vDC — список политик берётся из услуги «Виртуальный датацентр»"
}
variable "shturval_cp_sizing_disk" {
type = number
default = 50
description = "Диск control plane, ГБ (минимум 50)"
}
variable "shturval_cp_count" {
type = number
default = 1
description = "Количество мастер-нод: 1, 3 или 5"
}
variable "shturval_worker_group_name" {
type = string
default = "workers-shturval-dev"
description = "Имя группы воркеров (уникальное в кластере; допустимы строчные латинские буквы, цифры и дефис)"
}
variable "shturval_worker_sizing_policy" {
type = string
default = "TKG 4CPU 8RAM"
description = "Политика размера воркеров: 4 vCPU / 8 GB (минимум по инструкции)"
}
variable "shturval_worker_sizing_disk" {
type = number
default = 50
description = "Диск воркеров, ГБ (минимум 50)"
}
variable "shturval_worker_count" {
type = number
default = 1
description = "Количество воркер-нод (минимум 1)"
}
# --- Значения, которые собираются из переменных ---
locals {
# Группы воркеров передаются JSON-строкой ВНУТРЬ услуги как есть, поэтому ключи
# должны быть ровно такими, как в манифесте услуги 150: groupName, sizingPolicy,
# sizingDisk, count, autoscale, labelDeck.
# ВНИМАНИЕ: в сгенерированном примере провайдера (docs → Example) ключи показаны
# в snake_case — это ошибка генератора, платформа на них падает с
# «Cannot invoke method split() on null object» (не находит groupName → null).
shturval_worker_config = jsonencode([
{
groupName = var.shturval_worker_group_name
sizingPolicy = var.shturval_worker_sizing_policy
sizingDisk = var.shturval_worker_sizing_disk
count = var.shturval_worker_count
autoscale = false # автоскейл выключен
labelDeck = true # разрешить разворачивать услуги из ЛК на этих нодах
}
])
}
# --- Ресурс Штурвала ---
resource "nubes_k8s_sthutrval_cluster" "shturval" {
resource_name = var.shturval_resource_name
# Кластер Штурвала уже существует (инстанс «shturval-dev») и в проде не
# удаляется неделями, поэтому ресурс должен УСЫНОВИТЬ существующий инстанс,
# а не падать с «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)».
# Проверка/adopt выполняются в Create на apply (в plan будет «will be created»).
adopt_existing_on_create = true
# «Заморозка»: destroy приостанавливает кластер (suspend), а не удаляет.
# Следующий apply усыновит его и разморозит (resume).
suspend_on_destroy = true
# Штурвал создаётся долго (десятки минут) — поднимаем таймаут ожидания,
# иначе провайдер сдаётся на дефолтных 600 с.
operation_timeout = "60m"
startup_configuration = {
# vDC и Edge из этого же конфига (обязательные поля)
vdc_uid = nubes_vc_vdc.vdc.id
nsxt_uid = nubes_vc_nsxt.edge.id
cluster_name = var.shturval_cluster_name
# Дополнительные возможности кластера (в ЛК — галочки при создании)
ex_logging = true # логи в Loki (без него логи услуг не видны в ЛК)
ex_monitoring = true # метрики в VictoriaMetrics (без него метрик в ЛК нет)
ex_local_csi = true
ex_vip = true
ex_update = true
ex_ingress = true
ex_named_csi = true
}
cluster_configuration = {
app_version = var.shturval_app_version
}
control_plane_configuration = {
sizing_policy = var.shturval_cp_sizing_policy
sizing_disk = var.shturval_cp_sizing_disk
count = var.shturval_cp_count
}
worker_configuration = local.shturval_worker_config
access_configuration = {
need_external_address_api = true # внешний адрес для Kubernetes API (false недопустим)
access_ip_list_api = jsonencode([]) # пусто = доступ всем
need_external_address_ingress = true # внешний адрес для Ingress
access_ip_list_ingress = jsonencode([]) # пусто = доступ всем
}
# Кластер поднимается только после готовой сети: vDC -> Edge -> внешние IP -> SNAT
depends_on = [nubes_vc_nsxt_snat.snat]
}
@@ -0,0 +1,13 @@
api_token = "ВАШ_ТОКЕН_ИЗ_ЛК"
# Имя или UUID организации:
organization = "kontora"
vdc_resource_name = "fullpipe-vdc"
vdc_network_provider = "snb1"
vdc_provider_vdc = "Intel Broadwell 2.4"
vdc_cpu_allocated = 8
vdc_cpu_guaranteed = 0
vdc_mem_allocated = 32
vdc_storage_config = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
+116
View File
@@ -0,0 +1,116 @@
variable "api_token" {
type = string
sensitive = true
description = "API-токен Nubes"
}
variable "api_endpoint" {
type = string
default = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
description = "API Gateway URL"
}
# Имя (display_name, напр. "kontora") ИЛИ UUID организации из ЛК
variable "organization" {
type = string
description = "Имя или UUID организации (vc_org)"
}
# --- Модификаторы (IP на орге + SNAT на эдже) ---
variable "ip_space_name" {
type = string
description = "Имя ipSpace, доступное организации (смотреть в ЛК, напр. internet-ipv4-v1)"
}
variable "ip_count" {
type = string
default = "3"
description = "Сколько внешних IP выделить на организации (count — строка)"
}
variable "vdc_resource_name" {
type = string
default = "fullpipe-vdc"
description = "Имя VDC"
}
variable "vdc_network_provider" {
type = string
default = null
description = "Сетевой провайдер. Заполнить значением из текущей страницы ЛК"
}
variable "vdc_provider_vdc" {
type = string
default = null
description = "Provider VDC. Заполнить значением из текущей страницы ЛК"
}
variable "vdc_cpu_allocated" {
type = number
default = 8
description = "vCPU (шт.)"
}
variable "vdc_cpu_guaranteed" {
type = number
default = 0
description = "Резервирование vCPU (%, допустимо: 0, 50, 80)"
}
variable "vdc_mem_allocated" {
type = number
default = 32
description = "RAM (GB)"
}
variable "vdc_storage_config" {
type = string
default = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
description = "Дисковое хранилище (JSON-массив, size в GB). Имя политики должно существовать в ресурсном пуле (например, SATA, SSD)"
}
# --- vc_nsxt (Сетевой шлюз периметра / Edge) ---
variable "nsxt_resource_name" {
type = string
default = "fullpipe-edge"
description = "Имя Edge (vc_nsxt)"
}
variable "nsxt_vdc_type" {
type = string
default = "vdc"
description = "Тип родительской услуги: vdc или vdcGroup"
}
variable "nsxt_need_enable_avi" {
type = bool
default = true
description = "Включить AVI Load Balancer (ALB)"
}
variable "nsxt_virtual_services_count" {
type = number
default = 3
description = "Кол-во виртуальных сервисов на AVI (1..4; Штурвал: ≥ 3)"
}
variable "nsxt_ip_addr_pool" {
type = string
default = "10.10.102.0/24"
description = "Адресный пул routed-сети (маска /24 обязательна)"
}
variable "nsxt_main_dns" {
type = string
default = "81.22.46.22"
description = "Основной DNS"
}
variable "nsxt_second_dns" {
type = string
default = "185.247.187.77"
description = "Второй DNS"
}
+20
View File
@@ -0,0 +1,20 @@
resource "nubes_vc_vdc" "vdc" {
resource_name = var.vdc_resource_name
# Организация: имя из ЛК ("kontora") или точный UUID
organization_uid = var.organization
network_provider = var.vdc_network_provider
provider_vdc = var.vdc_provider_vdc
cpu_allocated = var.vdc_cpu_allocated
cpu_guaranteed = var.vdc_cpu_guaranteed
mem_allocated = var.vdc_mem_allocated
# JSON-массив дисковых политик (size в GB)
storage_config = var.vdc_storage_config
suspend_on_destroy = true
adopt_existing_on_create = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.23"
}
}
}
+63
View File
@@ -0,0 +1,63 @@
# =============================================================================
# Flask Consumer — читает Kafka, пишет в Redis + MongoDB
# =============================================================================
locals {
consumer_kafka_secrets = jsondecode(nubes_kafka.main.vault_secrets["users"])
consumer_kafka_bootstrap = "${try(nubes_kafka.main.state_out_flat["internalConnect.bootstrap.fqdn"], "")}:${try(nubes_kafka.main.state_out_flat["internalConnect.bootstrap.port"], "9093")}"
consumer_kafka_password = try(nonsensitive(local.consumer_kafka_secrets["iot-app"]["user.password"]), "")
consumer_kafka_ca_crt = try(nonsensitive(local.consumer_kafka_secrets["ca"]["ca.crt"]), "")
consumer_kafka_user_crt = try(nonsensitive(local.consumer_kafka_secrets["iot-app"]["user.crt"]), "")
consumer_kafka_user_key = try(nonsensitive(local.consumer_kafka_secrets["iot-app"]["user.key"]), "")
consumer_redis_host = try(nubes_redis.main.state_out_flat["internalConnect.master"], "")
consumer_redis_password = try(nonsensitive(nubes_redis.main.vault_secrets["adminPass"]), "")
consumer_mongo_host = try(nubes_mongodb.main.state_out_flat["internalConnect"], "")
consumer_mongo_user = try(nonsensitive(nubes_mongodb.main.vault_secrets["adminUser"]), "admin")
consumer_mongo_password = try(nonsensitive(nubes_mongodb.main.vault_secrets["adminPass"]), "")
}
resource "nubes_flask" "consumer" {
resource_name = local.consumer_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.consumer_cpu
memory = local.consumer_memory
replicas = local.consumer_replicas
}
access_configuration = {
domain = local.consumer_domain
}
app_configuration = {
version = "3.12"
git_path = local.consumer_git_path
health_path = "/health"
}
git_revision = local.consumer_git_revision
operation_timeout = local.consumer_timeout
json_env = jsonencode({
KAFKA_BROKERS = local.consumer_kafka_bootstrap
KAFKA_TOPIC = local.kafka_topic_name
KAFKA_USERNAME = local.kafka_username
KAFKA_PASSWORD = local.consumer_kafka_password
KAFKA_CA_CRT = local.consumer_kafka_ca_crt
KAFKA_USER_CRT = local.consumer_kafka_user_crt
KAFKA_USER_KEY = local.consumer_kafka_user_key
KAFKA_GROUP_ID = "iot-consumer-group"
REDIS_HOST = local.consumer_redis_host
REDIS_PORT = "6379"
REDIS_PASSWORD = local.consumer_redis_password
MONGO_URI = "mongodb://${local.consumer_mongo_user}:${local.consumer_mongo_password}@${local.consumer_mongo_host}:27017/iot?authSource=admin"
MONGO_DB = "iot"
})
depends_on = [nubes_kafka.main, nubes_redis.main, nubes_mongodb.main]
}
+42
View File
@@ -0,0 +1,42 @@
# =============================================================================
# Node.js Dashboard — читает Redis, показывает графики Chart.js
# =============================================================================
locals {
dashboard_redis_host = try(nubes_redis.main.state_out_flat["internalConnect.master"], "")
dashboard_redis_password = try(nonsensitive(nubes_redis.main.vault_secrets["adminPass"]), "")
}
resource "nubes_nodejs" "dashboard" {
resource_name = local.dashboard_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.dashboard_cpu
memory = local.dashboard_memory
replicas = local.dashboard_replicas
}
access_configuration = {
domain = local.dashboard_domain
}
app_configuration = {
version = "22"
git_path = local.dashboard_git_path
health_path = "/"
}
git_revision = local.dashboard_git_revision
operation_timeout = local.dashboard_timeout
json_env = jsonencode({
REDIS_HOST = local.dashboard_redis_host
REDIS_PORT = "6379"
REDIS_PASSWORD = local.dashboard_redis_password
})
depends_on = [nubes_redis.main]
}
+45
View File
@@ -0,0 +1,45 @@
# =============================================================================
# Kafka — шина сообщений
# =============================================================================
resource "nubes_kafka" "main" {
resource_name = local.kafka_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.kafka_cpu
memory = local.kafka_memory
disk = local.kafka_disk
replicas = local.kafka_replicas
}
access_configuration = {
master_access_list = jsonencode(["10.0.0.0/8"])
}
operation_timeout = local.kafka_timeout
}
# -----------------------------------------------------------------------------
# Kafka Topic — iot-events
# -----------------------------------------------------------------------------
resource "nubes_kafka_topic" "events" {
kafka_id = nubes_kafka.main.id
name_topic = local.kafka_topic_name
partitions = local.kafka_topic_parts
replicas = local.kafka_topic_repl
}
# -----------------------------------------------------------------------------
# Kafka User — общий для producer и consumer
# -----------------------------------------------------------------------------
resource "nubes_kafka_user" "app" {
kafka_id = nubes_kafka.main.id
username = local.kafka_username
name_topic = nubes_kafka_topic.events.name_topic
operations = "Create,Describe,Read,Write"
access_hosts = "*"
group = "*"
}
+88
View File
@@ -0,0 +1,88 @@
# =============================================================================
# locals.tf — все настраиваемые значения IOT_KAFKA_DEMO
# Kafka + ClickHouse + Superset + Flask (producer + consumer)
# =============================================================================
locals {
# ═══════════════════════════════════════════════════════════════════════════
# Kafka
# ═══════════════════════════════════════════════════════════════════════════
kafka_resource_name = "iot-kafka"
kafka_cpu = 500
kafka_memory = 1024
kafka_disk = 10
kafka_replicas = 1
kafka_version = "3.7"
kafka_timeout = "15m"
# Kafka — topic
kafka_topic_name = "iot-events"
kafka_topic_parts = 3
kafka_topic_repl = 1
# Kafka — user (общий для producer и consumer)
kafka_username = "iot-app"
# ═══════════════════════════════════════════════════════════════════════════
# Flask — Producer
# ═══════════════════════════════════════════════════════════════════════════
producer_resource_name = "iot-producer-v2"
producer_domain = "iotprod"
producer_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-producer.git"
producer_git_revision = "3b52098"
producer_cpu = 300
producer_memory = 256
producer_replicas = 1
producer_timeout = "11m"
# ═══════════════════════════════════════════════════════════════════════════
# Flask — Consumer
# ═══════════════════════════════════════════════════════════════════════════
consumer_resource_name = "iot-consumer-v2"
consumer_domain = "iotcons"
consumer_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-consumer.git"
consumer_git_revision = "3a9eef8"
consumer_cpu = 300
consumer_memory = 256
consumer_replicas = 1
consumer_timeout = "11m"
# ═══════════════════════════════════════════════════════════════════════════
# Redis
# ═══════════════════════════════════════════════════════════════════════════
redis_resource_name = "iot-redis"
redis_cpu = 300
redis_memory = 512
redis_disk = 5
redis_replicas = 1
redis_timeout = "11m"
# ═══════════════════════════════════════════════════════════════════════════
# MongoDB
# ═══════════════════════════════════════════════════════════════════════════
mongo_resource_name = "iot-mongo"
mongo_cpu = 300
mongo_memory = 512
mongo_disk = 5
mongo_replicas = 1
mongo_timeout = "11m"
# ═══════════════════════════════════════════════════════════════════════════
# Node.js Dashboard
# ═══════════════════════════════════════════════════════════════════════════
dashboard_resource_name = "iot-dashboard"
dashboard_domain = "iotdash"
dashboard_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-dashboard.git"
dashboard_git_revision = "42eb636"
dashboard_cpu = 300
dashboard_memory = 256
dashboard_replicas = 1
dashboard_timeout = "11m"
}
+29
View File
@@ -0,0 +1,29 @@
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
version = "3.0.0"
}
}
}
variable "api_token" {
type = string
sensitive = true
description = "Nubes API token"
}
variable "realm" {
type = string
description = "resource_realm parameter for all resources"
}
variable "s3_name" {
type = string
description = "S3 user name for backups"
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
}
+24
View File
@@ -0,0 +1,24 @@
# =============================================================================
# MongoDB — постоянное хранение IoT-событий
# =============================================================================
resource "nubes_mongodb" "main" {
resource_name = local.mongo_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.mongo_cpu
memory = local.mongo_memory
disk = local.mongo_disk
replicas = local.mongo_replicas
}
access_configuration = {
master_ip_space = "no-needed"
master_access_list = jsonencode(["10.0.0.0/8"])
}
operation_timeout = local.mongo_timeout
}
+51
View File
@@ -0,0 +1,51 @@
# =============================================================================
# Flask Producer — генерирует IoT-события, шлёт в Kafka
# =============================================================================
locals {
producer_kafka_secrets = jsondecode(nubes_kafka.main.vault_secrets["users"])
producer_kafka_bootstrap = "${try(nubes_kafka.main.state_out_flat["internalConnect.bootstrap.fqdn"], "")}:${try(nubes_kafka.main.state_out_flat["internalConnect.bootstrap.port"], "9093")}"
producer_kafka_password = try(nonsensitive(local.producer_kafka_secrets["iot-app"]["user.password"]), "")
producer_kafka_ca_crt = try(nonsensitive(local.producer_kafka_secrets["ca"]["ca.crt"]), "")
producer_kafka_user_crt = try(nonsensitive(local.producer_kafka_secrets["iot-app"]["user.crt"]), "")
producer_kafka_user_key = try(nonsensitive(local.producer_kafka_secrets["iot-app"]["user.key"]), "")
}
resource "nubes_flask" "producer" {
resource_name = local.producer_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.producer_cpu
memory = local.producer_memory
replicas = local.producer_replicas
}
access_configuration = {
domain = local.producer_domain
}
app_configuration = {
version = "3.12"
git_path = local.producer_git_path
health_path = "/health"
}
git_revision = local.producer_git_revision
operation_timeout = local.producer_timeout
json_env = jsonencode({
KAFKA_BROKERS = local.producer_kafka_bootstrap
KAFKA_TOPIC = local.kafka_topic_name
KAFKA_USERNAME = local.kafka_username
KAFKA_PASSWORD = local.producer_kafka_password
KAFKA_CA_CRT = local.producer_kafka_ca_crt
KAFKA_USER_CRT = local.producer_kafka_user_crt
KAFKA_USER_KEY = local.producer_kafka_user_key
PRODUCE_INTERVAL = "3"
})
depends_on = [nubes_kafka.main]
}
+26
View File
@@ -0,0 +1,26 @@
# =============================================================================
# Redis — кэш дашборда
# =============================================================================
resource "nubes_redis" "main" {
resource_name = local.redis_resource_name
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = local.redis_cpu
memory = local.redis_memory
disk = local.redis_disk
replicas = local.redis_replicas
}
access_configuration = {
master_ip_space = "no-needed"
master_access_list = jsonencode(["10.0.0.0/8"])
slave_ip_space = "no-needed"
slave_access_list = jsonencode([])
}
operation_timeout = local.redis_timeout
}
@@ -0,0 +1,4 @@
# Скопировать в terraform.tfvars и заполнить
api_token = "ВАШ_API_ТОКЕН"
realm = "ВАШ_REALM"
s3_name = "ВАШ_S3_USER"
+35
View File
@@ -0,0 +1,35 @@
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.0"
}
}
}
variable "api_token" {
type = string
sensitive = true
description = "Nubes API token"
}
variable "s3_uid" {
type = string
sensitive = true
description = "Nubes S3 UID"
}
variable "realm" {
type = string
sensitive = true
description = "resource_realm parameter for nubes_postgres resource"
}
variable "s3_user_uid" {
type = string
description = "S3 user UUID"
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
# log_level = "debug" # none | info | debug, default = "none"
}
+58
View File
@@ -0,0 +1,58 @@
resource "nubes_postgres" "npg" {
resource_name = "pgdev01"
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = 500
memory = 512
replicas = 1
disk = 10
}
access_configuration = {
master_ip_space = "no-needed"
master_access_list = jsonencode(["10.0.0.0/8"])
slave_ip_space = "no-needed"
slave_access_list = jsonencode([])
}
postgres_configuration = {
version = "17"
ssl_required = true
pooler_master = false
pooler_slave = false
}
postgres_conf = jsonencode([{
param_name = "log_connections"
param_value = ""
}])
backup_configuration = {
s3_uid = var.s3_uid
retain = 14
schedule = "0 0 * * *"
}
autoscale_configuration = {
enabled = false
schedule = 0
percent = 10
quota = 100
}
mtls_configuration = {
type = "off"
duration_ca = 175200
duration_server = 87600
}
operation_timeout = "11m"
adopt_existing_on_create = true
}
+50
View File
@@ -0,0 +1,50 @@
# =============================================================================
# PostgreSQL — пользователи (6 шт.)
# =============================================================================
resource "nubes_postgres_user" "pg_user_0" {
postgres_id = nubes_postgres.npg.id
username = "user0"
role = "ddl_user"
mtls_access = false
adopt_existing_on_create = true
}
# resource "nubes_postgres_user" "pg_user_1" {
# postgres_id = nubes_postgres.npg.id
# username = "user1"
# role = "ddl_user"
# adopt_existing_on_create = true
# }
# # =============================================================================
# # PostgreSQL — базы данных (3 шт.)
# # =============================================================================
# resource "nubes_postgres_database" "pg_db_1" {
# postgres_id = nubes_postgres.npg.id
# db_name = "dbapp1"
# db_owner = nubes_postgres_user.pg_user_1.username
# adopt_existing_on_create = true
# }
resource "nubes_postgres_database" "pg_db_2" {
postgres_id = nubes_postgres.npg.id
db_name = "dbapp2"
db_owner = nubes_postgres_user.pg_user_0.username
adopt_existing_on_create = true
}
# resource "nubes_postgres_database" "pg_db_3" {
# postgres_id = nubes_postgres.npg.id
# db_name = "dbapp3"
# db_owner = nubes_postgres_user.pg_user_1.username
# adopt_existing_on_create = true
# }
# S3 bucket — замени "buck0" на своё имя везде ниже
resource "nubes_s3bucket" "bukka0" { # ← замени buck0 на своё имя ресурса
resource_name = "btst" # ← замени buck0 на своё имя ресурса
#s3_user_uid = "naeel-s3"
s3_user_uid = var.s3_user_uid
bucket_name = "buckdev-e8c2" # ← замени buck0 на своё имя бакета
adopt_existing_on_create = true
}
+39
View File
@@ -0,0 +1,39 @@
# =============================================================================
# locals.tf — настраиваемые значения для Менеджмент Kubernetes кластер Штурвал
# Сервис 148 — vc_mgmt_sthutrval_cluster
# =============================================================================
locals {
# ─── Имя ресурса ─────────────────────────────────────────────────────────
shturval_mgmt_name = "shturval-mgmt-dev"
# ─── Startup Configuration ───────────────────────────────────────────────
# resource_realm — только sandbox.nubes.ru для dev
shturval_realm = "sandbox.nubes.ru"
shturval_cluster_name = "shturval-mgmt-dev-00"
# ─── Cluster Configuration ───────────────────────────────────────────────
shturval_app_version = "2.13.1" # 2.13.1 | 2.12.1 | 2.11.0
# ─── Control Plane Configuration ─────────────────────────────────────────
shturval_cp_sizing_policy = "TKG 4CPU 8RAM" # TKG 4CPU 8RAM | TKG 8CPU 16RAM | TKG 16CPU 32RAM
shturval_cp_sizing_disk = 50 # ГБ
shturval_cp_count = 1 # 1 | 3 | 5
# ─── Worker Configuration ────────────────────────────────────────────────
# JSON-строка, массив групп воркеров. Все поля имеют defaults.
shturval_worker_config = jsonencode([
{
groupName = "workers-shturval-mgmt"
sizingPolicy = "TKG 4CPU 8RAM"
sizingDisk = 50
count = 2
}
])
# ─── Access Configuration ────────────────────────────────────────────────
shturval_api_need_external = true
shturval_api_access_ip_list = "[]" # пусто = доступ всем
shturval_ingress_need_external = true
shturval_ingress_access_ip_list = "[]" # пусто = доступ всем
}
+29
View File
@@ -0,0 +1,29 @@
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.0"
}
}
}
variable "api_token" {
type = string
sensitive = true
description = "Nubes API token"
}
variable "realm" {
type = string
description = "resource_realm for Штурвал (sandbox.nubes.ru)"
}
variable "sizing_policy" {
type = string
description = "Control plane sizing policy (зависит от ресурсной платформы)"
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
}
+32
View File
@@ -0,0 +1,32 @@
# =============================================================================
# Менеджмент Kubernetes кластер Штурвал (сервис 148)
# Ресурс: nubes_vc_mgmt_sthutrval_cluster
# =============================================================================
resource "nubes_vc_mgmt_sthutrval_cluster" "shturval_mgmt" {
resource_name = local.shturval_mgmt_name
startup_configuration = {
resource_realm = local.shturval_realm
cluster_name = local.shturval_cluster_name
}
cluster_configuration = {
app_version = local.shturval_app_version
}
control_plane_configuration = {
sizing_policy = var.sizing_policy # ⚠️ обязательный, передаётся через tfvars
sizing_disk = local.shturval_cp_sizing_disk
count = local.shturval_cp_count
}
worker_configuration = local.shturval_worker_config
access_configuration = {
need_external_address_a_p_i = local.shturval_api_need_external
access_ip_list_a_p_i = local.shturval_api_access_ip_list
need_external_address_ingress = local.shturval_ingress_need_external
access_ip_list_ingress = local.shturval_ingress_access_ip_list
}
}
@@ -0,0 +1,8 @@
# =============================================================================
# terraform.tfvars.example — пример переменных для Штурвал Management (148)
# Скопировать в terraform.tfvars и заполнить реальными значениями
# =============================================================================
api_token = "ваш_api_токен"
realm = "sandbox.nubes.ru"
sizing_policy = "TKG 4CPU 8RAM" # TKG 4CPU 8RAM (мин) | TKG 8CPU 16RAM | TKG 16CPU 32RAM
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Синхронизация DEV_STAND с VM (без удаления state и .terraform)
set -euo pipefail
VM_HOST="naeel@5.172.178.213"
VM_PATH="/home/naeel/tf_provider/DEV_STAND"
LOCAL_PATH="/home/naeel/tf_provider/DEV_STAND"
SSH_KEY="/home/naeel/tf_provider/secrets/id_ed25519.txt"
echo "Syncing DEV_STAND → VM (excluding .terraform, state, lock)..."
rsync -avz --delete \
--exclude='.terraform/' \
--exclude='terraform.tfstate*' \
--exclude='.terraform.lock.hcl' \
-e "ssh -i ${SSH_KEY} -o ConnectTimeout=10" \
"${LOCAL_PATH}/" \
"${VM_HOST}:${VM_PATH}/"
echo "Done."
+134
View File
@@ -0,0 +1,134 @@
# Документация MkDocs: генерация и заливка в реестр
> ⛔⛔⛔ **НЕ ЛОМАТЬ РАБОТАЮЩИЙ КОД** ⛔⛔⛔
>
> Эта папка — **справочная**. Скрипты пайплайна в `TOOLS/scripts/` и `scripts/`
> работают и должны оставаться **нетронутыми**.
> Любая правка в них — только после явного «делай» и с проверкой, что ничего не сломалось.
---
## Что здесь
Всё про **генерацию документации** провайдера Nubes, **сборку** MkDocs-сайта
и **заливку** статики в S3-реестр.
## Два независимых потока
### A. Генерация Markdown-доков по ресурсам (API → YAML → .md)
| Шаг | Скрипт | Что делает |
|---|---|---|
| 1 | `TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/<стенд>` | Тянет спецификации из API стенда → `generated/<стенд>/resources_yaml/` |
| 2 | `TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/<стенд>` | YAML → Go-код (`TOOLS/bin/resource-generator`) + Markdown-доки (`TOOLS/bin/docs-generator`) в `generated/<стенд>/docs/` |
| 3 (опц.) | `TOOLS/scripts/05_generate_docs_llm.py` | Прогоняет .md через LLM (улучшение описаний) |
| 4 | `TOOLS/scripts/03_build_and_upload_provider.sh --profile ... <ver>` | Сборка провайдера + GPG-подпись + заливка бинарников в S3 |
### B. Сборка MkDocs-сайта + заливка доков в S3
| Шаг | Скрипт | Что делает |
|---|---|---|
| 1 | `TOOLS/scripts/04_build_and_publish_docs.sh --profile ... <ver>` | Генерирует `.mkdocs.tmp.yml` (версия/`docs_dir`/nav), собирает сайт (docker → venv → system mkdocs) в `site/` |
| 2 | `scripts/publish-docs.sh` | Заливает `site/` в S3 (`mc cp --recursive` + `mc policy set public`) |
| 3 (опц.) | `scripts/publish-doc-page.sh` | Заливка **одной** страницы |
| CI | `.github/workflows/publish-docs.yml` | Авто-публикация по git-тегу `v*.*.*` |
---
## Команды (полный цикл, стенд = dev/test/prod)
```bash
# DEV (пример)
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
./TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/dev 3.1.13
```
**Быстрая заливка** (YAML/Go уже сгенерированы, не менялись) — только шаг 3/4:
```bash
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test 5.1.17
./TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/test 5.1.17
```
### Ручная заливка доков (рабочий способ)
```bash
# S3-креды из secrets/.s3cfg_registry (или env S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY)
/home/naeel/terra/scripts/publish-docs.sh \
site \
tf-registry.containerk8s.services.ngcloud.ru \
nubes nubes 2.0.2
```
---
## Список файлов
### Скрипты (пайплайн)
- `TOOLS/scripts/01_generate_yamls.sh`
- `TOOLS/scripts/02_generate_resources_and_docs_v2.sh`
- `TOOLS/scripts/03_build_and_upload_provider.sh`
- `TOOLS/scripts/04_build_and_publish_docs.sh`
- `TOOLS/scripts/05_generate_docs_llm.py`
- `TOOLS/scripts/build-provider.sh`
- `scripts/publish-doc-page.sh`
- `scripts/publish-docs.sh` ← ⚠️ см. «Известная проблема» ниже
### Генераторы (Go-бинарники)
- `TOOLS/bin/resource-generator`
- `TOOLS/bin/docs-generator`
- `TOOLS/bin/yaml-generator`
### Конфиг
- `mkdocs.yml` — конфиг MkDocs (site_url, nav, тема material)
- `TOOLS/config/registry.env` — реестр (`REGISTRY_HOSTNAME`, `S3_ENDPOINT`, `S3_BUCKET`)
- `TOOLS/config/{dev,test,prod}/profile.env` — стенд (`NUBES_API_ENDPOINT`, `NAMESPACE`, `VERSION`)
- `TOOLS/config/{dev,test,prod}/services_list.txt`
- `TOOLS/config/{dev,test,prod}/operation_timeouts.json`
### Секреты
- `secrets/{dev,test,prod}.token`
- `secrets/private_key.asc` — GPG-подпись
- `secrets/.s3cfg_registry` — S3-креды
### Контент / ассеты
- `docs/` — ручные источники (`index.md`, `curated/`, `help/`, `30_registry/` и др.)
- `docs/30_registry/` — `guides/`, `resources/`, `assets/`, `javascripts/fix-slash.js`
- `generated/<стенд>/docs/` — сгенерированные доки (включая `_nav_fragment.yml`)
- `site/`, `site_test/` — результат сборки
---
## S3 / бакеты
| Что | Бакет | Путь |
|---|---|---|
| **Документация** | `terraform-registry` | `docs/<namespace>/<name>/<version>/` |
| **Бинарники провайдера** | `nubes-terraform-registry` | `<host>/<namespace>/<name>/<version>/` |
- Эндпоинт S3: `https://s3.msk-1.ngcloud.ru`
- Хост реестра: `tf-registry.containerk8s.services.ngcloud.ru`
- Клиент: `mc` (MinIO), алиасы `prod-s3`/`reg`/`registry`/`tfreg`
---
## ⚠️ Известная проблема: `scripts/publish-docs.sh` отсутствует в этом репозитории
1. Скрипт `scripts/publish-docs.sh` **удалён** из `/home/naeel/tf_provider`
коммитом `c2438f5` (2026-07-05, «superseded by devops/»).
2. Но `TOOLS/scripts/04_build_and_publish_docs.sh` (строка ~280) и
`.github/workflows/publish-docs.yml` (строка ~54) **до сих пор вызывают**
`./scripts/publish-docs.sh`.
3. **Следствие:** запуск `04` из этого репозитория соберёт сайт, но упадёт
на шаге заливки (`No such file or directory`). CI по тегу — аналогично.
**Рабочая копия скрипта живёт в старом репозитории** (отдельный git, не клон):
- `/home/naeel/terra/scripts/publish-docs.sh`
- архив: `/home/naeel/terraform__OFF/scripts/publish-docs.sh`
Копия этого скрипта сохранена рядом: [`publish-docs.sh`](./publish-docs.sh)
### Варианты устранения (только после «делай»)
1. Восстановить `scripts/publish-docs.sh` в это репозиторий (из копии рядом или из git `c2438f5^`).
2. Инлайнить заливку прямо в `04_build_and_publish_docs.sh` (как уже сделано в `publish-doc-page.sh`).
+176
View File
@@ -0,0 +1,176 @@
# Документация провайдера Nubes: генерация и публикация
> Актуально на 2026-09-03. Историческая версия — [`README.legacy.md`](./README.legacy.md).
## Общая схема
```
API стенда ──▶ generated/<стенд>/resources_yaml/ ──▶ generated/<стенд>/docs/ (.md)
│ (docs_dir для MkDocs)
▼
MkDocs build ──▶ site/ (HTML)
│
▼
S3 terraform-registry/docs/<namespace>/<name>/ (без версии, public)
│
▼
ВМ 5.172.178.213 nginx (зеркало /var/www/tf-docs/) ◀─ под tf_docs (proxy)
│
▼
https://tf-docs.nodejsk8s.dev.nubes.ru/<namespace>/
```
Ключевые принципы:
- **Без версий в URL**: docs публикуются в `docs/<namespace>/<name>/` перезаписью (`mc mirror --overwrite --remove`).
- **Вечный бесплатный домен**: `tf-docs.nodejsk8s.dev.nubes.ru/<namespace>/` (managed-кластер → под-прокси → ВМ nginx).
- Имя провайдера (`<name>`) во всех стендах — `nubes`; в URL сайта не фигурирует (только `<namespace>`), в S3-ключе — есть.
## Стенды
| Стенд | profile.env | Namespace (S3/URL) | API-эндпоинт | Токен |
|---|---|---|---|---|
| dev | `TOOLS/config/dev/profile.env` | `nubes-dev` | `https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc` | `secrets/dev.token` |
| test | `TOOLS/config/test/profile.env` | `nubes-test` | `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc` | `secrets/test.token` |
| prod | `TOOLS/config/prod/profile.env` | `nubes` | `https://lk-api-gateway.ngcloud.ru/api/v1/svc` | `secrets/prod.token` |
В `profile.env` также: `PROVIDER_NAME=nubes`, пути GPG-ключей, актуальная `VERSION` стенда.
## Нумерация версий провайдера по стендам
> ⛔ **ЕДИНСТВЕННАЯ схема (с 2026-09-03).** Старые диапазоны (`prod=2.*`, `dev=3.*`,
> `test=5.*`, а также `0.0.x`) — ЛЕГАСИ, **НЕ ИСПОЛЬЗОВАТЬ**. Полная чистка реестра
> выполнена 2026-09-03 — старые версии удалены из S3.
| Стенд | Диапазон версий | Первая |
|---|---|---|
| **prod** (`nubes`) | `1.*.*` | `1.0.0` |
| **dev** (`nubes-dev`) | `2.*.*` | `2.0.0` |
| **test** (`nubes-test`) | `3.*.*` | `3.0.0` |
Версия передаётся аргументом в `03_build_and_upload_provider.sh <ver>` и хранится в
`VERSION` в `profile.env`. Источник правды — [`VERSIONS.md`](../../VERSIONS.md).
## Поток A — генерация Markdown (API → YAML → .md)
| Шаг | Скрипт | Результат |
|---|---|---|
| 1 | `TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/<стенд>` | спецификации ресурсов из API → `generated/<стенд>/resources_yaml/` |
| 2 | `TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/<стенд>` | YAML → Go-код провайдера + Markdown-доки → `generated/<стенд>/docs/` (в т.ч. `_nav_fragment.yml`) |
| 3 (опц.) | `TOOLS/scripts/05_generate_docs_llm.py` | LLM-улучшение описаний `.md` |
| 4 | `TOOLS/scripts/03_build_and_upload_provider.sh --profile ... <ver>` | сборка провайдера + GPG-подпись + бинарники в S3 (не docs) |
## Поток B — сборка MkDocs-сайта и публикация
| Шаг | Скрипт | Что делает |
|---|---|---|
| 1 | `TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/<стенд> [ver]` | собирает сайт и публикует (см. ниже) |
| 2 | `scripts/publish-docs.sh <site> <host> <ns> <name>` | заливка `site/` в S3 (см. ниже) |
| 3 (опц.) | `scripts/publish-doc-page.sh` | заливка одной страницы |
| CI | `.github/workflows/publish-docs.yml` | авто-публикация по git-тегу `v*.*.*` |
### Детали шага 04
1. Читает `profile.env` стенда (`--profile`): `NAMESPACE`, `VERSION`, `NUBES_API_ENDPOINT`, `REGISTRY_HOST` (default `tf-docs.nodejsk8s.dev.nubes.ru`).
2. `MKDOCS_DOCS_DIR` = `generated/<стенд>/docs` — **никогда не сливается с ручным `docs/`**.
3. Копирует ручные ассеты в сгенерированный каталог: `docs/30_registry/` и `docs/curated/` → `generated/<стенд>/docs/`.
4. Подставляет в `generated/<стенд>/docs/guides/getting-started.md` актуальные `version` и `api_endpoint`.
5. Генерирует `.mkdocs.tmp.yml` из `mkdocs.yml`:
- `site_url: https://<REGISTRY_HOST>/<NAMESPACE>/`;
- `docs_dir` — относительный на `generated/<стенд>/docs`;
- в `nav` секция «Ресурсы» заменяется на `resources_nav` из `_nav_fragment.yml`.
6. Сборка в `site/` (по убыванию приоритета): docker `squidfunk/mkdocs-material` → `.venv` python mkdocs → системный `mkdocs`. Пинованные версии: `mkdocs==1.6.1`, `mkdocs-material==9.7.3`.
7. Заливка: `./scripts/publish-docs.sh site "$REGISTRY_HOST" "$NAMESPACE" "$PROVIDER_NAME" "$VERSION"`.
- ⚠️ `publish-docs.sh` принимает 4 аргумента (`site host ns name`); 5-й (`VERSION`) игнорируется — публикация всегда без версии.
### Детали publish-docs.sh (актуальный)
- Берёт S3-креды из `S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY` (или legacy `MINIO_*`), при вызове из `04` — подгружаются из `secrets/.s3cfg_registry`.
- `mc alias set registry <endpoint> <ak> <sk> --api S3v4`.
- `mc mirror --overwrite --remove "$SITE_DIR/" → registry/terraform-registry/docs/<namespace>/<name>/`.
- `mc policy set public` на target.
- Публикация «на месте»: старые файлы удаляются, версий нет.
## Промежуточные файлы и папки
| Папка/файл | Назначение |
|---|---|
| `generated/<стенд>/resources_yaml/` | сырые YAML-спеки из API (шаг A1) |
| `generated/<стенд>/docs/` | сгенерированные Markdown + `_nav_fragment.yml` (docs_dir для MkDocs) |
| `site/` | результат сборки MkDocs (HTML), заливается в S3 |
| `site_test/` | тестовая сборка по `.mkdocs.docs_test.yml` |
| `docs/` | ручные источники (`index.md`, `curated/`, `help/`, `30_registry/`); внутренние разделы (`00_overview`, `20_discovery`, `40_analysis`, `50_history`, `60_strategy`, `70_api`, `help/*`, `README.md`, `ai_universal_provider_gen.md`) исключаются через `exclude_docs` |
| `scripts/publish-docs.sh` | актуальная заливка docs в S3 (без версии) |
| `scripts/publish-doc-page.sh` | заливка одной страницы |
| `TOOLS/config/<стенд>/profile.env` | параметры стенда (endpoint, NAMESPACE, VERSION, токен, GPG) |
| `TOOLS/config/registry.env`, `services_list.txt`, `operation_timeouts.json` | конфиги реестра/генерации |
| `TOOLS/bin/` | генераторы: `resource-generator`, `docs-generator`, `yaml-generator` |
| `secrets/{dev,test,prod}.token`, `.s3cfg_registry`, `private_key.asc` | токены API, S3-креды, GPG |
| `mkdocs.yml` | базовый конфиг MkDocs (тема material, exclude_docs, extra) |
| `.mkdocs.tmp.yml` | генерируется в 04, удаляется по trap |
| `.mkdocs.docs_test.yml` | конфиг тестовой сборки (site_test) |
| `DOCS_PIPELINE/publish-docs.sh` | ⚠️ легаси-копия старого скрипта (с версией, `mc cp`); **не использовать** |
## S3 и хостинг
| Что | Бакет | Ключ |
|---|---|---|
| Документация | `terraform-registry` (public) | `docs/<namespace>/<name>/` — без версии |
| Бинарники провайдера | `nubes-terraform-registry` | `<host>/<namespace>/<name>/<version>/` |
- S3-эндпоинт: `https://s3.msk-1.ngcloud.ru` (Ceph RGW). Клиент `mc` (алиасы `prod-s3`/`reg`/`registry`/`tfreg`).
- Доставка до браузера: S3 → ВМ-зеркало (`/var/www/tf-docs/`) → nginx ВМ отдаёт `/<namespace>/` → под `tf_docs` (reverse-proxy в кластере) → `https://tf-docs.nodejsk8s.dev.nubes.ru/<namespace>/`.
- ВМ отдаёт также по прямому IP `http://5.172.178.213/<namespace>/`.
## Требования к окружению (настроено 2026-09-03)
Чтобы пайплайн работал **штатно и не ломался**, на машине сборки должно быть:
| Компонент | Как проверить | Что ставить |
|---|---|---|
| `python3-venv` (Debian/Ubuntu) | `python3 -m venv /tmp/v && ls /tmp/v/bin/pip` | `sudo apt install -y python3.12-venv` — без него venv создаётся БЕЗ pip |
| `.venv` проекта с mkdocs | `.venv/bin/python -m mkdocs --version` | пересоздать: `rm -rf .venv && python3 -m venv .venv && .venv/bin/pip install mkdocs==1.6.1 mkdocs-material==9.7.3` |
| Системный mkdocs (запасной) | `python3 -m mkdocs --version` | `pip3 install --user mkdocs==1.6.1 mkdocs-material==9.7.3` |
| `mc` (MinIO client) | `mc --version` | см. docs min.io |
| docker + образ `squidfunk/mkdocs-material` (запасной) | `docker images` | `docker pull squidfunk/mkdocs-material` |
> **Почему так.** `04` при `--profile` собирает через `.venv` проекта. Если `.venv` пустой/сломан (нет pip/mkdocs) — сборка падает. Корень: без системного пакета `python3.12-venv` виртуальное окружение создаётся без `pip`/`ensurepip`. Это чинится один раз (apt + пересоздание `.venv`), дальше не ломается.
> Версии зафиксированы: `mkdocs==1.6.1`, `mkdocs-material==9.7.3` (совпадают и в системном python3, и в `.venv`).
## Публикация: где запускать `mc mirror`
S3 (`s3.msk-1.ngcloud.ru`) из локальной сети **рвёт большие ответы** (рекурсивный листинг >нескольких сотен объектов зависает: `mc: Unable to list ... unexpected EOF`; малые `mc ls`/`mc cp` работают). Поэтому **заливку на S3 делать с ВМ `5.172.178.213`** — у неё быстрый канал до S3 (~10 МБ/с).
Полный цикл публикации стенда (сборка локально → S3 с ВМ → зеркало на ВМ):
```bash
# 1. Сборка (локально, штатно)
./TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/test 5.0.8
# (если site/ собирался docker-ом от root — mkdocs не сможет его перезаписать:
# sudo rm -rf site или docker run --rm -v $PWD:/docs --entrypoint rm squidfunk/mkdocs-material -rf /docs/site)
# 2. Передать собранный site/ на ВМ
tar -C site -cf - . | ssh naeel@5.172.178.213 'rm -rf ~/tmp-docs-site && mkdir -p ~/tmp-docs-site && tar -C ~/tmp-docs-site -xf -'
# 3. Залить на S3 с ВМ (быстрый канал)
ssh naeel@5.172.178.213 'mc mirror --overwrite --remove ~/tmp-docs-site/ registry/terraform-registry/docs/nubes-test/nubes/'
# 4. Обновить зеркало /var/www/tf-docs (откуда nginx отдаёт сайт)
ssh naeel@5.172.178.213 'mc mirror --overwrite --remove "registry/terraform-registry/docs/nubes-test/nubes/" /var/www/tf-docs/nubes-test/'
```
> ⚠️ Если `mc mirror`/`mc ls -r` локально зависает — это не баг скрипта, а сеть до S3; заливать с ВМ.
## Быстрые команды
```bash
# Полный цикл для стенда dev
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
./TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/dev
# Только пересборка и публикация (YAML/Go не менялись)
./TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/test
# Ручная заливка уже собранного site/
scripts/publish-docs.sh site tf-docs.nodejsk8s.dev.nubes.ru nubes-test nubes
```
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
# Заливка собранного MkDocs-сайта (site/) в S3-реестр.
# Копия рабочего скрипта из старого репозитория /home/naeel/terra/scripts/publish-docs.sh.
# ⚠️ НЕ ЛОМАТЬ РАБОТАЮЩИЙ КОД: этот файл — справочная копия, не подменяет пайплайн.
# Usage: publish-docs.sh <site-dir> <registry-host> <namespace> <name> <version>
SITE_DIR=${1:-site}
REGISTRY_HOST=${2:-tf-registry.containerk8s.services.ngcloud.ru}
NAMESPACE=${3:-nubes}
NAME=${4:-nubes}
VERSION=${5:-dev}
# Support both S3_* (New Standard) and MINIO_* (Legacy) variables
ENDPOINT=${S3_ENDPOINT:-${MINIO_ENDPOINT:-}}
ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ACCESS_KEY:-}}
SECRET_KEY=${S3_SECRET_KEY:-${MINIO_SECRET_KEY:-}}
if [ -z "$ENDPOINT" ] || [ -z "$ACCESS_KEY" ] || [ -z "$SECRET_KEY" ]; then
echo "Error: S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY must be set"
exit 2
fi
MC_ALIAS=registry
mc alias set $MC_ALIAS "$ENDPOINT" "$ACCESS_KEY" "$SECRET_KEY" --api S3v4
TARGET="${MC_ALIAS}/terraform-registry/docs/${NAMESPACE}/${NAME}/${VERSION}/"
# mc создаёт промежуточные каталоги неявно при копировании
mc cp --recursive "$SITE_DIR/" "$TARGET"
# Публичная политика на бакет
mc policy set public "$TARGET" || true
echo "Published docs to: https://${REGISTRY_HOST}/docs/${NAMESPACE}/${NAME}/${VERSION}/"
+28
View File
@@ -0,0 +1,28 @@
# FIYR_MGU — Материалы для поступления в магистратуру ФИЯР МГУ
Собрано по официальным источникам (http://www.ffl.msu.ru) и демо-вариантам вступительных испытаний по направлению «Лингвистика» (иностранный язык).
## Содержание
| Файл | Описание |
|---|---|
| `Структурированный_материал_для_подготовки.md` | **Главный документ**: формат экзамена, анализ реальных текстов, алгоритм написания реферата, клише, темы, план подготовки |
| `материалы/ANGL_2021.docx` | Демо-вариант, английский, 2021 (реферат по семиотике) |
| `материалы/ANGL_2023.docx` | Демо-вариант, английский, 2023 (реферат по концептуальной метафоре) |
| `материалы/ITAL_2021.docx` | Демо-вариант, итальянский, 2021 |
| `материалы/Английский_2018.pdf` | Английский, 2018 (старый формат — эссе + тест) |
| `материалы/Английский_2019.pdf` | Английский, 2019 (старый формат) |
| `материалы/Test_Cultura_2018.pdf` | Культурология, 2018 (демо) |
| `материалы/Test_RegRos_2018.pdf` | Регионоведение России, 2018 (демо) |
## Ключевые официальные ссылки
- Магистратура ФИЯР: http://www.ffl.msu.ru/study/master
- Раздел «Поступление»: http://www.ffl.msu.ru/apply/
- ЦПК МГУ: http://www.cpk.msu.ru
- Правила приёма МГУ 2026: https://cpk.msu.ru/files/2026/rules.pdf
## Контакты приёмной комиссии
- +7 (925) 108-85-68, +7 (495) 932-88-66 (с 20 июня по 25 августа)
- pk.ffl@org.msu.ru
@@ -0,0 +1,220 @@
# Подготовка к вступительному испытанию в магистратуру ФИЯР МГУ
## Направление «Лингвистика» (иностранный язык: английский)
> Составлено на основе официальных демо-вариантов ФИЯР МГУ за доступные годы
> (2018, 2019, 2021, 2023 — английский; 2021 — итальянский; 2018, 2019 — культурология,
> регионоведение). Дата подготовки: 2026-08-07.
---
## 1. Общая схема поступления (приём 2026)
| Параметр | Значение |
|---|---|
| Направление | 45.03.02 «Лингвистика» |
| Вступительное испытание | **лингвистика на иностранном языке** (англ./франц./нем./исп./ит.), письменно |
| Минимальный балл | 40 (устанавливается МГУ) |
| Подача документов | с 20 июня по 10 августа 2026 |
| Согласие на зачисление | до 24 августа 2026, 12:00 |
| Ссылка на экзамен | приходит на почту личного кабинета на Госуслугах, не позднее чем за сутки |
| Контакт приёмной комиссии | +7 (925) 108-85-68, pk.ffl@org.msu.ru |
Полезные ссылки:
- Страница магистратуры: http://www.ffl.msu.ru/study/master
- Регламент ВИ 2026 (PDF на странице магистратуры)
- Презентация магистерских программ (PDF там же)
- Правила приёма МГУ 2026: https://cpk.msu.ru/files/2026/rules.pdf
---
## 2. Формат вступительного испытания по английскому
> ⚠️ Важно: формат **изменился**. В 2018–2019 экзамен состоял из 2 частей
> (эссе по культурологическим темам + лексико-грамматический тест с 10 пропусками).
> Начиная с 2021 г. (подтверждено демо 2021 и 2023) экзамен — **один реферат по тексту**.
### Современный формат (2021, 2023) — ОДНО задание
**Задание:** «Прочитайте текст и изложите его содержание **в научном стиле своими словами**, соблюдая классическую структуру: введение, основная часть, заключение. Напишите **реферат прочитанного текста в количестве 500 слов**».
**Реферат должен содержать два смысловых блока:**
1. **Объективная / авторская информация:**
- проблематика, обсуждаемая автором (предмет и объект исследования);
- выбранный способ её обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.);
- методы исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный/качественный анализ; сравнительно-сопоставительный анализ и пр.).
2. **Интерпретация прочитанного (аргументация выводов обязательна):**
- текст/автор подтверждает или опровергает, обновляет/расширяет/дополняет прочитанное ранее;
- в чём текст вызывает недоверие (изложено противоречиво/бездоказательно/без примеров и пр.) и/или одобрение (привести аргументы).
**Структура работы:**
- Введение — жанр текста, смысл названия, общая тема.
- Основная часть — два указанных блока.
- Заключение — место и значение исследования данной темы в современной лингвистике.
**Жёсткие требования:**
- Объём ≈ **500 слов**.
- **Копирование 4 слов подряд** из исходного текста или других источников = некорректное цитирование (плагиат). При обнаружении работа оценивается **только на предмет языковой грамотности** — то есть фактически завалена по содержанию.
- Научный стиль: логическая последовательность, текстовая связность, точность, сжатость, однозначность; выбор лексики и грамматических структур формального/академического стиля.
---
## 3. Анализ реальных текстов демо-вариантов
### 3.1. Английский 2021 — «A Brief Introduction to Semiotics»
- **Автор:** Barbara Brownie, *Semiotics of Typography* (2009).
- **Тема:** введение в семиотику — науку о знаках.
- **Ключевое содержание:**
- Связь семиотики со структурализмом.
- История изучения знаков: Гиппократ, Аристотель → Соссюр, Пирс → Барт, Эко.
- Модель знака Соссюра: означающее (signifier) / означаемое (signified), произвольность связи, денотация/коннотация.
- Модель Пирса: репрезентамен / объект / интерпретант; иконы, индексы, символы.
- langue / parole, синтагматические и парадигматические отношения.
- Якорение (anchorage), коды, первичные коды и субкоды, «аберрантное декодирование» (У. Эко).
- **Примечание:** текст содержит все понятия семиотики — идеален для понимания логики «автор → теория → примеры → следствия».
### 3.2. Английский 2023 — «Some Consequences for Theories of Conceptual Structure»
- **Авторы:** George Lakoff & Mark Johnson, *Metaphors We Live By* (2004).
- **Тема:** теория концептуальной метафоры (метафора как способ мышления и категоризации опыта).
- **Ключевое содержание:**
- Концептуальная система человека в значительной степени метафорически структурирована.
- Сравнение трёх теорий: **абстракционизм**, **омонимия** (сильная/слабая), **теория метафорических концептов** авторов.
- Примеры: AN ARGUMENT IS A BUILDING, LOVE IS A JOURNEY, IDEAS ARE FOOD, HAPPY IS UP.
- Внутренняя и внешняя систематичность, укоренённость (grounding), частичное метафорическое структурирование.
- Критика абстракционизма и сильной омонимии; слабая омонимия ближе к позиции авторов, но не объясняет укоренённость в опыте.
- **Примечание:** текст — фрагмент научного рассуждения с последовательным опровержением альтернативных теорий. Хорош для демонстрации «логики аргументации».
### 3.3. Старые тексты (2018, 2019) — для понимания прошлого формата
- **2018:** темы эссе — цивилизационный подход (Шпенглер/Тойнби); восприятие западноевропейского искусства русской культурой. Тест — статья о художнике Уистлере.
- **2019:** темы — психоаналитическое направление (Фрейд/Юнг); культурные преобразования петровской эпохи. Тест — письмо Э. Золя о деле Дрейфуса.
---
## 4. Как писать реферат (пошаговый алгоритм)
### Шаг 1. Быстрое понимание текста (5–7 мин)
- Определите жанр (научный фрагмент, очерк, статья, отрывок монографии).
- Найдите главный тезис автора (обычно в 1–2 абзацах).
- Выделите структуру: что вводят, что доказывают, какие примеры приводят, какой вывод делают.
### Шаг 2. Составление «скелета» реферата (план)
- **Introduction:** жанр + название + общая тема + проблематика.
- **Body (блок 1):** предмет/объект, метод/способ рассуждения, ключевые положения.
- **Body (блок 2):** критическая оценка — что подтверждается/опровергается, что вызывает доверие/недоверие, с аргументами.
- **Conclusion:** значение темы для современной лингвистики.
### Шаг 3. Написание своими словами
- НЕ копируйте пассажи из текста (даже 4 слова подряд = плагиат).
- Пересказывайте идеи, перефразируйте термины и конструкции.
- Соблюдайте научный стиль.
### Шаг 4. Проверка (~5 мин оставшихся)
- Подсчёт слов (~500).
- Логические связки между абзацами.
- Отсутствие явных заимствований.
### Тайминг (на весь экзамен):
- Чтение и понимание: ~15–20 мин
- План: ~10 мин
- Написание: ~60–70 мин
- Проверка/подсчёт: ~10 мин
---
## 5. Клише и научный стиль (шпаргалка)
### Введение
- *The text under analysis is an extract from …*
- *The title of the text is …*
- *The text deals with / focuses on / is devoted to the problem of …*
- *The author addresses the issue of …*
- *The subject matter of the text is …*
### Основная часть (объективная информация)
- *The author argues / claims / maintains / points out that …*
- *The problem is approached through … (analysis, comparison, critical review)*
- *The author employs such methods as … (deduction, induction, classification, typology)*
- *Particular attention is paid to …*
- *The author illustrates the point with the example of …*
- *According to the author, … / As the author states, …*
### Основная часть (интерпретация/оценка)
- *The ideas presented here are consistent with / contradict …*
- *The author’s argument is convincing because …*
- *However, the reasoning appears somewhat inconsistent since …*
- *The text is well supported by examples, although some claims lack evidence.*
- *Personally, I find the author’s position on … convincing / debatable.*
### Заключение
- *To sum up / In conclusion, …*
- *The study is of considerable importance for modern linguistics because …*
- *The issues raised open new perspectives for further research in …*
### Логические связки
- причинность: *therefore, thus, consequently, as a result, due to, owing to*
- противопоставление: *however, nevertheless, whereas, on the contrary, although*
- добавление: *moreover, furthermore, in addition, besides*
- пример: *for instance, for example, such as, namely*
- обобщение: *in general, overall, on the whole*
---
## 6. Ключевые лингвистические темы для подготовки
(исходя из текстов прошлых лет и профиля факультета)
1. **Семиотика и структурализм** — знак, означающее/означаемое, модели Соссюра и Пирса, иконы/индексы/символы, langue/parole, синтагма/парадигма, коды и субкоды, коннотация/денотация. *(была в 2021)*
2. **Когнитивная лингвистика** — концептуальная метафора, категоризация, укоренённость в опыте, схемы образов. *(была в 2023)*
3. **Теория перевода** — эквивалентность, адекватность, трансформации, прагматика перевода.
4. **Социолингвистика** — языковая ситуация, диалекты/стандарт, языковая норма, функциональная грамотность, «языковой вопрос». *(итальянский 2021 — про итальянский и диалекты)*
5. **Лингводидактика / методика обучения ИЯ** — подходы, компетенции, ИИ в обучении, персонализация.
6. **Психолингвистика и нейролингвистика** — речевая деятельность, порождение/восприятие речи.
7. **Дискурс-анализ и коммуникативистика** — типы дискурса, PR, межкультурная коммуникация.
**Совет:** читайте англоязычные научно-популярные и академические тексты по этим темам, учитесь пересказывать их устно и письменно за 500 слов.
---
## 7. План подготовки (пример, 1 учебный год / интенсив)
1. **База (2–3 месяца):** повторить грамматику (времена, страдательный залог, герундий/инфинитив, условные, модальные), расширить академическую лексику; практика пересказов — 1 текст в неделю.
2. **Формат (2 месяца):** разбор демо-вариантов 2021 и 2023; написание рефератов по 1–2 в неделю с самопроверкой на 500 слов и отсутствие плагиата.
3. **Тематика (1 месяц):** чтение и реферирование текстов по темам п. 6.
4. **Скорость (последний месяц):** тренировки на время (полный экзамен за 2 ч), отработка тайминга, психологическая подготовка.
### Самопроверка перед сдачей
- [ ] Точно ~500 слов.
- [ ] Есть введение, 2 блока, заключение.
- [ ] Нет копирования 4+ слов подряд.
- [ ] Связки между абзацами.
- [ ] Научный/академический стиль, без разговорных оборотов.
---
## 8. Демо-варианты: где лежат файлы
В папке `материалы/` этой директории (`FIYR_MGU/материалы/`):
- `ANGL_2021.docx` — английский, 2021 (совр. формат — реферат по семиотике)
- `ANGL_2023.docx` — английский, 2023 (реферат по концептуальной метафоре)
- `ITAL_2021.docx` — итальянский, 2021 (реферат по итальянскому языку и диалектам)
- `Английский_2018.pdf` — английский, 2018 (старый формат: эссе + тест, скан)
- `Английский_2019.pdf` — английский, 2019 (старый формат, скан)
- `Test_Cultura_2018.pdf`, `Test_RegRos_2018.pdf` — демо по культурологии/регионоведению
> Примечание: полных демо за 2020, 2022, 2024, 2025 гг. на официальном сайте ФИЯР в
> открытом доступе нет; сторонние площадки (форумы, телеграм-каналы, вк) полные
> тексты экзаменационных материалов не публикуют. Рекомендуется сверяться с
> официальной страницей магистратуры ФИЯР.
---
## 9. Чего ждать на самом экзамене (по регламенту 2026)
- Проводится дистанционно (2026 — платформа МТС-link, «Яндекс-Телемост» для регионоведения).
- Для «Лингвистики» язык сдачи можно выбрать заранее (форма на сайте).
- Ссылка на экзамен — на почту Госуслуг за сутки.
- Оценивается по 100-балльной шкале, проходной минимум — 40.
---
*Документ носит справочно-учебный характер и основан на официальных материалах ФИЯР МГУ (ffl.msu.ru). Перед подачей документов проверяйте актуальную информацию на официальном сайте.*
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,48 @@
МГУ имени М.В. Ломоносова
Вступительные испытания по иностранному языку
Английский язык
2021 год
стр. 1 из 5
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
Реферат должен содержать два смысловых блока:
1. объективная/авторская информация
₋            проблематика, обсуждаемая автором (предмет и объект исследования);
₋            выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
2. интерпретация прочитанного (аргументация предложенных выводов обязательна)
₋            текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее;
₋            текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите аргументы) в Вашем понимании поднимаемых вопросов и темы в целом.
Структура работы:
Введение (определение жанра текста, смысла названия и общей темы).
Основная часть (два указанных выше смысловых блока).
Заключение (место и значение исследования данной темы в современной лингвистике).
Важные аспекты:
Напишите реферат прочитанного текста в количестве 500 слов.
Копирование 4 слов подряд из исходного текста или других источников расценивается некорректным цитированием, в случае обнаружения чего работа оценивается только на предмет языковой грамотности.
Характеристики научного стиля реферата: логическая последовательность изложения, текстовая связность; стремление автора к точности, сжатости, однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/академического стиля.
стр. 2 из 5
A Brief Introduction To Semiotics
(extract)
By Barbara Brownie
From Semiotics of Typography, 2009.
In its origins, semiotics is deeply entwined with Structuralism, an “analytical method”which seeks to find “deep structures” underlying systems of signs. However,contemporary semiotics is concerned more with the social dimensions of the use ofsigns. Contemporary semioticians focus on processes of communication, and how westructure our understanding of our environments.
The study of signs dates back as far as Hippocrates (460-377BC), who noted thatsymptoms are signs for underlying illness, thereby establishing that a sign “stands forsomething other than itself”. Outside of medicine, philosophers includingAristotle (384-32BC) established that and a sign can be divided into:
a) its physical self;
b) the thing to which it directly refers; and
c) its meaning, which may vary due to social and personal experience.
Much later, these ideas formed the basis for studies of signs and sign systems inlanguage and media, conducted by, among others, Ferdinand de Saussure (1857-1913, alinguist who established many fundamental ideas of structuralist semiotics) and CharlesSaunders Pierce (1839-1914), then later Roland Barthes (1915-1980, concerned largely with poststructuralism) and Umberto Eco (1932-). Although Pierce’s ideas will be brieflyintroduced below, this thesis will focus on the Saussurean tradition. Although Saussurehimself did not publish writing on the topic of semiotics, his Course de LinguistiqueGénérale survives in a text compiled by his students after his death. Focusing onlinguistic communication, this text establishes “the course that semiotic inquiry was totake during the first half of the twentieth century”, forming “the groundbase on whichmost contemporary structuralist theory now rests”. From the late 1960s, Barthesapplied semiotic theory in the field of cultural studies, and paved the way for theunderstanding of the linguistic sign as a material object, comparable to any other formor object that we may encounter in any media.
A sign is anything that communicates meaning beyond itself. The idea of the sign, how itcan be broken down, and the systems into which signs are organised, form the basis forthe study of semiotics.
For Ferdinand de Saussure, a sign is a “union” of two “equivalent” parts: the“signifier” (or “signal”) and “signified” (or “signification”), where the “signifier” isthe signs physical presence, and the “signified” is the concept evoked in the mind ofthe receiver. He stresses, however, that the “material” part of the sign (the signifier) andthe concept (the signified) are both “psychological” experiences of the receiver, so thateven the physical parts of a sign are only “sensory impressions”. In practice, these two parts of the sign are “always integrated into each other”, only divisible during the process of analysis.
стр. 3 из 5
An audience perceives the whole sign, and does not consciouslyseparate signifier from signified.
Saussure suggests that the connection between the two parts of the sign are establishedarbitrarily (as with linguistics), although he does concede that “certain signifiers [are]appropriate for their signifieds, as in onomatopoeia”. More recent theorists note thatmany signs are in fact “motivated”. Levi-Strauss, for example, observed that therelationship between a spoken sound and its written equivalent is “conventional”, or“rational”. Some conventions are established over time. Though initially arbitrary,they are ultimately adopted as “natural” after the relationship between signifier andsignified has been established in society for a long time.
Where Saussure discusses signifieds, he focuses on denotation, the initial, literal“referent a sign intends to capture”. Barthes, however, focuses on connotation. Theconnotative meaning of a sign involves associations that are established through socialconvention. The range of connotations that a sign evokes may vary, being specific toculture, and the knowledge and experience of the audience. Connotations areextensions of the denotative meaning. Barthes suggested, therefore, that connotation isa “second-order of signification”, in a chain of possible meanings. The form of thesignifier can contribute to the connotative meaning, so that the same signified can havedifferent meaning when presented in a different manner or style.
Pierce proposed a slightly different model of the sign, identifying its parts as“representamen”, “object” and “interpretant”. In this model, the “representamen” is therepresentational object or form, equating to Saussure’s signifier, the “object” is the thing to which is directly represented, and the “interpretant” is the meaning that is achievedonce the sign has been “evaluated” by the audience. Though Piercian and Saussureanmodels are both in use today, this text will use the Saussurean model of the sign.Pierce also suggested that signs represent their subjects in different ways, thereby fallinginto categories. In the first category of signs, “icons”, representamen resemble theirobject so that not much additional knowledge is required for a correct interpretation ofa sign, as in photographs. Indexical signs (“indices”) are directly connected to theobject, but do not resemble it. Most “natural signs”, such as footprints, fall into thiscategory. In the third category, “symbols”, the relationship between the representamenand object is established arbitrarily, as in “images, diagrams, and metaphors”.Saussure, however, feels that the term “symbol” is misleading when discussing language,since many symbols do display “a vestige of natural connection” between the signifierand signified, and are therefore never entirely arbitrary.
Saussure noted that, although in semiotic analysis we assess each sign individually, wegenerally do not encounter signs alone. This illustrates the structuralist view that “thenature of every element in any given situation has no significance by itself, and in fact isdetermined by its relationship to all other elements involved in that situation”.Therefore the context, the relationship between signs, must also be analysed. This vein ofsemiotic analysis investigates the meaning of a sign as developed according to
стр. 4 из 5
relationships between other signs in a group, alternative signs in the same set, and withinculturally established contexts.
Saussure introduced the “systems” within which signs are categorised. The sign is the“basic unit” of any “language”, ranging from words to military signals, and it is within the context of this language that the sign in understood. This context must bedivided into the language itself (a “formal system”, with “rules and conventions”)and individual instances of use, which Saussure termed “langue” and “parole”respectively. Because language is capable of “generating new aspects of itself”,parole, or “the execution of language”, results in unique contexts that are defined by“individual speakers”.
In any instance of parole, the meaning of a sign can be affected by syntagmatic andparadigmatic relations. The “syntagm” is the part of the text (in linguistics, thesentence) in which the sign is used alongside other signs from the same langue. Eachsign in such a sequence of signs is understood in terms of its syntagmatic relations withthe other signs that appear alongside it. So, for example, the meaning of a word isaffected by its particular use in a sentence. Saussure presents this relationship as“horizontal”, since language is received in a linear fashion. As well as by thepresence of other signs, meaning is determined by alternative signs, notable in theirabsence. Saussure identified “associative” relations (more commonly, “paradigmatic”relations) with other signs which could have been used in the same context. Thesealternative signs are vertically located within the same “paradigm” (set of signsbelonging to the same category). For example, the use of the word ‘tree’ as opposed to‘bush’ must signify a plant larger than a bush, otherwise ‘bush’ would have been used.The same syntagmatic and paradigmatic analyses can be applied outside of linguistics,as Barthes demonstrates when discussing clothes. Barthes identifies “items which cannotbe worn at the same time on the same part of the body (such as hats, trousers, shoes)” as having paradigmatic relations, while “the syntagmatic dimension is the juxtaposition ofdifferent elements at the same time in a complete ensemble”.When there are many possible readings of a sign (that is, many possible denotations, andhence connotations), context can also reduce the number of likely interpretations througha process of “anchorage”. Barthes identified anchorage in image captions, where “thetext directs the reader through the signifieds of the image, causing him to avoid someand receive others”. By anchoring a sign, it is possible to establish a “preferredmeaning”.
A “code” is a way of communicating meaning that has been “conventionalized” by anysociety or group. All signs depend on the receiver being familiar (consciously orunconsciously) with the language, or “code”, to which the sign belongs. SpokenEnglish, for example, requires understanding of the sounds that represent words in the English language; “even an indexical and iconic sign such as a photograph involves atranslation from three dimensions into two”. Codes, therefore, “provide a frameworkwithin which signs make sense”.
стр. 5 из 5
Societies have “primary” codes (usually the “dominant ‘natural’ language”), and withinany code, “sub-codes” exist. Language, for example, may be subdivided into “spokenand written forms”.
Barthes proposed “the notion that we ‘encode’ our experience of the world in order thatwe may experience it”. By a process of encoding, we “invent the world we inhabit”,representing and understanding it in ways that are specific to our social group. Sincecodes vary from culture to culture, the same sign or text may have different meaning todifferent audiences, and interpretations may vary from the message intended by the encoder. Such unintended interpretations are described by Umberto Eco as “aberrantdecoding”. In many instances aberrant decoding is unavoidable, as texts can be madeavailable to diverse, even international, audiences.
@@ -0,0 +1,51 @@
МГУ имени М.В. Ломоносова
Вступительные испытания по иностранному языку
Английский язык
2023 год
стр. 1 из 5
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
Реферат должен содержать два смысловых блока:
1. объективная/авторская информация
₋            проблематика, обсуждаемая автором (предмет и объект исследования);
₋            выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
2. интерпретация прочитанного (аргументация предложенных выводов обязательна)
₋            текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее;
₋            текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите аргументы) в Вашем понимании поднимаемых вопросов и темы в целом.
Структура работы:
Введение (определение жанра текста, смысла названия и общей темы).
Основная часть (два указанных выше смысловых блока).
Заключение (место и значение исследования данной темы в современной лингвистике).
Важные аспекты:
Напишите реферат прочитанного текста в количестве 500 слов.
Копирование 4 слов подряд из исходного текста или других источников расценивается некорректным цитированием, в случае обнаружения чего работа оценивается только на предмет языковой грамотности.
Характеристики научного стиля реферата: логическая последовательность изложения, текстовая связность; стремление автора к точности, сжатости, однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/академического стиля.
стр. 2 из 5
Some Consequences for Theories of Conceptual Structure
(extract)
By George Lakoff and Mark Johnson
From Metaphors We Live By, 2004.
Any adequate theory of the human conceptual system will have to give an account of how concepts are (1) grounded, (2) structured, (3) related to each other, and (4) defined. We have argued that most of our conceptual system is metaphorically structured. There are several strategies that linguists and logicians have used to handle, without any reference to metaphor, what we have called metaphorical concepts.
We will look at the strategy of abstraction. To see how it differs from the account we have offered, consider the word buttress in "He buttressed the wall" and "He buttressed his argument with more facts." On our account, we understand buttress in "He buttressed his argument" in terms of the concept BUTTRESS, which is part of the BUILDING gestalt. Since the concept ARGUMENT is comprehended partly in terms of the metaphor AN ARGUMENT IS A BUILDING, the meaning of "buttress" in the concept ARGUMENT will follow from the meaning it has in the concept BUILDING, plus the way that the BUILDING metaphor in general structures the concept ARGUMENT. Thus we do not need an independent definition for the concept BUTTRESS in "He buttressed his argument."
Against this, the abstraction view claims that there is a single, very general, and abstract concept BUTTRESS, which is neutral between the BUILDING "buttress" and the ARGUMENT "buttress." According to this view, "He buttressed the wall" and "He buttressed his argument" are both special cases of the same very abstract concept.
We would now like to show why the abstraction theory cannot account for the kinds of facts that have led us to the theory of metaphorical concepts—in particular, the facts concerning the metaphorical types (orientational, physical, and structural) and their properties (internal systematicity, external systematicity, grounding, and coherence).
The abstraction theory is inadequate in several respects. First, it does not seem to make any sense at all with respect to UP-DOWN orientation metaphors, such as HAPPY IS UP, CONTROL IS UP, MORE IS UP, VIRTUE IS UP, THE FUTURE IS UP, REASON IS UP, etc. What single general concept with any content at all could be an abstraction of HEIGHT, HAPPINESS, CONTROL, MORE, VIRTUE, THE FUTURE, REASON, and NORTH and would precisely fit them all? Moreover, it would seem that UP and DOWN could not be at the same level of abstraction, since UP applies to the FUTURE, while DOWN does not apply to the PAST. We account for this by partial metaphorical structuring, but under the abstraction proposal UP would have to be more abstract in some sense than DOWN, and that does not seem to make sense.
стр. 3 из 5
Second, the abstraction theory would not distinguish between metaphors of the form A is B and those of the form B is A, since it would claim that there are neutral terms covering both domains. For example, English has the LOVE IS A JOURNEY metaphor but no JOURNEYS ARE LOVE metaphor. The abstraction view would deny that love is understood in terms of journeys, and it would be left with the counterintuitive claim that love and journeys are understood in terms of some abstract concept neutral between them.
Third, different metaphors can structure different aspects of a single concept; for example, LOVE IS A JOURNEY, LOVE IS WAR, LOVE IS A PHYSICAL FORCE, LOVE IS MADNESS. Each of these provides one perspective on the concept LOVE and structures one of many aspects of the concept. The abstraction hypothesis would seek a single general concept LOVE abstract enough to fit all of these aspects. Even if this were possible, it would miss the point that these metaphors are not jointly characterizing a core concept LOVE but are separately characterizing different aspects of LOVE.
Fourth, if we look at structural metaphors of the form A is B (e.g., LOVE IS A JOURNEY, THE MIND IS A MACHINE, IDEAS ARE FOOD, AN ARGUMENT IS A BUILDING), we find that B (the defining concept) is more clearly delineated in our experience and typically more concrete than A (the defined concept). Moreover, there is always more in the defining concept than is carried over to the defined concept. Take IDEAS ARE FOOD. We may have raw facts and half-baked ideas, but there are no sauteed, broiled, or poached ideas. In AN ARGUMENT IS A BUILDING only the foundation and outer shell play a part in the metaphor, not the inner rooms, corridors, roof, etc. We have explained this asymmetry in the following way: the less clearly delineated (and usually less concrete) concepts are partially understood in terms of the more clearly delineated (and usually more concrete) concepts, which are directly grounded in our experience. The abstraction view has no explanation for this asymmetry, since it cannot explain the tendency to understand the less concrete in terms of the more concrete.
Fifth, under the abstraction proposal there are no metaphorical concepts at all and, therefore, no reason to expect the kind of systematicity that we have found. Thus, for example, there is no reason to expect a whole system of food concepts to apply to ideas or a whole system of building concepts to apply to arguments. There is no reason to expect the kind of internal consistency that we found in the TIME IS A MOVING OBJECT cases. In general, the abstraction view cannot explain the facts of internal systematicity.
Abstraction also fails to explain external systematicity. Our proposal accounts for the way that various metaphors for a single concept (e.g., the JOURNEY, BUILDING, CONTAINER, and WAR metaphors for arguments) overlap in the way that they do. This is based on the shared purposes and shared entailments of the metaphorical concepts. The way that individual concepts (such as CORE, FOUNDATION, COVER, SHOOT DOWN, etc.) mix with each other is predicted on the basis of shared purposes and entailments in the entire metaphorical system. Since the abstraction proposal does not have any metaphorical systems, it cannot explain why metaphors can mix the way they do.
стр. 4 из 5
Sixth, since the abstraction proposal has no partial metaphorical structuring, it cannot account for metaphorical extensions into the unused part of the metaphor, as in "Your theory is constructed out of cheap stucco" and many others that fall within the unused portion of the THEORIES ARE BUILDINGS metaphor.
Finally, the abstraction hypothesis assumes, in the case of LOVE IS A JOURNEY, for example, that there is a set of abstract concepts, neutral with respect to love and journeys, that can "fit" or "apply to" both of them. But in order for such abstract concepts to "fit" or "apply to" love, the concept LOVE must be independently structured so that there can be such a "fit." As we will show, LOVE is not a concept that has a clearly delineated structure; whatever structure it has it gets only via metaphors. But the abstraction view, which has no metaphors to do the structuring, must assume that a structure as clearly delineated as the relevant aspects of journeys exists independently for the concept LovE. It's hard to imagine how.
Another theory, the one of homonymy, looks at metaphorical concepts from a different perspective. The homonymy view takes the opposite to the abstraction tack. Instead of claiming that there is one abstract and neutral concept BUTTRESS, the homonymy view claims that there are two different and independent concepts, BUTTRESS1 and BUTTRESS2.
There is a strong homonymy view, according to which BUTTRESS1 and BUTTRESS2 are entirely different and have nothing to do with each other, since one refers to physical objects (building parts) and the other to an abstract concept (a part of an argument).
The weak homonymy view maintains that there are distinct and independent concepts BUTTRESS1 and BUTTRESS2 but allows that their meanings may be similar in some respects and that the concepts are related by virtue of this similarity. It denies, however, that either concept is understood in terms of the other. All it claims is that the two concepts have something in common: an abstract similarity. On this point, the weak homonymy view shares an element with the abstraction view, since the abstract similarity would have precisely the properties of the core concept that is hypothesized by the abstraction theory.
In general, the strong homonymy view cannot account for the relationships that we have identified in systems of metaphorical concepts; that is, it views as accidental all the phenomena that we explain in systematic terms.
In the first place, the strong homonymy position cannot account for any of the internal systematicity that we have described. For example, it would be possible, according to this view, for "I'm feeling up" to mean "I'm happy" and, simultaneously, for "my spirits rose" to mean "I got sadder." Nor can this position account for why the whole system of words used for war should apply in a systematic way to arguments or why a system of food terminology should apply in a systematic way to ideas.
Second, the strong homonymy view has the same problems with cases of external systematicity. That is, it cannot account for the overlap of metaphors and the possibility of mixing. It cannot explain, for example, why the "ground covered" in an argument can refer to the same thingas the "content" of the argument. This holds in general for all the examples of mixing that we have given.
стр. 5 из 5
Third, the strong homonymy view cannot explain extensions of the used (or unused) portion of a metaphor, as in "His theories are Gothic and covered with gargoyles." Since that theory has no general metaphors like AN ARGUMENT IS A BUILDING, it must view such cases as random.
The weak homonymy view is superior to the strong view precisely because it does allow for the possibility of such relationships. In particular, it holds that the various concepts expressed by a single word can in many cases be related by similarity. The weak homonymy view takes such similarities as given and assumes that they are sufficient to account for all the phenomena that we have observed, though without the use of any metaphorical structuring.
The most obvious difference between the weak homonymy position and ours is that it has no notion of understanding one thing in terms of another and hence no general metaphorical structuring. The reason for this is that most of those who hold this position are not concerned with how our conceptual system is grounded in experience and how understanding emerges from such grounding. Most of the inadequacies we find in the weak homonymy position have to do with its lack of concern for issues of understanding and grounding. These same inadequacies will, of course, apply also to the strong version of the homonymy position.
To our knowledge, no one explicitly holds the strong homonymy position, according to which concepts expressed by the same word (like the two senses of "buttress" or the many senses of "in"), are independent and have no significant relationships. Those who hold the homonymy position tend to identify themselves as holding the weak position, where the interdependencies and interrelationships that are observed between concepts are to be accounted for by similarities based on the inherent nature of the concept. However, to our knowledge, no one has ever begun to provide a detailed account of a theory of similarity that could deal with the wide range of examples we have discussed. Although virtually all homonymy theorists espouse the weak version, in practice there seem to be only strong homonymy theories, since no one has attempted to provide the detailed account of similarity necessary to maintain the weak version of the theory. And there is a good reason why no attempt has been made to give such a detailed account of the kinds of examples we have been discussing. The reason is that such an account would require one to address the issue of how we comprehend and understand areas of experience that are not well-defined in their own terms and must be grasped in terms of other areas of experience. In general, philosophers and linguists have not been concerned with such questions.
@@ -0,0 +1,35 @@
МГУ имени М.В. Ломоносова
Вступительные испытания по иностранному языку
Магистратура
Итальянский язык 2021 год
Задание
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
Реферат должен содержать два смысловых блока:
1. объективная/авторская информация
₋            проблематика, обсуждаемая автором (предмет и объект исследования);
₋            выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
2. интерпретация прочитанного (аргументация обязательна)
₋            текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее (приведите свои аргументы);
₋            текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите свои аргументы) в Вашем понимании поднимаемых вопросов и, в заключение, какую роль в разработке темы играет данное исследование в современной лингвистике (приведите свои аргументы).
Структура работы должна включать:
Введение (определение жанра текста, смысла названия и общей темы в рамках лингвистического направления/ на стыке лингвистических направлений).
Основная часть (два указанных выше смысловых блока).
Заключение (место и значение исследования данной темы в современной лингвистике).
В реферате должны быть учтены следующие важные аспекты:
Напишите реферат прочитанного текста в количестве 500 слов.
Копирование 4 слов подряд из исходного текста или других источников расценивается плагиатом, в случае обнаружения которого работа оценивается только на предмет языковой грамотности.
Характеристики научного стиля реферата: объективность, логическая последовательность изложения, текстовая связность; стремление автора к точности изложения фактов и терминологии, сжатости и однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/ академического стиля.
L’italiano e le carenze linguistiche: perché non è (solo) colpa della scuola
di Filomena Fuduli Sorrentino
Oggi l’analfabetismo è praticamente scomparso ma esiste un gran numero di analfabeti funzionali, cioè, persone che sanno leggere ma non riescono a comprendere un testo scritto o un discorso formale. Per capire bene il problema dell’italiano odierno dobbiamo ricordare che, fino al tempo dell’unità d’Italia, l’italiano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta l’Italia erano tra il 2,5% e il 10% dell’intera popolazione.
I docenti si lamentano delle carenze linguistiche dei loro studenti di vario tipo: grammatica, sintassi, e lessico. Il problema della lingua scritta si affronta anche con l’inglese nelle scuole di New York, dove per rimediare hanno attivato corsi di recupero di scrittura e lettura con docenti specializzati.
Il tema dell’uso dell’italiano corretto o errato è simile a quello affrontato in passato con la “questione della lingua”. Una storia vecchia che va di pari passo con quella della letteratura italiana: dal De vulgari eloquentia di Dante, alle Prose della volgar lingua del Bembo, il risciacquo dei panni nell’Arno del Manzoni, fino a giungere a Calvino e al neo-italiano industriale di Pasolini. L’unità linguistica iniziò con Dante, che attraverso la sua opera De Vulgari Eloquentia esaminò pregi e difetti delle diverse lingue parlate in tutta la penisola e in seguito l’italiano si sviluppò dal fiorentino, ma il percorso non fu facile. All’inizio del XIX secolo i dialetti italiani erano così diversi da essere reciprocamente incomprensibili, e verso di essi c’è sempre stato pregiudizio; la gente pensava che l’italiano standard fosse la lingua usata dalla borghesia mentre i dialetti venivano usati dagli agricoltori e dalla classe operaia.
Il 22 dicembre 1947 venne approvata la Costituzione con 453 voti a favore e 62 contrari, e nel 1948 entrava in vigore il diritto allo studio con l’articolo 34: “La scuola è aperta a tutti. L’istruzione inferiore, impartita per almeno otto anni, è obbligatoria e gratuita. I capaci e meritevoli, anche se privi di mezzi, hanno diritto di raggiungere i gradi più alti degli studi. La Repubblica rende effettivo questo diritto con borse di studio, assegni alle famiglie ed altre provvidenze, che devono essere attribuite per concorso.” Ma in realtà il diritto non era garantito a tutti i ragazzi per vari motivi: l’accesso all’istruzione superiore e all’università era riservato ai ragazzi di famiglie agiate, mentre quelli provenienti da famiglie povere e da classe operaia e agricola erano una risorsa economica per la loro famiglia, quindi dovevano andare a lavorare e non potevano frequentare la scuola. E così, fino agli anni 50-60, molti bambini non finivano nemmeno la scuola elementare. Nel 1950, anche se il paese stava attraversando un periodo di ricostruzione infrastrutturale, economica, sociale e politica, meno del 20% della popolazione italiana parlava correntemente l’italiano nella vita quotidiana. L’analfabetismo e il semi-analfabetismo erano ampiamente presenti nella popolazione.
Eppure bisogna aspettare il decreto statale del 2007 affinché l’età di frequenza scolastica obbligatoria sia revocata dai 14 ai 16 anni e tutti gli studenti possano completare almeno 10 anni di istruzione. Comunque, non è stata la scuola a diffondere l’italiano in tutta la penisola durante gli anni, bensì l’introduzione della televisione nel 1954, anche se aveva un solo canale. I programmi televisivi cominciarono a essere trasmessi dalla RAI, l’emittente statale. Negli anni tra il 1958 e il 1962 la televisione divenne un modo per riunire le persone (pochissime persone avevano effettivamente un televisore in casa) e soprattutto un modo per seguire programmi culturali e linguistici.
Infatti, tra il 1960 e il 1968 la RAI trasmise uno spettacolo di pomeriggio che si chiamava “Non è mai troppo tardi”, presentato dal maestro Alberto Manzi, responsabile dell’alfabetizzazione della popolazione italiana che non aveva avuto accesso alla scuola ed era rimasta completamente analfabeta. Con il programma del maestro Manzi molti analfabeti hanno imparato a leggere e a scrivere e circa un milione e mezzo di italiani hanno ottenuto il certificato di istruzione primaria (quinta elementare). Eppure, mentre durante i primi 20 anni della sua esistenza la televisione dello Stato ebbe una funzione istruttiva, dagli anni ’80 in poi si concentrò su spettacoli con comportamenti banali, e a volte anche volgari e lontani dalla realtà, ed ebbe un effetto negativo sull’istruzione culturale delle generazioni più giovani. Il linguaggio della televisione è diventato molto più semplice, pieno di slang, privo di sintassi, e spesso errato, e impoverisce la lingua italiana. In altre parole, una forma di “populismo linguistico” progettato per attrarre i giovani e la massa di persone prive di un’istruzione culturale.
Nel 1970 apparve il libro “Lettere da una tarantata” con una nota linguistica dello storico della lingua italiana Tullio De Mauro che diceva: “non ha padronanza dell’italiano e trova quindi difficoltà a scrivere. Nelle sue lettere sono presenti numerosi errori ortografici e forti storpiature dialettali, ma lei non si scoraggia e si convince che l’importante è farsi capire”. Lettere da una tarantata costituisce un importante punto di riferimento per capire la nascita e lo sviluppo dell’italiano popolare. In seguito, il libro diventerà un testo di riferimento per gli studi sull’uso dell’“italiano popolare unitario”; il libro raccoglie le 65 lettere che la protagonista Anna, contadina semianalfabeta, nata a Ruffano nel 1898, inviò tra il 1959 e il 1965 all’antropologa Annabella Rossi. Anna rappresenta per Tullio De Mauro l’incarnazione della volontà di comunicare delle classi inferiori. Egli ha apprezzato il suo stile, definendolo vivace e originale, e ha invece criticato l’italiano insegnato nella scuola, paragonandolo a un “rullo compressore” che rende la lingua piatta e vuota.
L’etichetta di italiano popolare fu introdotta nel 1970 da Tullio De Mauro e Manlio Cortelazzo. Lo storico De Mauro lo aveva definito “il modo d’esprimersi di un incolto che, sotto la spinta di comunicare e senza addestramento, maneggia quella che ottimisticamente si chiama la lingua nazionale”. Cortelazzo invece lo aveva definito come “il tipo di italiano imperfettamente acquisito da chi ha per madrelingua il dialetto”. Alcuni studiosi, partendo dalla concezione di De Mauro, mettono in dubbio l’effettiva presenza di un italiano standard, e hanno valutato positivamente l’italiano popolare considerandolo la ricchezza di quelle classi sociali con una competenza linguistica minima, ma pura e autentica. Altri studiosi invece hanno seguito la concezione di Cortelazzo riconoscendo ampio vigore all’italiano standard, e hanno evidenziato l’inferiorità dell’italiano popolare, sostenendo la necessità di estirparlo.
Per capire bene il problema dell’italiano odierno dobbiamo ricordare che, fino al tempo dell’unità d’Italia, l’italiano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta l’Italia erano tra il 2,5% e il 10% dell’intera popolazione. In seguito la lingua italiana è arrivata a essere parlata da circa 60 milioni di abitanti sparsi in tutta la penisola, ma il veloce cambiamento ha assorbito tratti dei dialetti locali e dell’italiano regionale presentano alterazioni della morfologia e della sintassi dell’italiano standard, derivante dalla tradizione scritta e adatto agli usi formali. Questa evoluzione graduale della lingua italiana successe senza che la scuola riuscisse a stare al passo dei suoi cambiamenti, resi estremamente veloci anche dai mezzi di comunicazione di massa.
Inoltre, l’italiano e il dialetto sono due lingue diverse ma gli italiani, invece di concepire una divisione tra le due lingue, hanno continuato a mischiarle tra loro e così oggi abbiamo dialetti italianizzati, e un italiano dialettizzato e pieno di errori. Eppure, negli ultimi 50 anni molti termini regionali, dalla Toscana, dalla Lombardia, dal Veneto, da Napoli e dalla Sicilia, sono entrati nella lingua nazionale e non sorprende che i dialetti siano stati studiati da linguisti e usati nella letteratura e nella poesia. Però, nonostante l’italiano sia una lingua ricca di termini, espressioni idiomatiche e sfumature semantiche, e i dizionari più completi possono contenere da 80.000 a 250.000 voci, le ricerche condotte dallo storico della lingua italiana, Tullio De Mauro, alcuni anni prima della sua morte (1932-2017), hanno dimostrato che circa la metà della popolazione usa solo 3000 parole nella conversazione di tutti i giorni.
Dunque, la lingua evolve e cambia e questo è stato da sempre dimostrato da studiosi, linguisti, e sociolinguisti. Nel 2013 De Mauro aveva affermato: “La lingua italiana – per chi la sa usare leggendo, scrivendo o parlando – sta bene. Stanno male gli italiani che la sanno usare poco. Stanno male non per ragioni puristiche o astratte, ma perché conoscere male la lingua nazionale significa studiare male – se uno ci prova – altre lingue e significa avere una vita di relazione modesta, non capire tante cose che servono sul lavoro, nella produzione”. Il grande e illustre prof. De Mauro aveva ragione, la lingua italiana è ricca di termini e, purtroppo, molti italiani conoscono un numero limitato di vocaboli sia della lingua italiana e sia di quella regionale, e di conseguenza le loro abilità linguistiche sono e rimangono limitate.
Per concludere: se nei licei e nelle università l’italiano corretto lo sappiamo scrivere e parlare in pochi, non dipende dalle scuole o dai docenti ma dalle famiglie e dalla società. Le scuole sono aperte a tutti e oggi tutti hanno accesso alle università ma questo non garantisce a tutti gli studenti una correttezza ortografica e grammaticale senza che essi si applicano a migliorare le loro carenze linguistiche e grammaticali. Molti ragazzi non hanno sufficiente possesso degli strumenti linguistici di base nelle medie e nei licei e questa lacuna di apprendimento non si può più colmare nelle università.
+99
View File
@@ -0,0 +1,99 @@
# 10 — Code Review (Sonnet) + Fix
**Дата:** 2026-07-22
**Источник:** Анализ Sonnet (Claude) через VS Code Copilot Chat
---
## Структура проекта
### apps — исходный код приложений
| Папка | Роль | Зависимости |
|---|---|---|
| `iot-producer` | Генерирует события → RabbitMQ | `amqplib`, `express` |
| `iot-consumer` | RabbitMQ → Redis + MongoDB | `amqplib`, `redis`, `mongodb`, `express` |
| `iot-dashboard` | Redis → UI (Chart.js) | `express`, `redis` |
| `iot-demo` | Только HISTORY — не приложение | — |
### IOT_RMQ_DEMO — Terraform-конфиг стенда
6 ресурсов в 5 файлах: infra.tf (RabbitMQ + Redis + MongoDB), producer.tf, consumer.tf, dashboard.tf, locals.tf (все параметры вынесены сюда).
---
## Что хорошо
**Архитектура:**
- Чёткое разделение ответственности: каждое приложение делает одно дело
- Dashboard читает **только Redis** — правильно, никакой нагрузки на MongoDB
- `ch.prefetch(10)` у consumer — нормальное управление backpressure
- TTL-индекс в MongoDB (`expireAfterSeconds: 604800`, 7 дней) — события автоматически удалятся
- `zRemRangeByRank("iot:recent", 0, -1001)` — лента событий ограничена 1000 записями
**Terraform:**
- `adopt_existing_on_create = true` — удобно при повторных `apply` без пересоздания
- `depends_on` расставлены корректно: consumer ждёт все три сервиса, dashboard — только Redis
- Весь хардкод вынесен в locals.tf, в ресурсах чисто
- `sensitive = true` на `api_token`
---
## Проблемы (на момент анализа)
### 🔴 Баг: `MONGO_URI` без схемы `mongodb://` — **ИСПРАВЛЕНО 2026-07-22**
В consumer.tf строка формировалась так:
```hcl
MONGO_URI = "${local.cons_mgo_user}:${local.cons_mgo_pass}@${local.cons_mgo_host}:27017/iot?authSource=admin"
```
Результат: `admin:@hostname:27017/iot?authSource=admin`
В consumer.js `MongoClient` получал этот URI и падал — схема `mongodb://` отсутствовала. Дефолтный fallback `mongodb://localhost:27017/iot` не срабатывал, потому что переменная окружения была задана (просто невалидна).
**Фикс (2026-07-22):**
```hcl
MONGO_URI = "mongodb://${local.cons_mgo_user}:${local.cons_mgo_pass}@${local.cons_mgo_host}:27017/iot?authSource=admin"
```
Результат: `mongodb://admin:@hostname:27017/iot?authSource=admin`
**Верификация:** consumer `errors=0` после фикса.
### 🟡 nack с requeue=true — потенциальный infinite loop
В consumer.js:
```js
ch.nack(msg, false, true); // requeue = true
```
При систематической ошибке (например MongoDB недоступна) сообщение будет бесконечно возвращаться в очередь и перечитываться.
**Рекомендация:** `requeue=false` + логировать потерянное сообщение.
### 🟡 `s3_name` — объявлена, но не используется
В main.tf есть переменная `s3_name`, которая нигде в TF-файлах стенда не применяется. Legacy от шаблона.
### 🟡 `.trigger` — пустой файл в `iot-producer`
Файл .trigger пустой. Если нужен для force-redeploy — добавить комментарий.
### 🟡 `requirements.txt` в Node.js-папках
Файлы `requirements.txt` остались от Flask-экспериментов в iot-producer и iot-consumer. Мусор.
### 🟡 MongoDB без пароля
`cons_mgo_pass = ""` в locals.tf. Для демо-стенда приемлемо, но зафиксировано как известное ограничение.
---
## Статус на 2026-07-22
| Проблема | Статус |
|---|---|
| MONGO_URI без mongodb:// | ✅ Исправлено |
| nack + requeue=true | 🟡 Не исправлено (низкий приоритет) |
| s3_name не используется | 🟡 Не исправлено |
| .trigger пустой | 🟡 Не исправлено |
| requirements.txt мусор | 🟡 Не исправлено |
| MongoDB без пароля | ℹ️ Приемлемо для демо |
@@ -0,0 +1,69 @@
# 2026-07-16 — Успешный тест nested-провайдера (5.1.2)
## Результат
PostgreSQL создан на TEST-стенде с nested HCL-блоками. Провайдер 5.1.2, registry `registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->`.
## Конфигурация (nubes_postgres.tf)
```hcl
resource "nubes_postgres" "npg" {
resource_name = "pgtst01"
startup_configuration = {
resource_realm = var.realm
}
cluster_configuration = {
cpu = 500
memory = 512
replicas = 1
disk = 10
}
access_configuration = {
master_ip_space = "no-needed"
master_access_list = jsonencode(["10.0.0.0/8"])
slave_ip_space = "no-needed"
slave_access_list = jsonencode([])
}
postgres_configuration = {
version = "17"
ssl_required = true
pooler_master = false
pooler_slave = false
}
postgres_conf = jsonencode([{ param_name = "log_connections", param_value = "" }])
backup_configuration = {
s3_uid = var.s3_uid
retain = 14
schedule = "0 0 * * *"
}
autoscale_configuration = {
enabled = false
schedule = 0
percent = 10
quota = 100
}
}
```
## State после apply
```
nubes_postgres.npg
nubes_postgres_database.pg_db_2
nubes_postgres_user.pg_user_0
nubes_s3bucket.bukka0
```
## Операции (все 201 OK)
- create (#18) — 2 мин 16 сек
- resume (#6) — восстановление после suspend
- create_user × 3
- create_database × 3
- delete_user × 2
- delete_database × 3
- suspend
## Версия
5.1.2 (test), 2.1.0 (prod), 3.1.0 (dev)
## Registry
`registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->` (kube5s.ru DNS → ingress 185.247.187.151)
Сертификат: `registry-kube5s-tls` (Let's Encrypt)
@@ -0,0 +1,89 @@
# 2026-07-16 — SubParams: map-fixed → nested Terraform attributes
## Контекст
Генератор ресурсов не обрабатывал `map-fixed`/`array-map-fixed` параметры —
они шли как `types.String`, пользователь был вынужден писать JSON руками.
Теперь YAML содержит `sub_params` (получены через метод Виталия —
`/instanceOperations/default/{id}` с `dataDescriptor`), и генератор
раскрывает их во вложенные Terraform-блоки.
## Изменения (2026-07-16, ветка svc-api)
### Подготовка
- Переход на Gateway API (`lk-api-gateway`) для всех стендов
- Метод Виталия: `/instanceOperations/default/{id}` вместо `/serviceOperation/{id}`
- YAML сгенерированы заново: dev=50, test=48, prod=46
- Списки сервисов обновлены из Gateway
- DDoS-Guard: User-Agent + Referer для всех запросов
- LEGACY-пометки на всех старых `deck-api`
### Размоноличивание templates.go
- `templates.go` (1224 строки) → 3 файла: `instance.go`, `subresource.go`, `action.go`
- Имена констант (`Instance`, `Subresource`, `Action`) не менялись
### Багфиксы lifecycle (Соннет)
- **Баг A**: нет `HasError()` guard после диагностик в Create — сайд-эффект выполнялся вопреки ошибкам
- **Баг B**: `not created` → авто-delete+create в crud.go противоречил философии (должен быть hard error)
### SubParams — вложенные Terraform-блоки
**Изменённые файлы:**
| Файл | Что |
|------|-----|
| `TOOLS/resource-generator/internal/types/types.go` | `SubParams []Param`, `IsNested bool` в `Param` |
| `TOOLS/resource-generator/internal/loader/loader.go` | `ConvertParams` — рекурсивная конвертация SubParams; `NormalizeParamType` → `map-fixed`/`array-map-fixed` |
| `TOOLS/resource-generator/internal/helpers/helpers.go` | 8 новых функций: `IsNested`, `IsNestedList`, `NestedModelName`, `NestedTfType`, `NestedSchemaType`, `NestedSchemaBlock`, `NestedSchemaEnd`, `NestedJSONExpr`, `SubSchemaType`, `SubDefaultExpr` |
| `TOOLS/resource-generator/internal/templates/instance.go` | nested struct'ы перед Model, `SingleNestedAttribute`/`ListNestedAttribute` в Schema, `BuildJSON` в Create/Modify |
| `TOOLS/resource-generator/internal/writers/writers.go` | 10 новых template-функций зарегистрировано |
| `provider/internal/resources_core/helpers.go` | `BuildJSON(map[string]string) string` — строит JSON из map |
**Что генерируется (пример postgres):**
```go
// Вложенный struct
type PostgresClusterConfigurationModel struct {
Cpu types.Int64 `tfsdk:"cpu" json:"cpu"`
Memory types.Int64 `tfsdk:"memory" json:"memory"`
Replicas types.Int64 `tfsdk:"replicas" json:"replicas"`
Disk types.Int64 `tfsdk:"disk" json:"disk"`
}
// В Model — указатель на nested struct
ClusterConfiguration *PostgresClusterConfigurationModel `tfsdk:"cluster_configuration"`
// В Schema — SingleNestedAttribute
"cluster_configuration": schema.SingleNestedAttribute{Required: true,
Attributes: map[string]schema.Attribute{
"cpu": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500)},
...
},
},
// В Create/Modify — JSON через BuildJSON
params[788] = resources_core.BuildJSON(map[string]string{
"cpu": fmt.Sprintf("%d", data.ClusterConfiguration.Cpu.ValueInt64()),
...
})
```
**Для array-map-fixed** (postgresConf) — `ListNestedAttribute` + `[]Model`.
### Результаты генерации
| Стенд | Go-файлов |
|-------|-----------|
| dev | 73 |
| test | 70 |
| prod | 65 |
### Подводные камни (учтены)
- **Required + Default**: подполя с default → `Optional + Computed + Default`
- **JSON-ключи**: `json:"cpu"` теги = оригинальный code (camelCase)
- **types.* в JSON**: `BuildJSON` вместо `json.Marshal` (types.Int64 не маршалится как число)
- **value_list**: enum-валидаторы — out of scope
- **SubParams только для Instance**: subresource/action используют плоские параметры
### Версия
5.0.68 → 5.0.73
@@ -0,0 +1,65 @@
# 2026-07-21 — CRUD-стенд: три приложения + PG
## Сделано
### Провайдер
- TEST 5.1.16, DEV 3.1.13 — fix: modify only if `hasServiceParamChanges` (не дёргает API при смене только git_revision)
- Шаблон — sequential modify→redeploy вместо if-else
### TEST_STAND/CRUD — рефакторинг
- `LUCEE/` → `CRUD/` (папка переименована)
- `nubes_postgres.npg_lucee` → `nubes_postgres.main_pg`
- Файлы: `nubes_postgres_lucee.tf` → `postgres.tf`, `userUNDdb.tf` → `postgres_user_db.tf`
- Добавлены: `flask.tf`, `nodejs.tf`
- `locals.tf` — каждая строка с комментарием
- `terraform.tfvars.example` — только нужные поля с пояснениями
- `README.md` — для нового пользователя (где брать токен, как запускать)
### DEV_STAND/CRUD — аналог TEST
- Те же файлы, провайдер `nubes-dev`, версия `3.1.13`
- `locals.tf` — каждая строка с комментарием
- `terraform.tfvars.example` — только нужные поля
### Приложения (три репо)
| Репо | Хеш | Что |
|---|---|---|
| `tfluceecrud` | `8268568` | Убран `DROP TABLE` → `CREATE TABLE IF NOT EXISTS` |
| `tfflaskcrud` | `54746e9` | `init_db()` на уровне модуля (gunicorn) |
| `tfnodejscrud` | `809d30b` | `app.listen` вне `initDB.then()`, SSL в Pool |
Все три — идентичный SQL, одна таблица `crud_items`, Nubes design.
### Баги исправлены
1. **Lucee DROP TABLE** — при каждом старте приложения таблица дропалась, данные терялись
2. **Flask init_db** — только в `__main__`, под gunicorn таблица не создавалась
3. **Node.js startup** — `app.listen` внутри `.then()` — если PG не ответил, сервер не стартовал. Исправлено: `app.listen` вынесен наружу, `initDB` с `.catch()`
4. **Node.js PGSSLMODE** — не передавался в `json_env` (только Lucee/Flask). PG требовал SSL → `pg_hba.conf rejects connection ... no encryption`
5. **Flask sslmode** — пытались добавить в `psycopg2.connect()`, но Flask работал и без него (PG в том же кластере). Откачено.
6. **s3_name в документации** — исправлено «S3 → Пользователи» → «S3 → Имя экземпляра»
7. **Домены в комментариях** — везде `.dev.nubes.ru`, убрано ошибочное `.test.nubes.ru`
### tf_examples — репозиторий примеров
- Создан `https://gitea.services.ngcloud.ru/Nail/tf_examples.git`
- `CRUD/` — копия `TEST_STAND/CRUD/`, без токенов (`.gitignore *.tfvars`)
- `SHTURVAL_MGMT/` — management-кластер Штурвал (сервис 148), подробный README
- Корневой README с перечнем примеров
### Импорт инстансов в Terraform
- `terraform import nubes_<тип>.<имя> <instance-uid>`
- UUID из URL: `deck-.../services/instance/detail/<uuid>`
- Импорт работает, но **Read падает с 404 если инстанс создан другим пользователем** — API возвращает 403/404
- Чужие инстансы импортировать нельзя
### README во всех репо
- Три репо приложений — одна строка: «Деплоится через Terraform (nubes_...)»
- Убраны `npm install`, `pip install`, `gunicorn` — всё делает платформа
### Известное ограничение
PG+user+DB и приложения нельзя создать за один apply — `vault_secrets["users"]` не заполнен на момент создания приложений. Два apply. Подробнее в README.
## Важные выводы
- **Не менять код без «делай»** — нарушал неоднократно (Flask sslmode, try() в tf)
- **Проверять YAML перед созданием кода** — структура папок (Flask `site/`, Node.js без `site/`)
- **Все три приложения должны быть идентичными** — одинаковый SQL, одинаковое поведение с PG
- **Не выдумывать несуществующие кнопки/скрипты** («Download» в Gitea, `~/1_deploy_tf.sh`)
+66
View File
@@ -0,0 +1,66 @@
# MAN Format Fix — 2026-08-10
**Проблема:** `service_man` из YAML содержит Markdown (`#`, `##`, `---`, `**`) + HTML (`<br/>`, `&quot;`), но рендерился как сырой текст. Теги `##`, `**`, `---` выводились буквально, не форматируя текст.
**Корень проблемы:** `md_in_html` расширение mkdocs не обрабатывает Markdown внутри `<div markdown="1">` и `<details>` — всё содержимое выводится как plain text.
**Решение:** использовать **нативный mkdocs admonition** `??? note` вместо HTML-тегов.
### Было (сломано)
```go
b.WriteString("<details class=\"man-content\">\n<summary>Справка (MAN)</summary>\n\n")
b.WriteString(htmlToMarkdown(man))
b.WriteString("\n\n</details>\n")
```
```html
<!-- Рендерилось как: -->
# Инструкция --- ## 1. Общая информация **текст**
<!-- Все теги видны буквально -->
```
### Стало (работает)
```go
b.WriteString("??? note \"Справка (MAN)\"\n\n")
md := htmlToMarkdown(man)
for _, line := range strings.Split(md, "\n") {
b.WriteString(" " + line + "\n")
}
b.WriteString("\n")
```
```markdown
??? note "Справка (MAN)"
# Инструкция по развертыванию
---
## 1. Общая информация
**текст**
```
```html
<!-- Рендерится как: -->
<details class="note">
<summary>Справка (MAN)</summary>
<h1>Инструкция по развертыванию</h1>
<hr />
<h2>1. Общая информация</h2>
<p><strong>текст</strong></p>
</details>
```
### Ключевые требования `???` admonition
1. **Пустая строка** после `??? note "Заголовок"` — ОБЯЗАТЕЛЬНА
2. **Все строки контента** с отступом ровно 4 пробела — включая пустые строки
3. `pymdownx.details` должен быть в `markdown_extensions` (уже есть)
### Затронутые файлы
| Файл | Изменение |
|------|-----------|
| `writers/writers.go:buildManualPage()` | `??? note` вместо `<details>` |
| `extra.css` | Убран `.man-content` CSS (больше не нужен) |
+173
View File
@@ -0,0 +1,173 @@
# Code Review провайдера — Opus — 2026-08-31
**Источник:** анализ и код-ревью через VS Code Copilot Chat
**Статус:** анализ завершён; часть исправлений внесена 2026-08-31
## Область анализа
Проверены:
- рукописное ядро провайдера в `provider/internal/core` и `provider/internal/resources_core`;
- CRUD, state management и валидация;
- HTTP-слой и `client.go`;
- регистрация провайдера и TLS-настройки;
- генераторы Go-ресурсов, YAML и build-пайплайн;
- Python- и shell-скрипты;
- gateway.
## Критичные находки
### 1. Отладочный лог с данными инстансов пишется в `/tmp` безусловно
В `provider/internal/core/client.go:629-637` замыкание `debug()` в `FindInstanceByDisplayName` всегда пишет в `/tmp/nubes_find_debug.log` с правами `0644`. В лог попадают `instanceUid`, `displayName` и `serviceId`.
Файл не защищён условием `NUBES_DEBUG_HTTP`, не ротируется и не очищается. Это создаёт риск раскрытия данных и неконтролируемого роста файла.
**Рекомендация:** убрать постоянную запись либо включать её только через явный debug-флаг; использовать безопасный путь и контролируемую ротацию.
### 2. Bearer-токен попадает в stderr при HTTP-отладке
В `provider/internal/core/client.go:1100-1101` вызов `httputil.DumpRequestOut(req, ...)` выводит полный исходящий запрос вместе с заголовком `Authorization: Bearer <token>` при `NUBES_DEBUG_HTTP=1`.
Токен может попасть в логи CI/CD или окружения выполнения.
**Рекомендация:** перед дампом удалять или маскировать `Authorization`; не выводить секреты ни в одном режиме.
### 3. В Python-скрипте сетевые вызовы выполняются без таймаутов
В `scripts/check_cloud_instances.py:87-88` вызовы `self.session.get(...)` не передают `timeout=`. При зависании API процесс может ожидать ответ бесконечно.
**Рекомендация:** добавить явные таймауты ко всем HTTP-вызовам и определить единое значение или конфигурационный параметр.
## Существенные находки
### 4. Retry сетевых ошибок применяется к POST-запросам
В `provider/internal/core/client.go:1113-1120` при сетевой ошибке повторяется любой HTTP-метод, включая POST к `/instances` и `/instanceOperations`.
Если сервер принял запрос, но ответ потерян, повтор может создать дубликат инстанса или операции. Идемпотентность POST не гарантирована.
**Рекомендация:** ограничить retry идемпотентными методами либо использовать идемпотency key и явную серверную поддержку повторов.
### 5. Ответ `401 Unauthorized` включён в retryable
В `provider/internal/core/client.go:1150-1156` статус `401` считается повторяемым. Протухший или неверный токен приводит к трём попыткам с задержкой, маскируя исходную ошибку авторизации и увеличивая время отказа.
**Рекомендация:** исключить `401` из retryable; возвращать ошибку авторизации сразу.
### 6. Gateway раскрывает внутренние upstream-адреса
В `gateway/server.js:60-71` корневой endpoint `/` и обработчик 404 возвращают наружу адреса `upstream` для маршрутов.
Публичный ответ раскрывает внутреннюю топологию сервисов.
**Рекомендация:** убрать `upstream` из публичных ответов; внутренние адреса оставлять только в серверных логах с необходимой санацией.
### 7. Некорректное определение неуспешной операции в Python
В `scripts/check_cloud_instances.py:187-189` используется сравнение `last_op.get("isSuccessful") == False`. При отсутствии поля возвращается `None`, поэтому состояние `OPERATION_FAILED` не определяется.
**Рекомендация:** использовать проверку `is False` либо явно обрабатывать отсутствие ключа согласно контракту API.
## Умеренные находки
### 8. Retry-логика дублируется в трёх местах
В `provider/internal/core/client.go:777-905` похожие циклы retry присутствуют в `doRequest`, `GetInstanceState` и `GetInstanceStateRaw`.
Дублирование увеличивает риск расхождения поведения и повторного появления ошибок безопасности.
**Рекомендация:** вынести общую retry-логику в единый внутренний helper с параметрами метода, таймаутов и политики повторов.
### 9. Пагинация имеет тихий предел 10 000 инстансов
В fallback-ветке `FindInstanceByDisplayName` (`provider/internal/core/client.go:747-749`) поиск прекращается после `page > 100` при размере страницы `100`.
При большем количестве инстансов совпадение может не быть найдено без предупреждения.
**Рекомендация:** убрать произвольный предел либо возвращать диагностируемую ошибку/предупреждение при достижении лимита.
### 10. Ошибка `gofmt` не останавливает генерацию
`FormatSourceOrWarn` в `TOOLS/resource-generator/writers.go:61` при ошибке форматирования только выводит предупреждение и записывает исходник.
В результате pipeline может сохранить неформатированный или потенциально некомпилируемый Go-код.
**Рекомендация:** считать ошибку форматирования фатальной для генерации либо выполнять последующую обязательную компиляционную проверку.
### 11. Секрет передаётся в командной строке shell-скрипта
В `TOOLS/s3_notification_example.sh:74` значение `SECRET_KEY` передаётся аргументом в `mc alias set`.
Секрет может быть виден через `ps` или аналогичный список процессов.
**Рекомендация:** использовать механизм передачи секрета через stdin, переменную окружения, конфигурационный файл с безопасными правами или другой поддерживаемый секретный канал.
## Дополнительные замечания
- В `provider/internal/core/client.go` ссылка на `tools/gen_v2/generate_resources_v2.go` обновлена на актуальный путь `TOOLS/resource-generator/internal/templates/instance.go`.
- В исходниках генератора (`TOOLS/resource-generator/internal/templates/*`, `TOOLS/resource-generator/internal/writers/writers.go`) метка `Code generated by tools/gen_v2` обновлена на `Code generated by TOOLS/resource-generator`.
- Текущий `provider/internal/resources_gen/registry.go` обновлён на новую метку генератора.
- `TOOLS/resource-generator/main.go` переведён на `run()` с корректным `exit code=1` и агрегированным отчётом по ошибкам записи ресурсов (instance/subresource/action).
- Пути debug-логов в `provider/internal/core/client.go` переведены на `os.TempDir()` с override через `NUBES_DEBUG_DIR` (без хардкода `/tmp`).
## Что выглядит хорошо
- Сериализация операций на инстансе через `instanceMutexes` в `client.go` защищает от параллельных операций API.
- TLS настроен с `MinVersion: TLS 1.2`; `InsecureSkipVerify` по умолчанию равен `false`.
- `api_token` отмечен как `Sensitive: true` в схеме провайдера.
- Канонизация JSON для сравнения state устраняет ложные различия из-за порядка ключей.
## Итоговый статус
| Находка | Статус |
|---|---|
| Безусловная запись данных инстансов в `/tmp` | Исправлено: debug gated + права `0600` |
| Bearer-токен в HTTP debug dump | Исправлено: `Authorization` маскируется |
| Python HTTP-вызовы без таймаутов | Исправлено: добавлен `REQUEST_TIMEOUT` |
| Retry POST-запросов | Исправлено: retry сетевых ошибок только для GET |
| `401` в retryable | Исправлено: исключён из retryable |
| Раскрытие upstream в gateway | Исправлено: `upstream` удалён из root-ответа |
| Ошибка определения `OPERATION_FAILED` | Исправлено: сравнение через `is False` |
| Дублирование retry-логики | Исправлено: общий helper для чтения состояния |
| Тихий предел пагинации | Частично исправлено: добавлена явная ошибка при достижении лимита |
| Некритичная ошибка `gofmt` в генераторе | Исправлено: fail-fast при ошибке форматирования |
| Секрет в аргументах shell-команды | Исправлено: исключена передача в argv |
## Выполненные изменения (2026-08-31)
- `provider/internal/core/client.go`:
- debug-лог `FindInstanceByDisplayName` теперь пишется только при `NUBES_DEBUG_HTTP=1`;
- права debug-логов снижены до `0600`;
- в stderr-дампе HTTP-запроса маскируется заголовок `Authorization`;
- retry сетевых ошибок ограничен методом `GET`;
- `401 Unauthorized` удалён из `isRetryable`;
- при достижении лимита fallback-пагинации возвращается явная ошибка.
- `GetInstanceState` и `GetInstanceStateRaw` переведены на общий helper `getInstanceStateWithRetry` с единым retry/HTTP-поведением.
- `scripts/check_cloud_instances.py`:
- добавлен `REQUEST_TIMEOUT = 30` и применён ко всем `session.get(...)`;
- проверка failed-операции изменена на `is False`.
- `gateway/server.js`:
- удалено поле `upstream` из публичного ответа `GET /`.
- `TOOLS/resource-generator/internal/helpers/helpers.go`:
- `FormatSourceOrWarn` переведён на fail-fast: возвращает ошибку при сбое `gofmt`.
- `TOOLS/resource-generator/internal/writers/writers.go`:
- все вызовы форматирования обрабатывают ошибку и прерывают генерацию.
- `TOOLS/resource-generator/main.go`:
- убраны `panic` на первом сбое записи ресурса;
- добавлена агрегация ошибок генерации с отчётом по каждому ресурсу;
- завершение с `exit code=1` и человекочитаемым сообщением в stderr.
- `TOOLS/resource-generator/internal/templates/instance.go`:
- обновлён marker генерации на `Code generated by TOOLS/resource-generator`.
- `TOOLS/resource-generator/internal/templates/subresource.go`:
- обновлён marker генерации на `Code generated by TOOLS/resource-generator`.
- `TOOLS/resource-generator/internal/templates/action.go`:
- обновлён marker генерации на `Code generated by TOOLS/resource-generator`.
- `provider/internal/resources_gen/registry.go`:
- обновлён marker генерации на `Code generated by TOOLS/resource-generator`.
- `scripts/s3_notification_example.sh`:
- убрана передача секрета в аргументах процесса;
- для `mc` используется временный `--config-dir` и переменная `MC_HOST_<alias>`.
- `provider/internal/core/client.go`:
- debug log path переведён на `os.TempDir()`;
- добавлен override директории через `NUBES_DEBUG_DIR`.
@@ -0,0 +1,14 @@
# Registry Getting Started URL Fix — 2026-08-31
## Проблема
В примере `required_providers` на странице `30_registry/guides/getting-started` значение `source` содержало старый адрес `registry.kube5s.ru` и вложенные HTML-комментарии `LEGACY`. Из-за этого пример Terraform был синтаксически и семантически неверным.
В этом же файле старый адрес с HTML-комментарием присутствовал в ссылке на пример Postgres.
## Решение
- `source` заменён на `tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes`.
- Ссылка на пример Postgres переведена на `tf-registry.containerk8s.services.ngcloud.ru`.
- Все вставки `LEGACY` и упоминания `registry.kube5s.ru` удалены из страницы.
- Версии профилей повышены: DEV `3.0.7`, TEST `5.0.6`, PROD `2.0.7`.
@@ -0,0 +1,73 @@
# Настройка Terraform для разных стендов
Дата: 2026-08-31
## Матрица стендов
| Стенд | Рабочие каталоги | Provider source | API endpoint | Версия в найденных Terraform-файлах |
|---|---|---|---|---|
| DEV | `DEV_STAND/CRUD`, `DEV_STAND/POSTGRES`, `DEV_STAND/IOT_KAFKA_DEMO`, `DEV_STAND/SHTURVAL_MGMT` | `tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes` | `https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc` | обычно `3.x` |
| TEST | `TEST_STAND/CRUD`, `TEST_STAND/PG`, `TEST_STAND/POSTGRES`, `TEST_STAND/MARIA_DB`, `TEST_STAND/IOT_RMQ_DEMO`, `TEST_STAND/buck0`, `TEST_STAND/kuber` | `tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes` | `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc` | обычно `5.x` |
| PROD | `PROD_STAND/PG1`, `PROD_STAND/POSTGRES`, `PROD_STAND/RABBIT` | `tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes` | `https://lk-api-gateway.ngcloud.ru/api/v1/svc` | обычно `2.x` |
Provider выбирается в `terraform { required_providers { nubes { ... } } }` конкретного рабочего каталога. API endpoint задаётся в блоке `provider "nubes"`.
## Что настраивать
1. Перейти в конкретный каталог конфигурации, например `TEST_STAND/PG`.
2. Создать локальный файл `terraform.tfvars` по шаблону `terraform.tfvars.example`, если он есть.
3. Заполнить только переменные, объявленные в `main.tf`/`variables.tf`:
- `api_token` — токен того же стенда;
- `realm` — Kubernetes-платформа/кластер;
- `s3_uid` или `s3_user_uid` — UUID S3 для backup или ресурса bucket;
- `s3_name` — имя S3, если это предусмотрено конфигурацией;
- дополнительные `org_uid`, `vdc_uid`, `edge_uid`, `sizing_policy` — только для соответствующих ресурсов.
4. Проверить имена ресурсов и параметры в остальных `.tf`-файлах: `resource_name`, домены, `git_revision`, CPU, memory, replicas, disk, PostgreSQL version, backup schedule и `adopt_existing_on_create`.
5. Выполнить Terraform из этого же каталога:
```bash
terraform init
terraform plan
terraform apply
```
Для CRUD-конфигураций с PostgreSQL сначала требуется первый `terraform apply` для базы, пользователя и БД, затем второй `terraform apply` для приложений. Это прямо указано в `TEST_STAND/CRUD/README.md`.
## Передача токена
Токен не следует хранить в репозитории. Допустимые варианты:
```bash
export TF_VAR_api_token="..."
terraform plan
```
или локальный `terraform.tfvars`, исключённый из публикации. Не использовать PROD-токен в DEV/TEST и не использовать TEST-токен в PROD.
## State и backend
В проверенных стендах нет блока `backend` и отдельных backend-конфигураций. Если backend не добавлен локально, Terraform использует локальный state в рабочем каталоге (`terraform.tfstate`). Нельзя запускать два разных стенда с одним state; для общего или удалённого state нужен отдельный backend с уникальным bucket/key для каждого стенда.
## Профили сборки provider
`TOOLS/config/{dev,test,prod}/profile.env` используется скриптами сборки и публикации provider, а не Terraform-манифестами стендов:
| Профиль | API | Token file | Namespace | Версия профиля |
|---|---|---|---|---|
| `dev` | dev Gateway | `secrets/dev.token` | `nubes-dev` | `3.0.7` |
| `test` | test Gateway | `secrets/test.token` | `nubes-test` | `5.0.6` |
| `prod` | production Gateway | `secrets/prod.token` | `nubes` | `2.0.7` |
Для сборки использовать профильный pipeline из `HOWTO-UPLOAD.md`, а не смешивать профиль одного стенда с Terraform-конфигурацией другого.
## Найденные расхождения и риски
- `docs/ops/STANDS.md` содержит устаревшие `deck-api-*`, старые пути `devops/profiles` и версии, не совпадающие с `TOOLS/config/*/profile.env` и частью Terraform-файлов.
- Версии provider неоднородны даже внутри одного стенда: перед запуском нужно сверять `required_providers` конкретного каталога с опубликованной версией.
- В `PROD_STAND/PG1/terraform.tfvars` обнаружен токен в открытом виде. Его нужно отозвать/заменить в Nubes и удалить из локального файла перед публикацией или передачей репозитория.
- В отдельных PROD-файлах встречаются захардкоженные пароли и адреса внешних сервисов; их следует перенести в переменные/секретное хранилище перед использованием в общем доступе.
- `TEST_STAND/PG/README.md` указывает версии и структуры параметров, которые могут отличаться от текущего `main.tf`; источником истины для запуска считать сам каталог Terraform и lock-файл после `terraform init`.
## Синхронизация на VM
`DEV_STAND/sync.sh` и `TEST_STAND/sync.sh` синхронизируют конфигурацию на VM и исключают `.terraform`, state и lock-файл. Перед синхронизацией проверить целевой стенд и не переносить state между стендами.
@@ -0,0 +1,29 @@
# Анализ полного pipeline документации и публикации
Дата: 2026-09-02
Проверен полный маршрут `tf_provider`:
```text
Nubes API
-> TOOLS/scripts/01_generate_yamls.sh
-> generated/<stand>/resources_yaml/*.yaml
-> TOOLS/scripts/02_generate_resources_and_docs_v2.sh
-> generated/<stand>/go/*.go
-> generated/<stand>/docs/*.md + _nav_fragment.yml
-> TOOLS/scripts/05_generate_docs_llm.py (опционально)
-> TOOLS/scripts/04_build_and_publish_docs.sh
-> .mkdocs.tmp.yml
-> site/
-> S3 terraform-registry/docs/<namespace>/<name>/<version>/
```
Параллельно релиз провайдера идёт через `03_build_and_upload_provider.sh` и `build-provider.sh`: временная копия provider собирается под linux/windows/darwin, подписывается GPG и загружается в `nubes-terraform-registry/<host>/<namespace>/<name>/<version>/`.
Ключевые реализации: `TOOLS/yaml-generator/main.go`, `TOOLS/resource-generator/main.go`, `TOOLS/docs-generator/main.go`, их `internal/**`, `mkdocs.yml`, профильные конфиги `TOOLS/config/<stand>/*`, `.github/workflows/publish-docs.yml` и серверные файлы `/home/naeel/TF/tf_registry/server/{main.go,handlers.go,router_versions.go,proxy.go}`.
Обнаружен фактический разрыв: `TOOLS/scripts/04_build_and_publish_docs.sh` и CI вызывают `./scripts/publish-docs.sh`, но такого файла в `tf_provider/scripts/` нет. Справочная рабочая копия находится в `DOCS_PIPELINE/publish-docs.sh`. Поэтому генерация `site/` возможна, а штатная финальная загрузка из текущего репозитория завершается ошибкой отсутствующего файла.
Подробный пользовательский отчёт сохранён в:
`/home/naeel/TF/TMP/tf_provider_full_docs_pipeline_2026-09-02.md`
@@ -0,0 +1,17 @@
# Fix cross-stand links publication
## Cause
The source change was present in `TOOLS/docs-generator/internal/writers/writers.go`, but `TOOLS/bin/docs-generator` was an older compiled binary. TEST generation therefore continued to produce an index without the links. The build validator also incorrectly treated intentional links to other documentation roots as contamination.
## Fix and verification
- Rebuilt `TOOLS/bin/docs-generator` from the current Go source.
- Updated the validator to allow links to the DEV, TEST, and PROD documentation roots while still rejecting foreign API, dashboard, and provider values.
- Regenerated and built DEV, TEST, and PROD sequentially.
- Published one `index.html` to each active VM mirror and verified the `Другие стенды` block remotely:
- `/var/www/tf-docs/nubes-dev/index.html`
- `/var/www/tf-docs/nubes-test/index.html`
- `/var/www/tf-docs/nubes/index.html`
The S3 mirror still reports `unexpected EOF`; direct VM transfer was used for the verified publication.
@@ -0,0 +1,15 @@
# Cross-stand links on documentation index pages
## Change
The generated resource index now includes a short "Other environments" section with links to the DEV, TEST, and PROD documentation home pages. The links are added in `TOOLS/docs-generator/internal/writers/writers.go`, the actual source of `generated/<stand>/docs/index.md`.
## Publication
All three profiles were regenerated and built sequentially. Only the resulting `index.html` was transferred to the corresponding active VM mirror:
- `/var/www/tf-docs/nubes-dev/index.html`
- `/var/www/tf-docs/nubes-test/index.html`
- `/var/www/tf-docs/nubes/index.html`
Each remote file was checked for the three cross-stand links. The regular S3 mirror continued to report `unexpected EOF`, so direct VM transfer was used again.
@@ -0,0 +1,11 @@
# Current stand in documentation index
The generated resource index now shows the current environment explicitly:
- `Текущий стенд: DEV`
- `Текущий стенд: TEST`
- `Текущий стенд: PROD`
Each index lists only the two other environments with short usage comments. The namespace is passed explicitly to `docs-generator`, so the label is generated from the selected profile rather than inferred in the HTML build.
DEV, TEST, and PROD were regenerated and their individual `index.html` files were published and verified on the VM. The S3 mirror still reports `unexpected EOF`; direct VM transfer was used.
@@ -0,0 +1,85 @@
# Баг Dev-генератора: рассинхрон nested-параметра
**Дата:** 2026-09-03
**Статус:** план решения, изменения не выполнены
## Симптом
Сборка Dev-провайдера падает на сгенерированном `95_nodejs_resource.go`:
```text
plan.JsonEnv.IsNull undefined
plan.JsonEnv.IsUnknown undefined
plan.JsonEnv.ValueString undefined
```
## Причина
В Dev API один и тот же параметр `jsonEnv` описан по-разному:
- в `create` — `map` с `sub_params` (`DB_PASS`), то есть nested-параметр;
- в `modify` — `map` без `sub_params`, то есть параметр выглядит плоским.
Генератор объединяет параметры через `params.Merge`. Поэтому в канонической
`SchemaParams` `jsonEnv` становится nested и модель содержит
`*NodejsJsonEnvModel`.
Однако `params.AlignParamTypes` переносит вложенные параметры только когда у
параметра операции уже установлен `HasSubParams`. У `modify.jsonEnv` этот флаг
ложный, поэтому `ModifyParams` сохраняет scalar-представление.
Шаблон `Update` видит `modify.jsonEnv` как scalar и генерирует вызовы
`IsNull()`, `IsUnknown()` и `ValueString()`. В сгенерированной модели это
указатель на nested-структуру, поэтому Go-код не компилируется.
## Универсальное решение
Генератор не должен содержать условий для Dev, Test, Prod или конкретного
сервиса. Нужна единая нормализация всех operation params относительно общей
канонической схемы:
```text
schemaParams = Merge(createParams, modifyParams, deleteParams)
createParams = NormalizeAgainstSchema(createParams, schemaParams)
modifyParams = NormalizeAgainstSchema(modifyParams, schemaParams)
deleteParams = NormalizeAgainstSchema(deleteParams, schemaParams)
```
Нормализация должна рекурсивно переносить из канонической схемы структурные
свойства:
- `Type`;
- `HasSubParams`;
- `SubParams` и их типы.
Собственные свойства конкретной операции должны сохраняться: `ID`,
`Required`, `Default`, описания и остальные operation-specific поля.
После нормализации `SchemaParams.jsonEnv` и `ModifyParams.jsonEnv` будут иметь
одинаковую nested-структуру, а шаблон сгенерирует nested-обработку вместо
scalar-методов.
## Граница ответственности
Расхождение Dev API остаётся дефектом входной схемы, но не должно ломать
универсальный генератор. Исправление только YAML Dev или специальная проверка
`jsonEnv` были бы стендовыми обходами и не решают общий класс проблем.
## Обязательная проверка
Добавить генераторный тест на общий случай:
```text
create: map-fixed/map с sub_params
modify: тот же code без sub_params
ожидание: modify после нормализации — nested
```
Проверка результата: сгенерированный Go-код должен компилироваться, а nested
параметр не должен получать scalar-вызовы в `Update`.
## Текущий статус стендов
- Test `3.0.0` опубликован.
- Prod `1.0.0` опубликован.
- Dev `2.0.0` не опубликован: сборка остановилась на компиляции generated Go.
@@ -0,0 +1,67 @@
# 2026-09-03 — Устранение хардкодов документации и публикация DEV
## Найденная причина
Общие материалы `docs/30_registry/` и `docs/curated/` копировались в каждый `generated/<stand>/docs/`, но подстановка выполнялась только для части `getting-started.md`. Поэтому в DEV попадали TEST-значения:
- TEST provider source;
- `5.0.5`;
- TEST API endpoint;
- `deck-test.ngcloud.ru`.
Дополнительно `02_generate_resources_and_docs_v2.sh` не очищал старые generated-файлы. Ресурс, отсутствующий в текущем `services_list.txt`, мог остаться от предыдущей генерации.
## Изменения
- Общие документы используют placeholders:
- `{{NAMESPACE}}`;
- `{{VERSION}}`;
- `{{PROVIDER_SOURCE}}`;
- `{{NUBES_API_ENDPOINT}}`;
- `{{DASHBOARD_URL}}`.
- `04_build_and_publish_docs.sh` подставляет значения рекурсивно во все скопированные Markdown-файлы.
- Добавлена проверка чужих namespace, API/dashboard host и старого `registry.kube5s.ru` до сборки.
- Профиль стал обязательным; обязательные значения не берутся из PROD fallback.
- `02_generate_resources_and_docs_v2.sh` очищает только собственный `generated/<stand>/docs` перед генерацией.
- `docs-generator` больше не содержит DEV default для API/provider source.
- Базовый `mkdocs.yml` больше не содержит versioned URL.
## Проверки
- `bash -n` для обоих docs scripts — PASS.
- `go test ./...` и `go build ./...` в `TOOLS/docs-generator` — PASS.
- DEV regeneration — PASS.
- DEV MkDocs build — PASS; contamination check — PASS.
- В DEV отсутствуют `5.0.5`, TEST API, `deck-test.ngcloud.ru` и `registry.kube5s.ru`.
- Legacy generated `vc_vm_v2` удалён чистой генерацией, так как отсутствует в актуальном `services_list.txt`.
## Публикация
Локальный рекурсивный S3 mirror завершался `unexpected EOF`, поэтому exit code штатного скрипта нельзя считать достаточным подтверждением загрузки. Проверенный артефакт `site/` был передан на ВМ `5.172.178.213` по SSH и атомарно установлен в:
```text
/var/www/tf-docs/nubes-dev/
```
На ВМ проверены страницы getting-started и curated PostgreSQL:
- namespace `nubes-dev`;
- provider version `2.0.0`;
- DEV API endpoint;
- DEV dashboard URL;
- отсутствие TEST-значений.
Legacy versioned каталоги TEST ранее удалены и после публикации отсутствуют:
```text
/var/www/tf-docs/nubes-test/5.0.5
/var/www/tf-docs/nubes-test/5.0.57
```
Публичный путь документации:
```text
https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/
```
Публичный `curl` завершался timeout на большом HTML; содержимое активного зеркала ВМ проверено напрямую.
@@ -0,0 +1,170 @@
# 2026-09-03 — Проверенный pipeline публикации документации
## Цель
Зафиксировать фактический pipeline публикации заново сгенерированной документации провайдера, чтобы не восстанавливать его заново по догадкам.
## Источник документации
Для стенда `<stand>` используются только сгенерированные страницы:
```text
generated/<stand>/docs/
```
Ручной каталог `docs/` не используется как основной `docs_dir`. Скрипт `04_build_and_publish_docs.sh` перед сборкой копирует в сгенерированный каталог только общие материалы:
```text
docs/30_registry/
docs/curated/
```
После копирования в `30_registry/guides/getting-started.md` подставляются параметры конкретного стенда:
- namespace;
- версия провайдера;
- API endpoint.
## Актуальные скрипты
Генерация Markdown выполняется так:
```text
TOOLS/scripts/01_generate_yamls.sh
-> generated/<stand>/resources_yaml/
TOOLS/scripts/02_generate_resources_and_docs_v2.sh
-> generated/<stand>/docs/
```
Сборка сайта выполняется скриптом:
```text
TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/<stand>
```
Он создаёт временный `.mkdocs.tmp.yml`, задаёт `site_url` с namespace стенда, запускает MkDocs и создаёт:
```text
site/
```
В конце этот скрипт вызывает актуальный:
```text
./scripts/publish-docs.sh site "$REGISTRY_HOST" "$NAMESPACE" "$PROVIDER_NAME" "$VERSION"
```
## Фактическое хранилище документации
Документация хранится не в bucket бинарников провайдера. Используется отдельный bucket:
```text
terraform-registry
```
Публикация выполняется без версии. Для любого стенда целевой S3 prefix:
```text
terraform-registry/docs/<namespace>/nubes/
```
Актуальный `scripts/publish-docs.sh` использует:
```text
mc mirror --overwrite --remove site/ registry/terraform-registry/docs/<namespace>/nubes/
```
Следствие: в URL документации нет версии `2.0.0`, `3.0.0` или `1.0.0`.
## Где выполнять S3 upload
История commit `9e02b69` зафиксировала, что из локальной сети большие рекурсивные операции S3 нестабильны. Поэтому `mc mirror` для документации выполняется на ВМ:
```text
5.172.178.213
```
Проверенный порядок:
```text
1. Собрать site/ локально.
2. Передать site/ на ВМ в ~/tmp-docs-site/.
3. На ВМ выполнить:
mc mirror --overwrite --remove \
~/tmp-docs-site/ \
registry/terraform-registry/docs/<namespace>/nubes/
4. На ВМ обновить локальное зеркало:
mc mirror --overwrite --remove \
registry/terraform-registry/docs/<namespace>/nubes/ \
/var/www/tf-docs/<namespace>/
```
S3 upload и обновление зеркала — два отдельных действия. Одной загрузки в S3 недостаточно, если публичный proxy читает локальное зеркало ВМ.
## Публичная доставка
На ВМ nginx использует корень:
```text
/var/www/tf-docs/
```
Сервис `tf_docs` проксирует публичный домен на ВМ. Для любого стенда итоговый путь:
```text
/var/www/tf-docs/<namespace>/
```
Итоговый URL любого стенда:
```text
https://tf-docs.nodejsk8s.dev.nubes.ru/<namespace>/
```
Например, для DEV `<namespace>` равен `nubes-dev`, но это только значение профиля, а не отдельная логика pipeline.
Путь с версией не используется для любого стенда:
```text
https://tf-docs.nodejsk8s.dev.nubes.ru/<namespace>/<version>/
```
не является корректным URL документации.
## Важное различие с публикацией бинарников
Бинарники Terraform-провайдера публикуются в другом bucket и с версионным prefix:
```text
nubes-terraform-registry/
tf-registry.containerk8s.services.ngcloud.ru/
<namespace>/nubes/<version>/
```
Документация публикуется отдельно:
```text
terraform-registry/docs/<namespace>/nubes/
```
Не смешивать эти два pipeline.
## Legacy, который не использовать
```text
DOCS_PIPELINE/publish-docs.sh
```
Это справочная legacy-копия старого скрипта. Она использует старую схему `mc cp`, старую структуру и версионный путь. Для текущей публикации использовать:
```text
scripts/publish-docs.sh
```
## История изменений, подтверждающая схему
- `dc469c6` — публикация docs без версии, `mc mirror`, `site_url` по стенду.
- `72a8a49` — актуализация README и новый docs host; старый скрипт помечен legacy.
- `9e02b69` — зафиксирована загрузка S3 с ВМ и обновление зеркала `/var/www/tf-docs/`.
- `02b7d7b` — подстановка namespace, версии и API endpoint выполняется после копирования `30_registry` в стендовый generated docs каталог.
+61
View File
@@ -0,0 +1,61 @@
# 2026-09-03 — Чистка реестра + новая нумерация версий + баг dev
## Новая схема нумерации версий (с 2026-09-03)
| Стенд | Namespace | Диапазон | Первая |
|---|---|---|---|
| prod | `nubes` | `1.*.*` | `1.0.0` |
| dev | `nubes-dev` | `2.*.*` | `2.0.0` |
| test | `nubes-test` | `3.*.*` | `3.0.0` |
⛔ Старые схемы (`prod=2.*`, `dev=3.*`, `test=5.*`, `0.0.x`) — ЛЕГАСИ, не использовать.
Обновлено: `VERSIONS.md`, `TOOLS/config/*/profile.env`, `DOCS_PIPELINE/README.md`,
`docs/30_registry/guides/getting-started.md`.
## Чистка реестра
Из S3 (`nubes-terraform-registry`, креды super `1112_terraform`) удалены ВСЕ старые версии:
- `nubes-dev`: 3.0.2–3.0.6
- `nubes`: 2.0.2, 2.0.3, 2.0.5, 2.0.6
- `nubes-test`: 0.0.1, 5.0.1–5.0.5, 5.1.17
После чистки в каждом namespace — 0 объектов. Легаси (5.1.17 и т.д.) нигде не осталось.
## Статус перегенерации (2026-09-03)
- ✅ **test** `3.0.0` — сгенерирован и загружен (`Done. Version 3.0.0 uploaded`).
- ❌ **dev** `2.0.0` — НЕ собирается (пропущен по решению пользователя), см. баг ниже.
- ⏳ **prod** `1.0.0` — в работе.
## Баг dev: nodejs jsonEnv (create vs modify)
Симптом: `03` dev падает на компиляции сгенерированного кода:
```
internal/resources_gen/95_nodejs_resource.go:350-353:
plan.JsonEnv.IsNull / IsUnknown / ValueString undefined
(type *NodejsJsonEnvModel has no field or method ...)
```
Причина: **API dev** для nodejs `jsonEnv`:
- в `create` (op id=58) — `map` **с `sub_params`** (типизированные ключи, напр. DB_PASS) → генератор создаёт вложенную модель `NodejsJsonEnvModel`;
- в `modify` (op id=59) — `map` **без `sub_params`** → генератор для diff генерирует строковое сравнение (`IsNull/ValueString`).
У test/prod `jsonEnv` без sub_params в обоих операциях → строка → собирается.
Корень: `TOOLS/resource-generator/internal/params/params.go`, `AlignParamTypes` —
подмешивает `SubParams` из schema в modify только если `HasSubParams` уже true:
```go
if !p.HasSubParams { continue } // modify-jsonEnv (без sub) пропускается
```
Возможный фикс: наследовать `HasSubParams`/`SubParams` из schema для параметров с тем же
code. ⚠️ Нюанс: diff-шаблон исключает nested-поля из `hasServiceParamChanges` — изменение
nested jsonEnv не будет триггерить modify (нужно продумать отдельно).
**Вывод:** сервисы/структуры API стендов отличаются (dev jsonEnv — nested в create).
Каждый стенд рассматривать независимо. dev отложен до решения по генератору/API.
## Прочее (инфраструктура, этот же день)
- Токены API `secrets/*.token` были отозваны на стороне IAM (401 IAM error при валидном exp) — обновлены 2026-09-03.
- S3-креды `.s3cfg_registry` (docs) не имеют прав на бакет бинарников `nubes-terraform-registry`;
заливка бинарников — subuser `super` аккаунта `1112_terraform` (см. `tf_registry/HISTORY/HOWTO-UPLOAD.md`).
@@ -0,0 +1,20 @@
# TEST and PROD documentation publication
## Result
- TEST documentation was regenerated from `TOOLS/config/test` with version `3.0.0`.
- PROD documentation was regenerated from `TOOLS/config/prod` with version `1.0.0`.
- TEST and PROD builds were executed sequentially because both use the shared local `site/` directory.
- TEST active mirror was replaced on the VM at `/var/www/tf-docs/nubes-test/`.
- PROD active mirror was replaced on the VM at `/var/www/tf-docs/nubes/`.
## Verification
- TEST active mirror contains `714` files and its `index.html` is present.
- PROD active mirror contains `344` files and its `index.html` is present.
- TEST HTML contains the TEST dashboard/API/provider values.
- PROD HTML contains the PROD dashboard/API/provider values.
## Infrastructure note
The S3 mirror command reported `unexpected EOF` while listing the registry. Its exit status was not treated as proof of publication. Each generated site was transferred directly to the VM, validated there, and atomically installed into its corresponding active mirror.
@@ -0,0 +1,219 @@
# 2026-09-21 — FullPipe (vDC + Edge): серия фиксов генератора и провайдера
## Контекст
Поднимался полный стенд `DEV_STAND/FullPipe` (целевой пайплайн: Организация → vDC → Edge),
заливался провайдер в реестр (`nubes-dev/nubes`). По ходу вылезла цепочка багов —
в генераторе ресурсов, в сгенерированном коде и в docs-генераторе.
Версия на выходе: **2.0.5** (DEV, namespace `nubes-dev`).
---
## Баг 1. Непересобираемый генератор (stale binary) — устранён ранее в этот же день
**Симптом:** `kind: modifier` в YAML не поддерживался; генерация YAML падала.
**Причина:** `02_generate_resources_and_docs_v2.sh` пересобирал `resource-generator`
только по `mtime`. Лежавший в `TOOLS/resource-generator/bin/resource-generator`
устаревший бинарь затенял исходники.
**Фикс:**
- генераторы (`resource-generator`, `docs-generator`) пересобираются **всегда** из исходников;
- устаревший бинарник удалён; `TOOLS/resource-generator/bin/` добавлен в `.gitignore`;
- обновлены `README.md`, `TOOLS/README.md`.
- Коммит: `7ecd2aa Fix generator rebuild and release pipeline`.
---
## Баг 2. `declared and not used: resolvedKafkaUid` — сборка падала
**Симптомы (сборка из сгенерированного кода):**
```
internal/resources_gen/119_akhq_resource.go:246:2: declared and not used: resolvedKafkaUid
internal/resources_gen/111_dnsrecord_resource.go:248:2: declared and not used: resolvedZoneUid
internal/resources_gen/21_vc_vdc_resource.go:244:2: declared and not used: resolvedOrganizationUid
... (и ещё по всем ресурсам с refSvc в create)
```
**Причина:** в шаблоне `TOOLS/resource-generator/internal/templates/instance.go`:
- блок объявления резолва шёл по `{{range .SchemaParams}}` — т.е. объявлял `resolvedX`
для **всех** refSvc-полей;
- а мапа `params` в `Create` НЕ содержала refSvc-условия и писала сырое `data.X`.
Итог: `resolvedX` объявлен, но нигде не использован → ошибка компиляции.
**Фикс (шаблон `instance.go`, `subresource.go`):**
- циклы резолва переведены на `{{range .CreateParams}}` / `{{range .ModifyParams}}`;
- в мапу `params` в `Create` добавлено refSvc-условие:
```
{{.ID}}: resolved{{ToCamel .Code}}, // в API уходит UUID
{{else}} data.X // сырое значение
```
- Коммит: `2286d34`.
---
## Баг 3. `terraform destroy` падал: «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)»
**Симптом:**
```
terraform destroy
nubes_vc_vdc.vdc: Refreshing state... [id=...]
╷ Error: РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)
```
**Причина:** `ModifyPlan` сгенерированного ресурса на **destroy-плане** запускал
create-time проверку существования/adopt (`PlanExistingResourceDiagnostics...` →
`FindInstanceByDisplayName`). Гварды `config == nil` и
`State.Raw.IsNull() && Plan.Raw.IsNull()` destroy не отсекали (config ненулевой —
блок ресурса ещё в `.tf`; а в destroy-плане state есть, plan = null).
Debug-подтверждение: `/tmp/nubes_find_debug.log` →
`PlanExistingResourceDiagnostics entered: serviceId=21 name="fullpipe-vdc" adopt=false`.
**Обходной путь (временный):** `adopt_existing_on_create=true` — но это «телега впереди
лошади»: destroy не должен зависеть от adopt.
**Фикс (шаблон `instance.go`, `ModifyPlan`):** добавить destroy-guard
```
if req.Plan.Raw.IsNull() { return }
```
Теперь destroy-план не запускает create-time проверку и доходит до `Delete`,
который по `suspend_on_destroy=true` отправляет `suspend`.
Логика suspend уже была в `Delete`: `deleteMode := "state_only"` → `"suspend"`.
- Коммит: `2286d34`.
---
## Баг 4. `Provider produced inconsistent result after apply`: `.organization_uid` было `"kontora"`, стало UUID
**Симптом:**
```
.provider produced an unexpected new value: .organization_uid:
was cty.StringVal("kontora"), but now cty.StringVal("ec4d3a6a-...")
```
**Причина:** refSvc-поле резолвилось и **записывалось обратно в state**, из-за чего
state (UUID) не совпадал с plan (user input).
**Фикс (универсальный, все сервисы):**
- резолв идёт только в локальную переменную `resolvedX`; в state остаётся ровно то,
что ввёл пользователь (имя ИЛИ UUID);
- refresh исключает refSvc-поля (`{{if eq .RefSvcId 0}}`) — не перезаписывает ввод;
- `ResolveRefSvcParamValue` принимает имя (→ UUID) и UUID (→ lowercase);
обратный маппинг `ResolveRefSvcParamDisplayName` для refresh.
- Коммит: `2286d34`.
---
## Баг 5. `Provider returned invalid result object after apply`: `vdc_group_uid` остался unknown
**Симптом (создание Edge):**
```
Error: Provider returned invalid result object after apply
After the apply operation, the provider still indicated an unknown value for
nubes_vc_nsxt.edge.vdc_group_uid.
```
**Причина:** в схеме refSvc-поля были `Optional: true, Computed: true` **без дефолта**
(строка шаблона: `{{- else if or .IsJson (gt .RefSvcId 0) }}Computed: true,{{- end }}`).
Если пользователь поле не задавал (например, `vdc_group_uid` при `vdc_type="vdc"`),
Terraform планировал его как **unknown** и требовал от провайдера известное значение.
Провайдер его не вычисляет (по дизайну хранит ввод юзера) → остаётся unknown → ошибка.
`Computed: true` — рудимент **старого** дизайна (когда провайдер писал резолвленный UUID
в state). После перехода на «храним ввод юзера» он стал вредным.
**Фикс (оба шаблона: `instance.go`, `subresource.go`):**
```
- {{- else if or .IsJson (gt .RefSvcId 0) }}Computed: true,{{- end }}
+ {{- else if .IsJson }}Computed: true,{{- end }}
```
Теперь незаданный refSvc = `null` (известное значение). `IsJson` оставлен Computed
намеренно (нужно для нормализации JSON из API).
Проверено: в сгенерированном `22_vc_nsxt_resource.go` →
`"vdc_group_uid": schema.StringAttribute{Optional: true, ...}` (без `Computed`).
- Коммит: `bffe3d9`.
---
## Баг 6. docs-generator: вложенный `map-fixed` рендерится как блок (НЕ исправлено → TODO)
Пример в сгенерированной доке (`generated/dev/docs/vc_nsxt_example.md`) рисует
`routed_net_configuration` **блоком**, но схема — `SingleNestedAttribute`, значит нужен
аргумент `= { ... }`. Копирование примера → `terraform validate` падает:
`Unsupported block type`.
Виноват `TOOLS/docs-generator/internal/writers/writers.go` → `formatParamOrBlock`
(~стр. 715). Подробности — `docs/TODO/docs_generator_nested_attr_syntax.md`.
Коммит: `92e04da`.
---
## Баг 7. FullPipe: дефолт `vdc_storage_config = "fast"`
**Симптом:** дефолт в `variables.tf` — `[{"name":"fast","size":200}]`.
Имя политики берётся из ресурсного пула (`getKeyListFromStruct(...providerVdcs[...].storage)`),
и `fast` в окружении не существует.
**История (по git):** `fast` появился в первом коммите стенда `7ff98f8` — причём их было
**два**: `vdc_provider_vdc = "fast-2.8"` и `vdc_storage_config = "fast"`. Коммит
`7d44697` («Fix FullPipe VDC example placeholders») поправил только `provider_vdc`
(`"fast-2.8"` → `null`), а `storage_config` не тронул. Так что «опять fast» — это
незакрытый второй хвост, а не откат.
**Фикс:** дефолт → `[{"name":"SATA","size":"200"}]` (совпадает с рабочим `terraform.tfvars`).
Коммит: `d608fba`.
---
## Добавлено в стенд FullPipe
- `DEV_STAND/FullPipe/edge.tf` — ресурс `nubes_vc_nsxt.edge` (create),
`vdc_uid = nubes_vc_vdc.vdc.id` (Edge создаётся после vDC),
`routed_net_configuration = { ... }` (аргумент, не блок — см. Баг 6).
- переменные `nsxt_*` в `variables.tf`, outputs `nsxt_*` в `outputs.tf`,
пример в `terraform.tfvars.example`.
- `versions.tf` → провайдер `2.0.4` (затем `2.0.5`).
- Коммит: `d608fba`.
---
## Изменённые файлы (генератор)
| Файл | Что |
|------|-----|
| `TOOLS/resource-generator/internal/templates/instance.go` | destroy-guard в `ModifyPlan`; резолв refSvc по `.CreateParams`; refSvc-условие в мапе `params`; refSvc без `Computed` |
| `TOOLS/resource-generator/internal/templates/subresource.go` | резолв по `.CreateParams`/`.ModifyParams`; refSvc без `Computed` |
| `TOOLS/scripts/02_generate_resources_and_docs_v2.sh` | детерминированная пересборка генераторов |
| `.gitignore`, `README.md`, `TOOLS/README.md` | игнор бинарника, доки |
## Версии
| Стенд | Namespace | Версия |
|---|---|---|
| DEV | `nubes-dev` | `2.0.5` |
## Коммиты сессии (master)
```
bffe3d9 fix(generator): refSvc-поля без Computed (unset = null, а не unknown)
92e04da docs(TODO): баг docs-generator - вложенный map-fixed как блок вместо = {}
d608fba stand(FullPipe): vc_nsxt (edge.tf), storage_config fast->SATA, provider 2.0.4
1401003 release(dev): 2.0.4
2286d34 fix(generator): destroy-guard в ModifyPlan + универсальный refSvc (имя или UUID)
7ecd2aa Fix generator rebuild and release pipeline
```
## Открытые вопросы
- [ ] docs-generator: `map-fixed` → `= { ... }`, `array-map-fixed` → JSON/jsonencode
(см. `docs/TODO/docs_generator_nested_attr_syntax.md`).
- [ ] Проверить `IsJson`-поля без дефолта: тот же класс unknown-after-apply? (не воспроизводилось).
- [ ] `fast` в тест-фикстуре `provider/internal/core/client_test.go:287` и спек-доке
`docs/60_strategy/...:158` — не трогали.
@@ -0,0 +1,62 @@
# 2026-09-24 — Штурвал dev-00: диагностика, adopt и дизайн «freeze on destroy»
Краткая запись по дню. Разбор с источниками (файл:строка, ответы API, логи) —
`NOTES/30_analysis/SHTURVAL_DEV00_DIAG_AND_FREEZE_DESIGN_2026-09-24.md`,
резюме для продолжения — `NOTES/40_chat_summaries/CHAT_RESUME_2026-09-24_shturval_freeze.md`.
## Изменения в репозитории
| Что | Файл | Коммит |
|---|---|---|
| `adopt_existing_on_create = true` для кластера Штурвала (иначе apply падал на существующем suspended-инстансе) | `DEV_STAND/FullPipe/shturval.tf` | `57abb7b` |
| Документация сессии (диагностика + дизайн freeze) | `NOTES/30_analysis/…`, `NOTES/40_chat_summaries/…` | `3df93ad` |
| Универсальный третий режим destroy `keep_on_destroy` (`state_only`) для всех instance-ресурсов + предупреждения в `Delete` | `TOOLS/resource-generator/{types.go,loader.go,templates/instance.go}` | `22c6c83` |
| Режим «заморозки» в конфиге стенда: `keep_on_destroy=true` (эдж/SNAT/квота IP), adopt для эджа, явный `suspend_on_destroy` у кластера | `DEV_STAND/FullPipe/{edge.tf,modifiers.tf,shturval.tf}` | `40aef87` |
| Релиз dev-провайдера `2.0.22` (три платформы + SHA256SUMS/подпись, залито в реестр) | `VERSIONS.md` | `c29df21` |
## Баг после заморозки: регистр UUID внутри JSON (исправлен)
- Первый `apply` после freeze упал: `required params mismatch … startupConfiguration` — `nsxtUid` в плане
(`2c37fed1-…`, lowercase из пересозданного эджа) против `2C37FED1-…` (UPPERCASE) в живом инстансе.
- Причина: регистр UUID нормализовался в 5 местах (отправка в API, одиночные значения, create-only сравнение,
state), но **внутри JSON** — нет; adopt приостановленного инстанса сравнивает параметр целиком как JSON.
- Проведён аудит (8 мест, таблица в `NOTES/30_analysis/SHTURVAL_DEV00_DIAG_AND_FREEZE_DESIGN_2026-09-24.md` §5.3).
- Фикс: `jsonutil.LowercaseUUIDsInText` + нормализация строк внутри JSON (закрывает adopt-suspended, modifier-compare,
state_refresh, диагностику), UUID-подстроки в `JsonNormalize()`; тесты в `jsonutil` и `resources_core`.
- Открыто: ref-параметр внутри JSON не валидируется при adopt; регистр ключей в `lookupLiveParam`.
## Проверка цикла на живом стенде
- `terraform destroy` (провайдер `2.0.22`): `0 added, 0 changed, 5 destroyed`, ошибок нет.
Кластер и vDC ушли в `suspend`, эдж остался `running` с `ipSpaceName=internet-ipv4-v1`, квота IP — `count=3`,
state пуст. Предупреждения: «заморожен, а не удалён» ×2 (кластер, vDC), «оставлен как есть» (эдж),
«Аллокация IP не снималась» (квота), «SNAT не выключался».
- Обратный ход (`apply` → adopt + `resume`) — следующий шаг, запускает пользователь.
Бэкап перед правкой: `TMP/backup_2026-09-24/shturval.tf.before-adopt`.
## Итоги диагностики кластера `shturval-dev-00`
- Кластер здоров: 2 ноды Ready (k8s v1.35.1, платформа 2.14.0), `shturvalserviceconfigs` 41/41 `ready`,
`nodeconfigitems` 4/4, endpoints есть у всех 35 сервисов.
- Единственный «мусор» — 4 подвисших пода `kube-system/shturval-init-job` (3 Error + 1 Unknown) при
`Complete 1/1` у Job. Причина: webhook-и Штурвала недоступны, пока Cilium не поднял сеть
(`connect: operation not permitted`). Самоочистка по `ttlSecondsAfterFinished: 86400` (~25.09 14:31 UTC).
- Счётчики ЛК расшифрованы: `Pods` = готовые/всего (без Completed), «Системные сервисы» = число сервисов в режиме
`auto` (17/24 во время установки → 24/24), «Ingress» — домен-шаблон, «Конфигурация узлов» — NodeConfigItems.
## Итоги разбора destroy
- `nubes_vc_org_ip_allocation` при `keep_on_destroy = false` отправляет `count=0` и падает, если квота занята
(2 адреса держит кластер: `.146` API, `.148` ingress; `suspend` их не освобождает).
- Упавший destroy оставляет «рваное» состояние: SNAT снят, edge/vDC/квота — нет.
- `adopt_existing_on_create = true` решает восстановление: apply усыновил инстанс `94627ff4-…` и сам сделал
`resume`; SNAT восстановлен (`internet-ipv4-v1`). Проверено на живом стенде.
## Принятое направление (дизайн)
Три режима destroy в одной общей логике: `delete` (дефолт), `suspend` (где сервис умеет),
`keep` → `state_only` (эдж, SNAT, квота IP). Реализация — через генератор
(`TOOLS/resource-generator`), без ручных правок `resources_gen/`. Дефолты провайдера остаются разрушающими,
freeze включается явно в `.tf` стенда; в `Delete` обязательны предупреждения («заморожено», «оставлено как есть»).
Полный teardown — только явный opt-out и в порядке: кластер → `count=0` → SNAT → эдж → vDC.
@@ -0,0 +1,43 @@
# 2026-09-25 — Штурвал в примере `fullpipe_chain` + страница документации
## Что сделано
Примеры (`tf_examples`, отдельный репозиторий `https://gitea.services.ngcloud.ru/Nail/tf_examples.git`)
и страница документации приведены к рабочей конфигурации стенда `DEV_STAND/FullPipe`
(аккаунт `tazet@narod.ru`) — теперь цепочка полная: **vDC → Edge → внешние IP → SNAT → Штурвал**.
| Файл | Изменение |
|---|---|
| `tf_examples/fullpipe_chain/shturval.tf` | **новый**: все настройки Штурвала в одном файле (переменные + `locals` + ресурс `nubes_k8s_sthutrval_cluster`), как в рабочем стенде |
| `tf_examples/fullpipe_chain/versions.tf` | провайдер `2.0.21` → `2.0.23` (последняя dev) |
| `tf_examples/fullpipe_chain/edge.tf` | `keep_on_destroy = true`, `adopt_existing_on_create = true` |
| `tf_examples/fullpipe_chain/modifiers.tf` | `keep_on_destroy = true` у квоты IP и SNAT (было `false`) |
| `tf_examples/fullpipe_chain/outputs.tf` | выводы Штурвала: `shturval_id`, `shturval_name`, `shturval_state_params` |
| `tf_examples/fullpipe_chain/terraform.tfvars.example` | блок параметров Штурвала (закомментированные значения = рабочие default) |
| `tf_examples/fullpipe_chain/README.md`, `tf_examples/README.md` | цепочка со Штурвалом, 5 ресурсов, требования, таблица «заморозки», состав файлов |
| `docs/curated/pipeline/vdc_edge_ip_snat.md` | переписан: требования, чек-лист услуги 150 (ALB + AVI ≥ 3, IP ≥ 3), проверка результата (адреса API/Ingress), «заморозка» при destroy, полное удаление |
| `mkdocs.yml` | заголовок в nav: «Пайплайн vDC → Edge → IP → SNAT → Штурвал» |
## Проверки
- `terraform init` + `terraform validate` + `terraform fmt -check` на копии примера в `/tmp` — без ошибок.
- `terraform plan` (копия в `/tmp`, организация `kontra`, токен `secrets/dev.token`) — `5 to add, 0 change, 0 destroy`, ошибок нет.
- Копия для проверки делалась в `/tmp`, **не** в `tf_examples/`: там нет `.gitignore` для `.terraform/`, и служебные файлы уехали бы в публичный репозиторий.
## Факты и правила, подтверждённые по ходу
- Все настройки Штурвала держим **в одном файле** `shturval.tf` (переменные + ресурс): чтобы выключить Штурвал — удалить файл.
- Порядок из чек-листа услуги 150: организация (вручную в ЛК) → vDC → Edge (ALB, AVI VS ≥ 3) →
внешние IP (≥ 3) → SNAT → кластер. Минимум ноды: 1 + 1 по 4 vCPU / 8 ГБ / 50 ГБ.
- `worker_configuration` — JSON-строка с **camelCase**-ключами (`groupName`…): snake_case валит платформу
(«Cannot invoke method size.split() on null object»).
- «Заморозка»: `keep_on_destroy` важнее `suspend_on_destroy`; у Edge операции `suspend` нет вообще.
- Публикация доков: `TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/dev <версия>`
(версию надо передавать аргументом — в `profile.env` она отстаёт);
сборка локальным mkdocs (docker на этой машине недоступен).
## Ошибка в работе (зафиксировано)
При первой проверке стенда я вывел в терминал содержимое `DEV_STAND/FullPipe/terraform.tfvars` —
файл содержит живой `api_token`. В git файл не попадает (`*.tfvars` в `.gitignore`), но токен оказался
в логе вывода. Правило: секреты из `.tfvars` не печатать, сравнивать по хешу/маскировать.
@@ -0,0 +1,252 @@
> ⛔ **ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО (пометка 2026-09-24). ТАК ДЕЛАТЬ НЕЛЬЗЯ.**
> Архитектура отменённого захода (`kind: modifier` в YAML + реестр в генераторе). История.
> Актуально: `NOTES/40_chat_summaries/CHAT_RESUME_IAC_2026-09-24.md`.
# Opus: архитектура модификаторов (project, полный) — 2026-09-22
Источник: ответ Opus на `prompt_for_opus_modifier_architecture_full.md`.
## Ключевая модель
Модификатор — **декларативная проекция подмножества полей родителя**, а не «действие».
Отсюда:
- один **reconcile** (Create ≡ Update), не два разных пути;
- payload всегда **полный по своим полям** (не дельта);
- источник истины — родитель; модификатор в state хранит только read-back.
---
## 1. Сравнить и применить — полный payload, не дельта
Дельта запрещена: бэкенд трактует отсутствующий параметр как reset-to-default (класс A).
Reconcile:
1. взять все `SchemaParams`;
2. заданные пользователем → значение из плана;
3. незаданные → live → default (уже в `operation_run_bycode.go`);
4. drift в Read — сравнение модели с `state_params` родителя (`state_refresh.go`).
## 2. Досылка незаданных (заливы A и B) — канон
`CompactParams` в шаблоне + досылка в клиенте — два конца одного бага.
Правило по приоритету (уже в `operation_run_bycode.go:98-118`):
| Ситуация | Что слать |
|---|---|
| задан пользователем | значение из плана |
| не задан, есть live ParamValue | live |
| не задан, нет live, есть DefaultValue | дефолт |
| не задан, ничего нет | **пропустить** (не синтезировать) |
**Дыра:** `CompactParams` в `modifier.go:92` выкидывает пустые ДО клиента (теряется
«задал пусто» vs «не задал»). → Убрать `CompactParams` из шаблона модификатора,
передавать map напрямую. Единственная точка решения — клиент. `CompactParams`
оставить только для instance-ресурсов.
## 3. Delete / rollback
No-op Delete = скрытый drift (класс D). Пока обратный payload не подтверждён —
допустимы 3 стратегии через флаг YAML `delete_strategy`:
1. `noop_warn` — удалить из state + `AddWarning` (дефолт для необратимых: `ip_space`);
2. `inverse` — если есть «выключающие» значения в modify (напр. `needEnableAVI:false`);
3. `error` — запретить destroy (`AddError`), если откат критичен.
Обратный payload — та же modify с выключающими значениями. Для `ip_space` его нет → только `noop_warn`.
## 4. Idempotency + ID
- ID = **идентичность** (родитель + имя модификатора) = `instanceUID:modifierName`.
Это правильно и не должен меняться per-apply. opUid в ID **не класть** (иначе replace).
opUid — только в лог/приватный state.
- **Двойная аллокация (класс E)** защищается не ID, а **идемпотентностью modify**:
pre-check «desired == current» → пропустить run. Для `ip_space` перед modify читать
`state_params`; если целевое достигнуто — skip.
## 5. Связь с родителем
- `<service>_id` — ссылка на родителя (Required, уже так). `depends_on` не нужен —
пользователь передаёт UUID.
- Borrow state не нужен: Read тянет `state_params` родителя по UUID.
- Родителя нет (`ShouldRemoveFromState`) → модификатор удаляется из state (уже есть).
## 6. Create vs Update
Единый `reconcile(ctx, plan)`; Create и Update вызывают его (устраняет дубль веток).
## 7. Полный перечень кейсов (13 шт)
| # | Кейс | Поведение |
|---|---|---|
| 1 | create родителя → create модификатора | reconcile, полный payload |
| 2 | изменение одного поля | полный payload, соседние не сбрасываются (A) |
| 3 | partial params | досылка live→default→skip (B) |
| 4 | `integer > 0` без значения/дефолта | пропустить (не слать `"0"`) |
| 5 | `is_modifiable:true` (`needEnableAVI`) | не CreateOnly, менять без replace (C) |
| 6 | destroy модификатора | по `delete_strategy` (D) |
| 7 | replace/taint | reconcile + idempotency pre-check (E) |
| 8 | повторный apply без изменений | desired==current → skip |
| 9 | родитель удалён | remove из state |
| 10 | API не вернул код в state_params | unknown→null (уже) |
| 11 | два модификатора разных типов | разные ID |
| 12 | operation in progress | waitForInstanceIdle (уже) |
| 13 | drift на платформе | Read → план показывает изменение |
## Сводка мест правки
| Место | Правка |
|---|---|
| `modifier.go:92` | убрать `CompactParams` → прямой map (п.2) |
| `modifier.go:77` | единый `reconcile()` (п.6) |
| `modifier.go:156` | `delete_strategy` (п.3) |
| `modifier.go:100` | ID = `instanceUID:modifierName` (п.4) |
| `RunOperationByCodeWithTimeout` / reconcile | idempotency pre-check (п.4,7) |
| `params.go:116` | учитывать modifier-канал/`is_modifiable` (класс C, кейс 5) |
| loader модификаторов | YAML-поля `delete_strategy`, `idempotency` |
| `operation_run_bycode.go` | оставить как есть (guard корректен) |
## Открытые вопросы к Opus (не закрыты ответом)
1. **Где брать значения для `inverse`-стратегии Delete?** Для `network` «выключающие»
значения — это хардкод per-modifier? Как их задать декларативно в YAML, без хардкода
в генераторе?
2. **Формат YAML новых полей.** Точная схема `delete_strategy` и `idempotency`:
enum-значения, дефолты, валидация (fail-fast на неизвестных).
3. **Pre-check «desired == current» — где читать current?** Через
`RefreshResourceState`/`state_params` или отдельный GET? Как сериализовать сравнение
для map-fixed/array-map-fixed (порядок ключей)?
4. **Как пометить модификатор «idempotency: check_before_run» на уровне YAML**
(а не хардкодом в коде reconcile)?
5. **Что если желаемое == текущее, но была «частичная» ошибка ранее** — пропускать run
безопасно всегда, или есть исключения?
---
# Ответы Opus №2 (уточнения по 5 вопросам)
## 1. inverse-Delete — только декларативно в YAML, хардкод запрещён
Обратный payload зависит от параметров: `needEnableAVI:false` валиден, а
`virtualServicesCount` (`integer > 0`) обнулить нечем → `0` невозможен.
Значит inverse-значения задаются **явным блоком в YAML**. Если хоть один параметр
не имеет валидного inverse — стратегия `inverse` недопустима (fail-fast в загрузчике).
Для `ip_space` inverse нет вообще → только `noop_warn`.
## 2. Точная схема YAML новых полей
```yaml
operations:
- kind: modifier
modifier: network
action: modify
delete_strategy: noop_warn # enum: noop_warn | inverse | error
idempotency: check_before_run # enum: none | check_before_run
delete_params: # обязателен ТОЛЬКО при delete_strategy: inverse
- code: needEnableAVI
value: "false"
params: [...]
```
Go-контракт (`OperationSpec`):
```go
DeleteStrategy string `yaml:"delete_strategy,omitempty"` // "" → noop_warn
Idempotency string `yaml:"idempotency,omitempty"` // "" → none
DeleteParams []ParamSpec `yaml:"delete_params,omitempty"`
```
Дефолты: `delete_strategy` → `noop_warn`; `idempotency` → `none`.
Fail-fast в `ValidateSpec`: значение вне enum → ошибка; `inverse` с пустым
`delete_params` → ошибка; `delete_params.code` нет в `params` → ошибка; inverse-значение
нарушает constraint параметра → ошибка на этапе генерации.
`GenModifier` получает `DeleteStrategy`, `Idempotency`, `DeleteParams`.
## 3. Откуда читать current + как сравнивать
**Читать из `state_params`, отдельный GET не делать** (это уже источник истины для Read;
второй источник = риск рассогласования).
Сравнение по типу:
| Тип | Как сравнивать |
|---|---|
| bool/int/string | равенство после `normalizeUniversalValueV6` |
| map-fixed | `JSONStringsEquivalent` (игнор порядка ключей) |
| array-map-fixed | deep-equal с сохранением **порядка элементов** (порядок значим) |
Порядок ключей map-fixed — нормализовать (не значим). Порядок элементов
array-map-fixed — НЕ нормализовать (значим).
## 4. idempotency декларативно
Поле `idempotency` на modify-операции в YAML → `ValidateSpec` → `GenModifier.Idempotency`
→ шаблон `modifier.go` в `reconcile()` эмитит pre-check `{{- if eq .Idempotency "check_before_run" }}`.
Для `ip_space` — в YAML; для остальных — дефолт `none`.
## 5. Когда безопасно skip run при desired == current (НЕ всегда)
Три условия безопасного skip:
1. **Инстанс idle** — если pending/in-progress, сначала `waitForInstanceIdle`, потом
перечитать `state_params` (иначе mid-flight аллокация даст ложное «уже равно»).
2. **current из живого state_params, НЕ из TF-state** — после частичной ошибки TF-state
может врать, а state_params отражает реальную платформу.
3. **Сравнение по всем полям, не по одному** — skip только при совпадении ВСЕХ полей.
Итог:
```
idle? нет → wait, re-read
всё-live == всё-desired? да → skip run
иначе → reconcile (полный payload)
```
---
# Ответы Opus №3 (сверка с фактическим кодом, расхождения + сомнения)
## Факт №1: контракт — в lib, поведение — в GenModifier
Подтверждено: `delete_strategy`/`idempotency`/`delete_params` добавляются в
**`lib.OperationSpec`** (`TOOLS/lib/types.go`), resource-generator получает через алиас
(`types.go:19`). Ссылка «types.go:39» была неточной — канон в lib.
Граница:
| Где | Что |
|---|---|
| `lib.OperationSpec` / `lib.ParamSpec` | всё из YAML, видно обоим генераторам |
| `GenModifier` (локально) | производные для шаблона, флаги `Needs*`, готовый inverse-список |
Правило: парсится из YAML → lib; вычисляется загрузчиком для шаблона → GenModifier.
## Факт №2: pre-check — в `core` (вариант A), экспортировать сравнение
`normalizeUniversalValueV6` приватная и требует `universalCfsParam` — в `resources_core`
этих данных нет. Pre-check делать **в `core`**, не в resources_core и не в шаблоне.
Конкретно — экспортированный метод в `core`, вызывается из `operation_run_bycode.go`
сразу после `fetchOperationCfsParams`:
```go
func (c *UniversalClient) modifierDesiredEqualsCurrent(
desired map[string]string, cfsParams []universalCfsParam) bool
```
Сравнение: нормализовать обе стороны через `normalizeUniversalValueV6`; для
map-fixed/array-map-fixed — JSON-эквивалентность. Но `JSONStringsEquivalent` лежит в
`resources_core` → импорт в `core` даст **цикл**. Вынести JSON-эквивалентность в
нейтральный пакет (`core/jsonutil` или в сам `core`) и переиспользовать в обоих местах.
Вариант C (только `JSONStringsEquivalent` без нормализации) — **отклонён** (ложный diff
`true`/`1`).
Управление: `RunInstanceOperationUniversalByCode` получает флаг `idempotent` (из
`GenModifier.Idempotency` → шаблон → параметр вызова); idle-гейт выше pre-check.
## Дополнительные сомнения (ответы)
1. **Idempotency и полный payload НЕ конфликтуют** (разные уровни). Бинарно на весь
модификатор: `ALL == ALL` → skip целиком; любое расхождение → полный payload.
Полудельты нет.
2. **Частичный inverse — допустим и правилен.** `delete_params` покрывает только
обратимые поля; необратимые/constraint просто не входят. Fail-fast смягчить:
ошибка не «inverse обязан покрыть всё», а «код в delete_params обязан существовать
в params и value удовлетворять constraint». Delete при inverse = modify с
delete_params + досылка live остальных (полный payload).
3. **`noop_warn` дефолт — оставить, но критичные — вручную `error`.** Дефолт мягкий
(`noop_warn`, всегда с `AddWarning`), а необратимые (`ip_space`) автор спеки явно
помечает `delete_strategy: error` в YAML. Генератор сам не решает обратимо/необратимо.
@@ -0,0 +1,59 @@
> ⛔ **ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО (пометка 2026-09-24). ТАК ДЕЛАТЬ НЕЛЬЗЯ.**
> Баг отменённого захода (`kind: modifier` в YAML + реестр в генераторе). История.
> Актуально: `NOTES/40_chat_summaries/CHAT_RESUME_IAC_2026-09-24.md`.
# Opus-разбор: modify-модификатор сбрасывает create-поля в дефолт — 2026-09-22
Источник: ответ Opus на `prompt_for_opus_modifier_null_bug.md`.
## Симптом
`nubes_vc_nsxt_network` (modifier vc_nsxt.network, modify 111) после create Edge с
`needEnableAVI=true`, `virtualServicesCount=3` сбрасывал `needEnableAVI` на платформе
обратно в `false`.
## Корень бага (подтверждено cfsParams операций)
Два пути отправки modify ведут себя по-разному:
- **Generic modify** (`UpdateResource` → `RunInstanceOperationUniversalWithDefaults`,
`client.go:493`) — в цикле дозаполнения шлёт **все** незаданные cfsParams их текущим
`ParamValue` (или `DefaultValue`) — безусловно.
- **Модификатор** (`RunOperationByCodeWithTimeout` → `RunInstanceOperationUniversalByCode`,
`client.go:1518`) — в аналогичном цикле стоял guard `if !param.IsRequired { continue }`,
который пропускал опциональные параметры.
`needEnableAVI` — опциональный параметр modify 111 и не входит в `SchemaParams` модификатора
`vc_nsxt.network` (там только SNAT/routedNetConfiguration). Итог:
1. модификатор его не шлёт (не его поле);
2. back-fill его пропускает (`IsRequired == false`);
3. бэкенд видит отсутствующий параметр → трактует как reset-to-default → `false`.
`CompactParams` тут ни при чём для `needEnableAVI` — параметр вообще не был в payload модификатора.
## Ответы Opus
1. **Полный или частичный payload?** Канон — полный: все параметры операции, незаданные
дозаполняются текущим live-значением (`ParamValue`). Бэкенд для modify трактует
пропущенный/null как reset-to-default, поэтому частичный payload обязан затирать create-поля.
2. **Где чинить?** В `RunInstanceOperationUniversalByCode` — убрать `IsRequired`-guard в цикле
дозаполнения (стало: слать ВСЕ незаданные params их live-значением, как в `WithDefaults`).
- НЕ в `CompactParams` (он не видит полный набор cfsParams, только поля модификатора).
- НЕ в шаблоне генератора (шаблон тоже не знает полного набора).
3. **Риск для vc_org.ip_space:** основной live-путь безопасен (досылка идёт **текущим** значением,
не хардкод-дефолтом). На fallback-пути `/instanceOperations/default/{opId}` `ParamValue` пуст —
есть только `DefaultValue`; но тот же риск уже несёт `WithDefaults`, новой регрессии нет.
## Внесённый фикс
`provider/internal/core/client.go` — `RunInstanceOperationUniversalByCode`, цикл дозаполнения:
убраны `if !param.IsRequired { continue }` и `if !param.IsRequired && val == "" { continue }`.
Теперь все незаданные параметры modify досылаются их live-значением (или default).
Коммит: `c420ea0`.
## Примечание
Костыль в `DEV_STAND/FullPipe/edge_network.tf` (явная передача ALB/VS/qos в модификаторе)
после фикса ядра становится избыточным, но не вреден. После пересборки провайдера можно
убрать эти три поля из `edge_network.tf` — досылка теперь происходит автоматически.
@@ -0,0 +1,76 @@
> ⛔ **ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО (пометка 2026-09-24). ТАК ДЕЛАТЬ НЕЛЬЗЯ.**
> Ревью плана отменённого захода (`kind: modifier` в YAML + реестр в генераторе). История.
> Актуально: `NOTES/40_chat_summaries/CHAT_RESUME_IAC_2026-09-24.md`.
# Opus: ревью плана редизайна модификаторов — 2026-09-22
Источник: ответ на `prompt_for_opus_modifier_plan_review.md` (план `PLAN_modifier_redesign.md`).
## 1. Порядок шагов — скрытые зависимости
- Шаг 4 (шаблон) ссылается на API из шагов 6–7 → **сначала 5→6→7, потом 4**.
- Шаг 8 (yaml-generator) должен идти ДО регенерации `dev` и до сборки.
Скорректированный порядок: 1 → 2 → 3 → 5 → 6 → 7 → 4 → 8 → регенерация → 9 → 10.
## 2. Шаг 5 (вынос JSON-эквивалентности)
Путь верен. **Оставить реэкспорт-обёртку `JSONStringsEquivalent` в `json_normalize.go`**,
не заменять вызовы по всему resources_core (иначе диф на инстансы, вопрос 7).
Переносятся самодостаточные 5 функций: `JSONStringsEquivalent`, `normalizeJSONIfPossible`,
`encodeCanonicalJSON`, `writeCanonicalJSON`, `normalizeJSONScalarsToStrings`.
Вариант «готовые строки в core» — отклонить (размазывает нормализацию, не снимает
потребность в JSONStringsEquivalent в core).
## 3. Шаг 6 — сигнатура и сравнение
- Маппинг code→param по **двум** алиасам: `p.Code` И `p.SvcOperationCfsParam` (как в
operation_run_bycode.go:50-58). Один `Code` даст пропуски.
- Имя `modifierDesiredEqualsCurrent` — unexported, вызов внутри core. Слово «экспортированный» убрать.
- bool/int/string — `normalizeUniversalValueV6` + сравнение. map-fixed — `JSONStringsEquivalent`.
- **array-map-fixed — дыра:** `normalizeUniversalValueV6` строит дефолт только для
`map-fixed`/`HasPrefix "map"` (params.go:33); `array-map-fixed` туда не попадает →
сравнивать сырые значения через `jsonutil.JSONStringsEquivalent`, не через normalize.
- desired = только явно заданные коды (до досылки live/default), иначе pre-check всегда «равно».
## 4. Шаг 4.4 Delete=inverse — подводный камень
- Delete не имеет `plan` (только `req.State`). `reconcile(ctx, plan *Model)` не подходит.
→ `reconcile(ctx, model *Model, override map[string]string)`; для inverse override = delete_params.
- `deleteParams` — финальные **wire-строки** (`"false"`, готовый JSON), БЕЗ прогонки через
`ParamFormat`/тип. В реестре `deleteParam{Code, Value}` несёт готовую строку.
## 5. Шаг 8 — расширение реестра
Верно. Держать в `serviceSpecificModifiers` (main.go:34), не отдельным реестром.
Структура `modifierException` корректна. При переходе со `map[string]string` на структуру:
`ModifierName` берётся из структуры (сейчас `modName, ok := serviceSpecificModifiers[name]`
— строка 95).
## 6. Пропущенные кейсы
- **taint/replace + `delete_strategy=error`** — конфликт: replace = Delete→Create, Delete=error
блокирует → пользователь не сможет заменить error-модификатор. Решить явно:
запретить replace у error (документировать) или отличить «чистый destroy» от replace.
- **unknown в pre-check** — при unknown (computed ref) сравнение невозможно; шаг 6 должен
skip-ить pre-check при unknown (иначе пустая строка даст ложный diff/панику).
- **partial apply** — досылка live для незаданных + pre-check; проверить кейс «часть задана, часть live».
## 7. Риск сломать инстансы
Низкий при условиях:
- **НЕ удалять `CompactParams`** (helpers.go:68) — убирается только из modifier-шаблона;
функция нужна инстанс/action.
- **Шаг 7 — новый метод `RunOperationByCodeIdempotent`, НЕ менять сигнатуру**
`RunOperationByCodeWithTimeout`/`RunInstanceOperationUniversalByCode` (зовут инстансы).
- Шаг 1 (поля OperationSpec) аддитивен — безопасно.
## Дополнительно (не в вопросах)
- **Шаг 4.3 (ID=identity) — ломающая миграция state.** Смена формата ID изменит ID уже
задеплоенных модификаторов → Terraform форснёт replace. Нужно: либо сохранить старый
формат ID, либо явный state-migration plan. В плане не отмечено.
- **Шаг 3 — `normalizeDeleteStrategy`/`normalizeIdempotency`** — где живут (в loader.go, рядом
с веткой modifier). Не указано.
- **ValidateSpec** — проверка `delete_params.code ∈ op.Params` по lower-code; сверить поле `Code`.
@@ -0,0 +1,43 @@
> ⛔ **ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО (пометка 2026-09-24). ТАК ДЕЛАТЬ НЕЛЬЗЯ.**
> Код-ревью отменённого захода (`kind: modifier` в YAML + реестр в генераторе). История.
> Актуально: `NOTES/40_chat_summaries/CHAT_RESUME_IAC_2026-09-24.md`.
# Opus-код-ревью: модификаторы (kind: modifier) — 2026-09-22
Источник: ревью по `prompt_for_opus_modifiers_review.md`.
Объекты: `nubes_vc_org_ip_space` (vc_org.ip_space, modify 207) и `nubes_vc_nsxt_network` (vc_nsxt.network, modify 111).
Файлы: шаблон `TOOLS/resource-generator/internal/templates/modifier.go`, loader, `generated/dev/go/19_vc_org_ip_space_modifier.go`, `22_vc_nsxt_network_modifier.go`, `registry.go`, `provider/internal/resources_core/crud.go` (RunOperationByCodeWithTimeout), `provider/internal/core/client.go` (RunInstanceOperationUniversalByCode, RefreshResourceState, ShouldRemoveFromState).
## 1. Жизненный цикл Create/Update/Read/Delete
- **Create ≡ Update**: оба тела идентичны — гонят `modify` с текущими params. Любое изменение любого атрибута = повторный запуск `modify` целиком, не дельта.
- **Идемпотентность на платформе, не в провайдере.** Нет сравнения «до/после», нет проверки, что операция применила именно эти значения (только `validate-cfs` + `run`). Если API-`modify` аккумулирует, а не перезаписывает (особенно `vIPConfigure`) — повтор = дубли/лишний расход квоты.
- **Refresh (Read) частичный и потенциально вредный.** `RefreshResourceState` тянет `state_params` и перезаписывает input-поля:
- если платформа не эхоит код в `state_params` (типично для операционных modify-параметров) — дрейф не детектируется, Read почти no-op → управление «вслепую»;
- если эхоит, но нормализованно (bool как `1/0`, порядок ключей в `routedNetConfiguration`/`map-fixed`) — вечный diff. `JsonNormalize()` стоит только как plan-modifier на Required-строке; ветка `map-fixed` в refresh json-нормализацию не гарантирует.
## 2. Delete = no-op — ожидаемо? Подводные камни
No-op ожидаем (обратного payload нет). Но:
- **destroy убирает ресурс из state, оставляя эффект на платформе** (выделенные IP, включённый AVI/LB). Инфраструктура и state расходятся молча.
- **Самый опасный сценарий — taint/replace или destroy→apply**: Create снова гонит `modify` → повторное выделение внешних IP (`nubes_vc_org_ip_space`). Прямой риск двойного выделения и расхода.
- `BuildActionID(instanceUID, "modify", "ip_space")` — детерминированный константный ID, не привязан к реальному opUid. State не отражает, какая операция и с какими значениями отработала; два модификатора одного типа на одном инстансе получили бы одинаковый ID.
## 3. Риски передачи по коду (code → id) в RunInstanceOperationUniversalByCode
- **Резолв code→id полностью зависит от `GET /instanceOperations/{opUid}?fields=cfsParams`** — того запроса, что даёт 500 на проблемных инстансах. Без fallback модификатор неработоспособен целиком (без словаря `codeToParam` параметры не отправить).
- **Коды захардкожены в сгенерированном коде** (`vIPConfigure`, `needEnableAVI`…). Переименование на платформе ломается в рантайме («код параметра X не найден»), а не на компиляции — молчаливая деградация.
- **Частичный payload = скрытые сайд-эффекты.** `CompactParams` выкидывает пустые Optional. Для `modify` пропуск параметра платформа может трактовать как «сбросить в дефолт» (не задал `needEnableAVI` → LB может выключиться). Семантика PATCH vs PUT не контролируется провайдером.
- opId ищется среди `AvailableOperations`: не то состояние инстанса → жёсткий отказ «операция недоступна». `LockInstance` сериализует операции по инстансу — конкурентность закрыта корректно.
## ТОП-3 критичных
1. **Двойное выделение при replace/destroy→apply** (особенно `ip_space`): no-op Delete + повторный `modify` на Create + отсутствие проверки идемпотентности = риск задвоить внешние IP/квоту. Нужен guard перед `modify` (проверка по `state_params`/наличию ресурса), либо явно документировать запрет replace.
2. **Refresh либо слепой, либо вечный diff.** Для операционных modify-параметров `state_params` обычно их не возвращает → Read ничего не сверяет; там где возвращает — нормализация (bool/JSON `map-fixed`) ломает план. Решить: честный drift-refresh с нормализацией, либо явно пометить поля как не-refreshable.
3. **Жёсткая зависимость от падающего `?fields=cfsParams`.** code→id держится на запросе, который 500-тит на проблемных инстансах — модификатор ложится целиком. Fallback на `/instanceOperations/default/{opId}` — условие работоспособности, а не «приятная опция».
## Мелочи
- Константный `BuildActionID` — ID не привязан к реальной операции.
- Захардкоженные коды ломаются в рантайме, а не на сборке.
@@ -0,0 +1,183 @@
# Ответ Соннета: Анализ UX документации Nubes Terraform Provider
**Дата:** 2026-08-10
**Вопрос:** см. prompt_for_sonnet_docs_ux.md
**Ответчик:** Claude Sonnet
---
## TL;DR
Документация функционально корректна, но навигация сломана для нового пользователя — он не может найти нужный ресурс без знания URL. Три приоритета: восстановить sidebar, сделать главную страницу рабочей, поднять размер шрифта MAN.
---
## 1. Общая оценка
**Хорошо:**
- YAML → Markdown пайплайн — надёжная основа, параметры актуальны
- Cloud snapshot для `state_out_flat` / `vault_secrets` — уникальная ценность, ни у кого нет
- Dual example (minimal + full) — правильный выбор
**Плохо:**
- Боковые панели скрыты → юзер попадает на страницу и не знает как вернуться к другим ресурсам
- `font-size: 0.62rem` для MAN — нечитаемо, создаёт впечатление "broken UI"
- Версия в URL, но не в UI → юзер не уверен смотрит ли он актуальное
- Индексная страница — голая таблица из 43 строк без группировки и фильтрации
---
## 2. Рекомендации по блокам
### A. Навигация
**A1 — Навигация между ресурсами:**
Лучший вариант — вернуть левый sidebar с категориями. 43 ресурса легко разбиваются на группы:
- **Базы данных**: postgres, mysql, redis, mongodb, ...
- **Очереди**: kafka, rabbitmq, activemq, ...
- **Хранилище**: s3, swift, ...
- **K8s**: kubernetes, helm, ...
- **VMware**: vdc, vm, ...
- **Приложения**: lucee, nodejs, flask, ...
- **Сеть/прочее**: остальное
Sidebar с категориями даёт ориентацию за 3 секунды. Поиск mkdocs (`search`) — бесплатный бонус.
**A2 — Вернуть sidebar:**
Да. Убрать из `extra.css` строки:
```css
.md-sidebar--primary { display: none !important; }
```
Правый sidebar (TOC) убрать только для страниц ресурсов — там он бесполезен. Реализуется через meta-tag `hide: [toc]` в frontmatter генерируемых файлов.
**A3 — Быстрый поиск:**
- Включить встроенный поиск mkdocs-material (`search` plugin)
- В `IndexMD()` добавить категории как `## Базы данных`, `## Очереди` — тогда sidebar mkdocs покажет дерево
---
### B. Дизайн страницы ресурса
**B1 — MAN font-size:**
Поднять с `0.62rem` до `0.78rem` — достаточно компактно, но читаемо. Заодно обернуть MAN в `<details>` с заголовком "Справка (MAN)" — DevOps обычно не читает MAN, ему нужны параметры.
**B2 — Структура страницы:**
Текущий порядок (MAN в начале) — неоптимален. Предлагаю:
```
1. Заголовок + Inline nav
2. Краткое описание (1-2 строки из ServiceDisplayName + первый абзац MAN)
3. Minimal example (СРАЗУ — копируй и пробуй)
4. Create params (таблица)
5. Outputs (state_out_flat + vault_secrets)
6. MAN (в <details> collapsed)
```
DevOps хочет пример → понял структуру → посмотрел параметры. MAN читает если застрял.
Это изменение в `buildManualPage()` — перенос `buildExamplePage()` фрагмента вверх. Либо создать новый `buildCombinedLandingPage()`.
**B3 — Версия в UI:**
Добавить в `buildHeader()`:
```
# Resource nubes_postgres · v5.0.5 · Service ID: 90 · PostgreSQL
```
`version` уже передаётся в `ResourceDocs()` — просто прокинуть в `buildHeader()`.
---
### C. Таблицы параметров
**C1 — Колонки таблиц:**
Текущие колонки: `Code | Type | Description | Constraints`. Добавить `Required` и `Default`:
```
| Параметр | Тип | Обязательный | По умолчанию | Описание | Ограничения |
```
`Required` и `Default` уже есть в данных (`SplitParams()` их разделяет), просто не выводятся в единой таблице. Убрать разделение на две таблицы — одна таблица с колонкой Required проще для чтения.
**C2 — Вложенные параметры (map-fixed):**
Текущий вариант (`### clusterConfiguration` → отдельная таблица) — приемлем. Улучшить: добавить ссылку-якорь в основной таблице:
```
| clusterConfiguration | map-fixed | [Развернуть ↓](#clusterconfiguration) | ... |
```
Так юзер понимает что кликнуть. Реализуется в `renderParamTable()` + `renderNestedParams()`.
---
### D. Примеры
**D1 — Страница Example:**
- Поменять местами: Minimal example → Full example (не в `<details>`)
Сейчас Full в раскрывашке — правильно. Но заголовок `Minimal example — only required parameters` на английском среди русского контента — резает глаз. Перевести.
- Добавить комментарии в код: `# Выберите из: 1, 3, 5` для параметров с value_list — LLM уже обогащает, но это должно быть в HCL-примере тоже.
- Outputs usage: сейчас шаблонные строки с `baza`. Показать реальные ключи из cloud snapshot если есть:
```hcl
# PostgreSQL connection string:
# nubes_postgres.baza.state_out_flat["internalConnect.master"]
```
---
### E. Общие рекомендации
**E1 — Чего не хватает:**
1. **Lifecycle warning** — блок про `suspend_on_destroy` сейчас внизу create params мелким шрифтом. Это КРИТИЧНАЯ информация (пользователь может случайно "удалить" БД). Поднять выше, оформить как `!!! danger` admonition.
2. **Связанные ресурсы** — PostgreSQL → пример связки с Lucee/NodeJS уже есть в `buildOutputsPage()`, но только для service_id=90. Обобщить через теги в YAML.
3. **Changelog** — нужен, но это отдельная задача (нужно хранить diff между версиями YAML).
**E2 — Приоритеты:**
Quick wins (высокий эффект, минимум кода):
1. Убрать `display:none` с primary sidebar + включить search
2. MAN font-size 0.62rem → 0.78rem
3. Версия в buildHeader()
4. Добавить категории в IndexMD() + _nav_fragment.yml
---
## 3. Приоритизированный план действий
**Phase 1 — Quick wins (1-2 дня, 1 разработчик)**
1. `extra.css`: убрать `display:none` для primary sidebar
2. `extra.css`: MAN font-size `0.62rem` → `0.78rem`
3. writers.go: прокинуть `version` в `buildHeader()`
4. `IndexMD()`: добавить категорийные заголовки (требует маппинга service → category)
5. `WriteNavFragment()`: группировка по категориям в `_nav_fragment.yml`
**Phase 2 — Структура страницы (3-5 дней)**
6. Новый порядок секций: пример → параметры → MAN в `<details>`
7. Объединить required/defaults в одну таблицу с колонкой Required
8. Lifecycle warning → admonition `danger`
**Phase 3 — Таблицы и примеры (1 неделя)**
9. Якори для вложенных параметров
10. Реальные ключи из cloud snapshot в HCL-примерах
11. Перевести заголовки примеров на русский
**Долгосрок (отдельный эпик)**
12. Changelog между версиями
13. Теги для связанных ресурсов
---
## 4. Конкретные предложения по коду
| Файл | Изменение |
|------|-----------|
| extra.css | Убрать строки `display: none !important` для обоих sidebar; MAN `.man-content` `font-size: 0.78rem` |
| `writers/writers.go`: `buildHeader()` | Добавить `version` параметр, вывести в заголовок |
| `writers/writers.go`: `IndexMD()` | Принять маппинг `categoryMap map[int]string`, выводить заголовки `## Category` |
| `writers/writers.go`: `buildCreateParamsPage()` | Единая таблица Required + Default с колонкой Required (bool); lifecycle → admonition |
| `writers/writers.go`: `buildManualPage()` | MAN обернуть в `<details><summary>Справка (MAN)</summary>` |
| mkdocs.yml | Включить `search` plugin |
---
## 5. Антипаттерны — что НЕ делать
1. **Не делать многоуровневую sidebar по операциям** — `Manual / Create params / Modify params` в sidebar превратит дерево в 43×6=258 пунктов. Только верхний уровень в sidebar, внутри — inline nav.
2. **Не трогать LLM prompt ради структуры** — структура страниц это Go-генератор, не LLM. LLM только обогащает тексты.
3. **Не делать HTML-таблицы** — Markdown-таблицы уже работают; HTML нужен только для сложных случаев (`lifecycle-note` div — допустимо).
4. **Не скрывать пример в `<details>`** — сейчас Full example скрыт, это правильно. Minimal должен быть ОТКРЫТ и первым.
5. **Не добавлять JS-фильтрацию на index** — mkdocs search уже умеет фильтровать; второй поиск создаёт путаницу.
---
**Ключевой вывод:** Самое больное место — отсутствие sidebar и нечитаемый MAN. Эти два изменения (по 3 строки CSS) дадут 80% UX-эффекта. Остальное — итеративно.
@@ -0,0 +1,61 @@
# Ответ Соннета: Как смержить nav (статический + динамический)
**Дата:** 2026-08-10
**Вопрос:** Как смержить статический `nav:` (руководства) и динамический (43 ресурса по категориям) в mkdocs?
**Ответчик:** Claude Sonnet
---
## Нет встроенного `!include` в mkdocs
mkdocs-material не имеет нативного механизма подключения внешних YAML-фрагментов в `nav:`. Плагины (monorepo, awesome-pages) решают другие задачи и здесь не помогут.
---
## Три варианта (по убыванию рекомендованности)
### Вариант 1 — Рекомендую: расширить существующий Python pre-build шаг
В `04_build_and_publish_docs.sh` уже есть Python-блок, который пишет `.mkdocs.tmp.yml`. Добавить туда чтение `_nav_fragment.yml` и инъекцию в `nav:`:
```python
import yaml
nav_fragment_path = Path(docs_dir) / "_nav_fragment.yml" if docs_dir else None
if nav_fragment_path and nav_fragment_path.exists():
fragment = yaml.safe_load(nav_fragment_path.read_text(encoding="utf-8"))
resources_nav = fragment.get("resources_nav", [])
config = yaml.safe_load(text)
for item in config.get("nav", []):
if isinstance(item, dict) and "Ресурсы" in item:
item["Ресурсы"] = resources_nav
break
text = yaml.dump(config, allow_unicode=True, default_flow_style=False, sort_keys=False)
```
**Плюсы:** ноль новых зависимостей, PyYAML уже в окружении, merge в одном месте, статические секции ("Руководства") остаются нетронутыми.
**Предупреждение:** PyYAML при `dump` меняет форматирование (кавычки, отступы) — это нормально для `.mkdocs.tmp.yml`, который никто не читает руками.
### Вариант 2: docs-generator пишет полный mkdocs.yml
Сделать отдельный файл `mkdocs_base.yml` (тема, плагины, CSS, статический nav — без ресурсов), Go-генератор его читает, добавляет ресурсный nav, пишет финальный mkdocs.yml.
**Минус:** Go-генератор становится ответственным за весь mkdocs.yml, сложнее поддерживать структуру темы/плагинов.
### Вариант 3: mkdocs-awesome-pages
Плагин создаёт `.pages` файлы в директориях и управляет порядком через них. Но `nav:` в mkdocs.yml при этом должен быть либо полностью убран, либо включать ресурсы явно — проблему merge не решает.
---
## Дополнительная проблема: guides при смене `docs_dir`
Когда `docs_dir` переключается на `generated/{stand}/docs_llm`, пути вида `30_registry/guides/getting-started.md` в `nav:` ломаются — этих файлов там нет.
**Решение:** в том же Python pre-build шаге скопировать `30_registry` в `$MKDOCS_DOCS_DIR/30_registry/` перед сборкой. Или — убрать guides из профильного nav (оставить только ресурсы).
---
**Итого:** Вариант 1 — минимальные изменения, всё уже на месте. Нужно только расширить существующий Python блок в `04_build_and_publish_docs.sh` примерно на 10 строк + скопировать `30_registry` в `docs_dir`.
+36
View File
@@ -0,0 +1,36 @@
# Sonnet: ответ по улучшению документации
## Принцип: «User Journey First» — 4 сценария
A. «Хочу задеплоить» → описание (30s) → минимальный пример (1m) → apply
B. «Хочу настроить параметр» → справка с читаемыми constraints
C. «Хочу использовать output» → outputs + HCL-примеры
D. «Хочу modify/restart/suspend» → страница операций
## Изменения по 3 слоям
### Слой 1: LLM-промпт — P1 (макс. польза, 0 компиляции)
- Раскрывать `value_list` → «Допустимые значения: 1, 3, 5, 7»
- Раскрывать `regex` → «Формат: cron»
- Заполнять пустые описания из MAN
- Группы (clusterConfiguration) — 1 предложение из MAN
- Операции — заполнять «—» из MAN
- Заменять TODO в примерах на реальные значения
### Слой 2: docs-generator (Go) — P2
- Убрать колонку ID
- value_list → читаемый текст
- Двойной пример: минимальный (15 строк) + полный в <details>
- Секция «Быстрый старт» перед MAN
### Слой 3: mkdocs — P3
- Sidebar по категориям (Базы данных / K8s / Хранилище / ...)
- Хлебные крошки
- Убрать inline-навигацию (заменяет sidebar)
- Back/Next кнопки
## Порядок реализации
1. LLM промпт — мгновенный эффект на все 34 сервиса
2. renderParamTable — убрать ID, раскрыть constraints
3. buildExamplePage — двойной пример
4. mkdocs nav — категории в sidebar
+130
View File
@@ -0,0 +1,130 @@
# Sonnet Briefing: анализ и улучшение документации провайдера
Цель: изучить КАЖДЫЙ шаг генерации документации и предложить конкретные улучшения,
чтобы пользователю было понятно и удобно работать с каждым ресурсом.
---
## ⛔ Файлы — ПРОЧИТАТЬ ОБЯЗАТЕЛЬНО ВСЕ
### Генераторы
| # | Файл | Что смотреть |
|---|------|-------------|
| 1 | `TOOLS/docs-generator/main.go` | весь main — как вызывается, какие флаги |
| 2 | `TOOLS/docs-generator/internal/writers/writers.go` | ВСЕ функции. Особенно: `buildCreateParamsPage`, `buildModifyParamsPage`, `buildOutputsPage`, `buildExamplePage`, `buildManualPage`, `renderParamTable`, `renderNestedParams`, `htmlToMarkdown`, `formatParamOrBlock` |
| 3 | `TOOLS/docs-generator/internal/types/` | структуры YAML-спеки |
### LLM-обработка
| # | Файл | Что смотреть |
|---|------|-------------|
| 4 | `TOOLS/scripts/05_generate_docs_llm.py` | весь скрипт — как вызывается LLM, промпт, как парсится ответ |
| 5 | `docs/LLM_DOCS_GENERATION.md` | архитектура, правила для LLM |
### Результаты генерации (примеры)
| # | Файл | Что смотреть |
|---|------|-------------|
| 6 | `generated/test/docs/postgres_params_create.md` | RAW-вывод docs-generator (без LLM) |
| 7 | `generated/test/docs_llm/90_postgres.md` | ПОСЛЕ LLM-обработки |
| 8 | `generated/test/docs/postgres.md` | главная страница (MAN) |
| 9 | `generated/test/docs/postgres_example.md` | HCL пример |
| 10 | `generated/test/docs/postgres_outputs.md` | выходные параметры |
| 11 | `generated/test/docs/postgres_ops.md` | список операций |
---
## Текущий пайплайн (3 шага)
```
YAML-спеки
→ docs-generator (Go) → raw .md с HTML-таблицами
→ LLM (gpt-oss-120b, по одному файлу) → улучшенные .md
→ mkdocs-material → статический сайт → S3
```
---
## Что видит пользователь СЕЙЧАС (пример: postgres_params_create.md)
### До LLM (raw):
```html
<table><thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>
<tr><td>788</td><td><strong><code>cluster_configuration</code></strong></td><td><code>map-fixed</code></td><td></td><td></td></tr>
```
- Колонка ID (техническая, пользователю не нужна)
- Английские заголовки (Code, Description, Constraints)
- Пустые ячейки Description
- Raw `value_list=` в Constraints
### После LLM:
```
| clusterConfiguration | map-fixed | да | — | — |
```
- Русские заголовки ✅
- ID убран ✅
- Но: пустые Description (—), value_list не раскрыт
---
## Проблемы (что нужно улучшить)
### 1. Пустые описания параметров
Многие параметры в выводе имеют `—` в колонке «Описание». Если сервис предоставил `descr` или `man` в YAML — он должен быть в документации.
### 2. Технические колонки
- Колонка «Constraints» показывает `value_list=1, 3, 5, 7` вместо читаемого «Допустимые значения: 1, 3, 5, 7»
- Колонка «Default» показывает пустую строку вместо «нет» или «—»
- ID параметров виден в raw-версии, но нужен ли он вообще?
### 3. MAN-секция (service_man)
Это HTML-строка с полным руководством от облачного провайдера. Она обрабатывается `htmlToMarkdown()` — regex-заменами. Часто результат нечитаемый: сломанные списки, потерянные ссылки, HTML-мусор.
### 4. HCL-примеры
`buildExamplePage` генерирует пример с ВСЕМИ параметрами (required + default). Это гигантский манифест на 100+ строк. Может, показывать сначала минимальный working example, а полный — отдельно?
### 5. Навигация
На каждой странице — строка навигации из 6 ссылок. Занимает место, дублируется. Может, сделать сайдбар или хлебные крошки?
### 6. LLM-промпт (05_generate_docs_llm.py)
Промпт просит «улучшить формулировки», но:
- Не просит раскрывать value_list в читаемый вид
- Не просит добавлять «почему» и «зачем» к параметрам
- Не использует `service_man` как дополнительный контекст для обогащения описаний
- Обрабатывает по одному файлу — теряет контекст между страницами
---
## Вопросы
### Q1: Структура страниц
Текущая: Manual | Create params | Modify params | Outputs | Ops | Example.
Удобно ли это? Что переставить/добавить/убрать? Может, всё на одной странице с якорями?
### Q2: HTML-таблицы vs Markdown-таблицы
Сейчас raw — HTML, LLM конвертирует в Markdown-таблицы. Оставить Markdown? Или HTML-таблицы лучше (CSS, выравнивание)?
### Q3: LLM-промпт
Как улучшить промпт чтобы:
- description параметров наполнялся из `service_man` где возможно
- value_list показывался читаемо
- empty cells говорили «не указано» а не «—»
### Q4: HCL-примеры
Минимальный пример + полный? Или только минимальный? Или только полный?
### Q5: Постраничная vs одностраничная документация
7 .md файлов на сервис. Это норм или перебор? Может, генерировать один README.md на сервис со всем внутри?
### Q6: Что ещё можно улучшить для UX?
Посмотри на любые 2-3 страницы из `generated/test/docs_llm/` и скажи: что непонятно, что раздражает, чего не хватает.
---
## Ожидаемый ответ
1. Анализ текущего состояния: что хорошо, что плохо (с конкретными примерами из файлов)
2. Конкретные предложения по каждому из 6 вопросов
3. Unified diff предлагаемых изменений в writers.go и 05_generate_docs_llm.py
4. Пример одной страницы «как должно быть» для postgres (хотя бы params_create)
+50
View File
@@ -0,0 +1,50 @@
# Документация — полный диалог и финальный план
## Ответы на 3 вопроса Соннета
### 1. Inline-навигацию убирать?
**Убирать, но сначала добавить страницы в sidebar.**
Сейчас ресурсные страницы не в `mkdocs.yml nav:` — они сироты. Inline nav — единственная навигация.
Порядок: сначала P3 (добавить в sidebar через `_nav_fragment.yml`), потом убрать inline из writers.go.
### 2. Минимальный пример — только required без default?
**Да.** Параметр с дефолтом и так сработает без указания. Минимальный пример = required=true И default пустой.
15 строк вместо 100.
### 3. Категории для sidebar
Группировка по 7 категориям:
| Категория | Сервисы |
|---|---|
| Базы данных | postgres, redis, mongodb, mariadb, clickhouse |
| Очереди | rabbitmq, kafka |
| Хранилище | s3, s3bucket, nextcloud |
| K8s | k8s_velero, k8s_sthutrval_cluster, k8s_openbao, vc_mgmt_sthutrval_cluster |
| VMware | vc_org, vc_vdc, vc_nsxt, vcexternalip, vapp, vc_vm_v2, vc_vm_v3, vc_vdc_group |
| Приложения | flask, nodejs, lucee, http, gitea, superset, pgadmin, harbor, akhq, llm_ai |
| Сеть | zones_v2, dnsrecord |
---
## Финальный план (3 слоя)
### P1: LLM-промпт (05_generate_docs_llm.py) — 0 компиляции, 34 сервиса
6 инструкций:
- value_list → «Допустимые значения: X, Y, Z»
- regex → «Формат: cron / UUID / IP»
- Описания групп из MAN
- Пустые описания заполнять
- Операции без «—»
- TODO в примерах → реальные значения
### P2: docs-generator (writers.go)
- renderParamTable: убрать ID, value_list → читаемый текст
- buildExamplePage: минимальный пример + полный в <details>
### P3: mkdocs навигация
- docs-generator генерирует _nav_fragment.yml с категориями
- 04_build_and_publish_docs.sh вставляет его в mkdocs.yml
- writers.go: убрать inline nav
- mkdocs.yml: breadcrumbs + prev/next
### Порядок: P1 → P2 → P3
+32
View File
@@ -0,0 +1,32 @@
# Документация — финальный план P1 (утверждён)
Дата: 2026-08-09
Источник: Sonnet, после серии брифов и уточнений
## Что меняется в 05_generate_docs_llm.py
### 1. SYSTEM_PROMPT — замена
Новый промпт с правилами A-E (см. HISTORY/SONNET/docs_prompt_full_response.md)
### 2. max_tokens: 4096 → 8192
### 3. Новая функция extract_man(text) → str
Вырезает блок ## MAN из Name.md. Используется как контекст для params/ops.
### 4. Новая функция build_prompt(file_type, filename, content, man) → str
Формирует сообщение для LLM: тип файла + MAN-контекст + содержимое.
### 5. Обработка ВСЕХ типов файлов
Было: только Name.md
Стало: Name.md, _params_create.md, _params_modify.md, _ops.md, _example.md
MAN-контекст: для params и ops, без MAN для главной и примеров.
### 6. Копирование в docs_llm/
- outputs, params-landing, subresource — копировать as-is
- 30_registry/, guides/, index.md — копировать из docs/
### Решения
- Subresource: копировать as-is (не через LLM)
- max_tokens: 8192
- Вывод: docs_llm/
- 30_registry копировать в самом скрипте
+35
View File
@@ -0,0 +1,35 @@
# Sonnet: финальный план P1 — 05_generate_docs_llm.py
## Пайплайн на один сервис
```
docs/Name.md ─┐
docs/Name_params_create.md ─┤ LLM → docs_llm/Name.md
docs/Name_params_modify.md ─┤ docs_llm/Name_params_create.md
docs/Name_ops.md ─┤ docs_llm/Name_params_modify.md
docs/Name_example.md ─┘ docs_llm/Name_ops.md
docs_llm/Name_example.md
docs/Name_outputs.md ─── copy → docs_llm/Name_outputs.md
docs/Name_params.md ─── copy → docs_llm/Name_params.md
docs/Name_subresource*.md ─── copy → docs_llm/Name_subresource*.md
```
## Ключевые детали
### MAN-контекст
- Извлекается из `docs/Name.md` (raw HTML) функцией `extract_man()`
- Передаётся в том же сообщении что и params/ops файлы
- Для главной страницы (Name.md) и примеров (_example.md) — без MAN
### Параметры LLM
- `max_tokens`: 4096 → **8192**
- `temperature`: 0.15 (без изменений)
- `model`: gpt-oss-120b (без изменений)
### Интеграция с 04_build_and_publish_docs.sh
Вариант A: DOCS_GEN_DIR → `docs_llm/`. Скрипт копирует guides/, 30_registry/, index.md в docs_llm/.
## Вопрос: subresource-страницы обрабатывать LLM?
postgres_user.md, postgres_database.md, postgres_backup.md — их структура как у _params_create.md.
Пока копировать as-is или тоже через LLM?
+38
View File
@@ -0,0 +1,38 @@
# Sonnet: ответы — готовый SYSTEM_PROMPT и механизм MAN→группы
## Вопрос 2: MAN → группы
Явный маппинг НЕ нужен. LLM делает семантический матч:
- Видит группу `clusterConfiguration` с sub-params `cpu, memory, disk, replicas`
- Видит в MAN: «Квота (millicore) ядра пода... Квота памяти... Размер диска... Количество узлов»
- Сопоставляет по смыслу → «Ресурсы пода кластера: CPU, RAM, диск, реплики»
Условие: MAN в том же сообщении, что и params-файл.
## Готовый SYSTEM_PROMPT
См. полный текст с правилами A-E:
- A: удалить колонку ID
- B: value_list → «Допустимые значения: X, Y, Z»
- C: regex → читаемый формат
- D: пустые описания → заполнить из MAN
- E: группы (map-fixed) → 1 предложение о содержимом
+ правила для _ops.md, _example.md, главной страницы (MAN)
## Новая логика вызова LLM
MAN передаётся как контекст в том же сообщении что и params-файл:
```
Тип файла: _params_create
Сервис: postgres
=== MAN СЕРВИСА ===
{текст MAN из Name.md}
=== Файл ===
{содержимое}
```
## 2 вопроса
1. max_tokens 4096 → 8192? (params для postgres ~4KB HTML)
2. Писать в docs_llm/ или сразу на место?
+162
View File
@@ -0,0 +1,162 @@
# Sonnet: ПОЛНЫЙ ответ — готовый SYSTEM_PROMPT + механизм MAN→группы
## Вопрос 2: механизм маппинга MAN → группы
**Явный маппинг не нужен.** LLM делает его сам через семантику.
Как это работает для `clusterConfiguration`:
```
LLM видит в _params_create.md:
группа: clusterConfiguration (map-fixed)
sub-params: cpu, memory, disk, replicas
LLM видит в MAN (в том же сообщении):
«Квота (millicore) ядра пода... Квота (megabyte) памяти...
Размер диска... Количество узлов (реплик)...»
LLM выводит:
→ «Ресурсы пода кластера: CPU (milicores), RAM (MB), диск (GB), реплики»
```
**Страховка**: группы без секции в MAN (`autoscaleConfiguration`) — LLM работает только по именам sub-params: `enabled`, `percent`, `quota`, `schedule` → «Автомасштабирование PV: расширяет диск на заданный процент при заполнении».
**Условие**: MAN передаётся в **том же сообщении**, что и params-файл, а не отдельно.
---
## Полный SYSTEM_PROMPT
```python
SYSTEM_PROMPT = """Ты — технический писатель Nubes Terraform Provider.
Улучшаешь автогенерированные Markdown-файлы документации.
═══════════════════════════════════════════════════════
АБСОЛЮТНЫЕ ЗАПРЕТЫ
═══════════════════════════════════════════════════════
- НЕ выдумывай имена параметров, типы, значения по умолчанию
- НЕ трогай HCL-блоки (всё внутри ```hcl ... ```)
- НЕ трогай имена параметров в таблицах (snake_case / camelCase из API)
- НЕ трогай навигационные строки вида [Manual](x.md) · [Create params](y.md) ...
- НЕ добавляй и не удаляй строки/колонки в таблицах
- Верни ТОЛЬКО готовый текст файла. Без объяснений, без``` вокруг всего текста
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ТАБЛИЦ ПАРАМЕТРОВ (_params_create.md, _params_modify.md)
═══════════════════════════════════════════════════════
Таблицы содержат столбцы: ID | Code | Type | Required | Default | Description | Constraints
Можно менять ТОЛЬКО текст в <td>Description</td> и <td>Constraints</td>.
ПРАВИЛО A — удали колонку ID:
Удали <th>ID</th> из заголовка и соответствующий первый <td>число</td> из каждой строки.
ПРАВИЛО B — value_list в Constraints:
value_list=1, 3, 5, 7 → очисти ячейку Constraints до пустой.
В ячейку Description добавь строку: «Допустимые значения: **1, 3, 5, 7**»
Если в Description уже был текст — добавь после него, через пробел или перевод строки (<br/>).
ПРАВИЛО C — regex в Constraints:
Замени regex-строку на читаемое описание формата:
- cron-подобный regex → «Формат: cron-выражение. Пример: `0 0 * * *`»
- UUID regex → «Формат: UUID»
- IP-адрес regex → «Формат: IP-адрес»
- Прочее → кратко опиши формат своими словами
ПРАВИЛО D — пустое Description (пустая ячейка или —):
Напиши краткое описание параметра (1–2 предложения). Приоритет источников:
1. MAN — ищи текст, связанный с параметром по смыслу и по именам sub-params
2. Имя параметра snake_case → понятный русский
3. Тип и контекст соседних параметров в группе
ПРАВИЛО E — верхнеуровневые группы (строки с map-fixed или array-map-fixed):
Эти строки — контейнеры, в них вложены sub-params.
Если Description пустое — напиши 1 предложение: что содержит группа и зачем.
Смотри на имена sub-params (они идут в следующих строках) + MAN.
Пример: clusterConfiguration с sub-params cpu/memory/disk/replicas
→ «Ресурсы пода кластера: CPU (milicores), RAM (MB), диск (GB) и количество реплик»
Пример: backupConfiguration с sub-params s3_uid/retain/schedule
→ «Параметры резервного копирования: S3-хранилище, расписание и глубина хранения»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ОПЕРАЦИЙ (_ops.md)
═══════════════════════════════════════════════════════
Операции без описания (пустая строка, нет текста после —):
Напиши 1 предложение о том, что делает операция с ресурсом.
Используй MAN если передан. Не придумывай параметров.
Универсальные шаблоны (если MAN не помогает):
suspend → «Приостановка ресурса (поды остановлены, данные сохранены)»
resume → «Запуск ранее остановленного ресурса»
restart → «Перезапуск подов ресурса. ⚠️ Возможна кратковременная недоступность»
reconcile → «Принудительная синхронизация состояния с API»
recovery → «Восстановление из резервной копии»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ГЛАВНОЙ СТРАНИЦЫ (Name.md — секция ## MAN)
═══════════════════════════════════════════════════════
Блок ## MAN содержит HTML внутри <div class="man-content">.
Преобразуй HTML → читаемый Markdown:
<h2>/<h3> → ## / ###
<ul><li> → - элемент списка
<strong> → **текст**
<code> → `текст`
<a href="url">текст</a> → [текст](url)
<br/>, <p>, <div> → удали тег, замени переносами строк где нужно
Лишние пустые строки подряд → одна пустая строка
Сохраняй всё смысловое содержание. Не перефразируй, не сокращай.
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ПРИМЕРОВ (_example.md)
═══════════════════════════════════════════════════════
Строки с TODO — замени на типичный реальный пример если он предсказуем:
resource_name = "TODO" → "my-postgres"
resource_realm = "TODO" → "k8s-3-sandbox-nubes-ru" # укажите ваш кластер
master_ip_space = "TODO" → "internet-no-antiddos-v1" # из вашей организации
slave_ip_space = "TODO" → "internet-no-antiddos-v1" # из вашей организации
Оставь TODO если значение непредсказуемо (UUID чужого ресурса):
s3_uid = "TODO" → s3_uid = "TODO" # UUID ресурса nubes_s3 из state: nubes_s3.backup_store.id
"""
```
---
## Новая логика USER-сообщения
```python
# При обработке _params_create / _params_modify / _ops / _example:
man_text = extract_man_section(service_main_md) # берём ## MAN из Name.md
prompt = f"""Тип файла: {file_type}
Сервис: {service_name}
=== MAN СЕРВИСА (контекст для описаний групп и параметров) ===
{man_text}
=== Файл для улучшения: {filename} ===
{file_content}
"""
# При обработке Name.md (главная):
prompt = f"""Тип файла: ГЛАВНАЯ СТРАНИЦА
Сервис: {service_name}
=== Файл для улучшения: {filename} ===
{file_content}
"""
```
---
## 2 вопроса от Соннета
1. `max_tokens` сейчас `4096` — но `_params_create.md` для postgres ~4KB HTML. Поднять до `8192`?
2. Писать в `docs_llm/` или сразу на место?
---
## Ответы
1. **max_tokens = 8192** — да. После обогащения описаниями файл станет больше.
2. **Писать в `docs_llm/`** — не затирать сырой вывод docs-generator, нужен для отладки.
+195
View File
@@ -0,0 +1,195 @@
# Sonnet Briefing: вывод этапов (stages) в Terraform-провайдере Nubes
> Цель задания: изучить код и выдать **точный план** — какие строки в каких файлах менять.
> Без реализации. Только анализ и unified diff.
---
## 1. Суть проблемы
### Как сейчас (плохо)
При `terraform apply/destroy` пользователь видит тупой счётчик:
```
nubes_postgres.pg_db: Still creating... [00m10s elapsed]
nubes_postgres.pg_db: Still creating... [00m20s elapsed]
nubes_postgres.pg_db: Still creating... [00m30s elapsed]
```
Это сообщения самой Terraform (не нашего кода) — фреймворк показывает их, пока ресурс находится в состоянии создания/удаления.
### Как должно быть (как в autotest)
```
[OK ] 1. Валидация — 63.3 sec
[OK ] 2. Конфигурация — 0.3 sec
[OK ] 3. Внешний IP — 2.2 sec
[..] 4. Доступ — 1.3 sec ← ТЕКУЩИЙ этап (ещё идёт)
5. DNS — ещё не начат
6. Основной процесс
7. Проверки
```
---
## 2. Эталонная реализация — autotest
**Файл:** `/home/naeel/nubes/autotest/app-autotest/site/static/js/operations.js:331`
```js
function showStages(stages){
if(!stages||!stages.length) return;
let html='<div style="font-size:11px;...">Этапы</div>';
stages.forEach(s=>{
const done=!!s.dtFinish; // этап завершён?
const icon=done?(s.isSuccessful?'✅':'❌'):'⏳'; // ⏳ = текущий
html+=`<div>${icon} ${s.stage} — ${(s.duration||0).toFixed(1)}s</div>`;
});
boxes[boxes.length-1].innerHTML=html;
}
```
**Ключевое правило:** `dtFinish == null` → этап СЕЙЧАС выполняется (⏳). `dtFinish != null` → завершён (✅/❌).
**Поллинг:** каждые 2 секунды через `GET /api/test/status/{opUid}` → `showStages(sd.stages)`.
**API-запрос к Nubes:** `GET /instanceOperations/{opUid}?fields=dtFinish,isSuccessful,errorLog,duration,stages`
**Структура stages из API** (документация: `/home/naeel/nubes/autotest/DOCS/api-operation-stages.md`):
```json
{ "stage": "1. Валидация", "isSuccessful": true, "dtFinish": "2026-...", "duration": 63.3 },
{ "stage": "2. Доступ", "isSuccessful": null, "dtFinish": null, "duration": 14.1 },
{ "stage": "3. Проверки", "isSuccessful": null, "dtFinish": null }
```
---
## 3. Что УЖЕ есть в провайдере
### Файл: `provider/internal/core/client.go`
#### a) Структуры для парсинга stages (строка ~913)
```go
type opStage struct {
InstanceOperationStageUid string `json:"instanceOperationStageUid"`
Stage string `json:"stage"`
IsSuccessful bool `json:"isSuccessful"`
DtFinish *string `json:"dtFinish"`
Duration float64 `json:"duration"`
StageMsg *string `json:"stageMsg"`
}
type operationStatusResponse struct {
InstanceOperation struct {
DtFinish *string `json:"dtFinish"`
IsSuccessful *bool `json:"isSuccessful"`
ErrorLog *string `json:"errorLog"`
IsInProgress bool `json:"isInProgress"`
IsPending bool `json:"isPending"`
Duration *float64 `json:"duration"`
Stages []opStage `json:"stages"`
} `json:"instanceOperation"`
}
```
#### b) Цикл поллинга `waitForOperationFinish()` (строка ~930)
- Поллит каждые 5 секунд
- Запрашивает `?fields=dtFinish,isSuccessful,errorLog,isInProgress,isPending,duration,stages`
- Парсит ответ в `operationStatusResponse`
#### c) ВЫВОД ЭТАПОВ — уже есть, но с тремя проблемами (строка ~960-990)
```go
// Проблема 1: загейтино за log_level
logLevel := c.LogLevel
if v, ok := ctx.Value(ctxKeyLogLevel).(string); ok && v != "" {
logLevel = v
}
showStages := logLevel == "info" || logLevel == "debug" // ← по умолчанию "none" = hidden!
// Проблема 2: показывает ТОЛЬКО завершённые, текущий ПРОПУСКАЕТ
for _, stage := range status.InstanceOperation.Stages {
if stage.DtFinish == nil || *stage.DtFinish == "" {
continue // ← ТЕКУЩИЙ ЭТАП ИГНОРИРУЕТСЯ
}
fmt.Fprintf(tty, " [%s] %s — %.1f sec\n", status2, stage.Stage, stage.Duration)
}
// Проблема 3: пишет в /dev/tty через ttyOut()
tty := ttyOut()
defer tty.Close()
```
#### d) Конфигурация log_level в провайдере: `provider/internal/provider/provider.go:150`
```go
logLevel := "none" // ← ДЕФОЛТ! Этапы СКРЫТЫ всегда, пока пользователь не выставит log_level="info"
```
---
## 4. Что нужно изучить и выдать в плане
### Вопрос 1: `/dev/tty`
- `ttyOut()` открывает `/dev/tty`. В каких окружениях это работает, а в каких — нет?
- Стоит ли заменить на `tflog.Info()` / `tflog.Debug()` (стандартный terraform-логгинг)?
- Или оставить `/dev/tty` как самый надёжный способ прямого вывода?
- Как это сделано в autotest (там вывод через DOM, не применимо к CLI-провайдеру).
### Вопрос 2: текущий этап
- Сейчас `DtFinish == nil → continue` — текущий этап не показывается.
- Нужно: для `DtFinish == nil` выводить `[..] {stage} — {duration}s` (текущий).
- При этом не плодить дубликаты — отслеживать, какой этап уже был показан.
- Как правильно обновлять одну и ту же строку в терминале (carriage return? перепечатывать?)
### Вопрос 3: log_level по умолчанию
- Сейчас `"none"` — этапы скрыты.
- Нужно ли менять дефолт на `"info"`? Плюсы: пользователь сразу видит этапы. Минусы: лишний вывод в CI.
- Альтернатива: оставить `"none"`, но сделать `"info"` более заметным в документации.
### Вопрос 4: формат вывода
- Сейчас: `[OK] 1. Валидация — 63.3 sec`
- Для текущего: `[..] 2. Основной процесс — 14.1 sec`
- Для ещё не начатых: показывать или нет? В autotest показывают все (с `⏳`).
- Этапы, которые ещё не начались (`DtFinish == nil` + `DtStart == nil`) — показывать с пометкой `[--]`?
### Вопрос 5: `Still creating...` от Terraform
- Сообщения `Still creating...` генерятся самим фреймворком Terraform.
- Можно ли их подавить/заменить? Или они останутся в любом случае?
- Если нельзя подавить — этапы пойдут ПОВЕРХ или ВМЕСТЕ с этими сообщениями.
### Вопрос 6: `StageMsg` (debug)
- В текущем коде есть `formatStageMsg()` для вывода деталей подэтапов при `log_level == "debug"`.
- Это работает? Стоит сохранить?
---
## 5. Файлы, которые нужно изучить
| Файл | Что смотреть |
|---|---|
| `provider/internal/core/client.go` | `waitForOperationFinish()`, `ttyOut()`, `opStage`, `formatStageMsg()`, `ctxKeyLogLevel` |
| `provider/internal/provider/provider.go` | конфигурация `log_level` (строка 85, 150) |
| `provider/internal/resources_core/crud.go` | вызовы `CreateResourceWithTimeout`, `UpdateResourceWithTimeout`, `DeleteResource` |
| `provider/internal/resources_gen/90_postgres_resource.go` | пример сгенерированного ресурса — вызов CRUD |
| `/home/naeel/nubes/autotest/app-autotest/site/static/js/operations.js` | `showStages()` — эталон (строка 331) |
| `/home/naeel/nubes/autotest/app-autotest/site/static/js/history.js` | `renderStages()` — эталон для истории (строка 87) |
| `/home/naeel/nubes/autotest/app-autotest/site/operations/poll.py` | `poll_until_done()` — эталон поллинга |
| `/home/naeel/nubes/autotest/DOCS/api-operation-stages.md` | структура stages из API |
---
## 6. Ожидаемый результат
**Не код, а ПЛАН.** В ответе должно быть:
1. Краткий анализ: что работает, что сломано, почему.
2. Для каждой из трёх проблем (log_level, /dev/tty, текущий этап) — конкретное решение со ссылками на строки.
3. Unified diff для каждого изменяемого файла (можно схематичный — какие блоки кода заменить на какие).
4. Ответы на все 6 вопросов из раздела 4.
5. Оценка рисков: что может пойти не так при каждом изменении.
---
## 7. Правила (обязательно)
- ⛔ Критерий завершения операции — `dtFinish`. ЭТО НЕ ТРОГАТЬ НИ ПРИ КАКИХ УСЛОВИЯХ.
- ⛔ Логика поллинга (частота, таймауты) — не менять без согласования.
- ⛔ Существующие сигнатуры функций — не менять без согласования.
- ✅ Новая логика — новые функции/блоки, не ломать существующее.
@@ -0,0 +1,55 @@
# Stages Output — Финальный план реализации
> Утверждён: 2026-08-09
> Источник: Sonnet briefing + уточнения
## Принятые решения
| Решение | Почему |
|---|---|
| Вывод через `/dev/tty` с fallback на `os.Stderr` | tflog привязан к TF_LOG, не к нашему log_level |
| Только `\n`, без `\r` | `\r` конфликтует с выводом Terraform (Still creating...) |
| Текущий этап: `[..] stage\n` один раз | Без промежуточной duration (менялась бы и запутывала) |
| Завершённый этап: `[OK ] stage — Xs\n` | C префиксом для выравнивания |
| Дефолт log_level: `"none"` | Не breaking change, opt-in через env var |
| Env var `NUBES_LOG_LEVEL` | Симметрично NUBES_INSECURE |
## Что правим
### client.go — 3 правки
1. **Вынести tty из цикла** (resource leak fix)
- `tty := ttyOut()` + `defer tty.Close()` → перед `for {`
- Убрать `tty := ttyOut()` и `defer tty.Close()` из тела цикла
2. **Добавить `lastPendingUID`** рядом с `printedStages`
3. **Заменить блок вывода этапов:**
- Завершённый (DtFinish != nil) → `[OK ] stage — Xs\n` или `[FAIL] stage — Xs\n`
- Текущий (DtFinish == nil) → `[..] stage\n` один раз при смене UID
### provider.go — 1 правка
4. **Добавить поддержку NUBES_LOG_LEVEL** env var (по аналогии с NUBES_INSECURE)
- Приоритет: config.LogLevel > NUBES_LOG_LEVEL > "none"
## Формат вывода (пример)
```
[..] 1. Валидация
[OK ] 1. Валидация — 63.3 sec
[..] 2. Основной процесс
[OK ] 2. Основной процесс — 21.3 sec
[..] 3. Проверки
[OK ] 3. Проверки — 107.7 sec
[..] 4. Настройка
[OK ] 4. Настройка — 4.6 sec
[DONE] 201.5 sec
```
## НЕ ТРОГАТЬ
- Критерий завершения (dtFinish)
- Интервал поллинга (5 сек)
- Таймауты
- Сигнатуры функций

Some files were not shown because too many files have changed in this diff Show More