Compare commits

182 Commits
Author SHA1 Message Date
“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
2088 changed files with 12830 additions and 30939 deletions
+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/` — лог рассуждений агента (обязательно) - `doc/thinking/` — лог рассуждений агента (обязательно)
@@ -37,20 +8,26 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=
## Git ## Git
Коммитить и пушить через SSH после каждого завершённого этапа. - Коммитить и пушить после каждого завершённого этапа
- **Синхронизация: `git pull` (НЕ `reset --hard`).** `reset --hard` стирает локальные правки. Для синхронизации после пуша — только `git pull`.
Версионирование тегами: `vMAJOR.MINOR.PATCH` - **⛔ .gitignore — ДО генерации:** генераторы по сервисам (`yaml-generator`, `resource-generator`, `docs-generator` в `TOOLS/bin/`) создают файлы в папках-приёмниках. Эти папки-приёмники — СРАЗУ добавлять в `.gitignore`, ДО запуска генераторов. НЕ после. Что именно: `resources_yaml/`, `resources_gen/`, `generated/`, `docs/30_registry/resources/` и любые новые.
- Patch — любое изменение кода - **Аудит при старте:** перед началом работы — `git ls-files` на сгенерированное, сверить с `.gitignore`
- Minor — новая фича / компонент - **⛔ Симлинки запрещены.** Никаких `ln -s`. Разные стенды — разные файлы. Общий код — через импорты, не через симлинки.
- Major — breaking change
```bash
git tag vX.Y.Z && git push origin vX.Y.Z
```
## Поведение агента ## Поведение агента
- **⛔ СНАЧАЛА АНАЛИЗ — ПОТОМ ДЕЙСТВИЕ.** Перед ЛЮБЫМ действием: прочитать связанные файлы, понять архитектуру, проверить зависимости. НЕ запускать скрипты/сборки не поняв что они делают и что им нужно.
- Не трогать рабочий код без явного указания - Не трогать рабочий код без явного указания
- Не делать ничего сверх того, о чём явно попросили - Не делать ничего сверх того, о чём явно попросили
- Деструктивные операции (`kubectl delete`, `rm -rf`, `terraform destroy` и др.) — только после явного подтверждения с указанием конкретных объектов - Деструктивные операции (`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: env:
NAMESPACE: nubes NAMESPACE: nubes
NAME: nubes NAME: nubes
DEFAULT_HOST: terra.k8c.ru DEFAULT_HOST: tf-registry.containerk8s.services.ngcloud.ru
jobs: jobs:
publish: publish:
+23
View File
@@ -8,9 +8,28 @@
# === Generated files (NOT code — regenerate from API) === # === Generated files (NOT code — regenerate from API) ===
provider/resources_yaml/ provider/resources_yaml/
provider/internal/resources_gen/ provider/internal/resources_gen/
# === External repos (managed separately) ===
tfluceecrud/
tfflaskcrud/
tfnodejscrud/
tf_examples/
gateway/
generated/ generated/
TOOLS/bin/ TOOLS/bin/
universal_rebuild/docs-gen/ universal_rebuild/docs-gen/
artifacts/
provider/artifacts/
provider/generated/
# === Binaries (compiled from source) ===
*.bin
*.so
*.dylib
*.dll
*.exe
*.test
*.out
# === Build artifacts (generated by devops scripts) === # === Build artifacts (generated by devops scripts) ===
devops/profiles/*/generated/ devops/profiles/*/generated/
@@ -54,7 +73,9 @@ secrets/id_ed25519.txt
# === MkDocs === # === MkDocs ===
site/ site/
site_test/
.mkdocs.tmp.yml .mkdocs.tmp.yml
.mkdocs.docs_test.yml
# === Generated universal_rebuild artifacts === # === Generated universal_rebuild artifacts ===
universal_rebuild/universal_rebuild/ universal_rebuild/universal_rebuild/
@@ -91,3 +112,5 @@ universal_rebuild/service_params_gen
terraform-provider-mycloud terraform-provider-mycloud
artifacts/api-meta/*/errors.log artifacts/api-meta/*/errors.log
TOOLS/bin/ TOOLS/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 = "3.1.13"
}
}
}
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-экземпляра
+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 = "5.1.16"
}
}
}
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 = "3.1.1"
}
}
}
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 = "3.1.13"
}
}
}
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 authors 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 authors 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 Pierces 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 Saussures 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 tobush 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 слов подряд из исходного текста или других источников расценивается плагиатом, в случае обнаружения которого работа оценивается только на предмет языковой грамотности.
Характеристики научного стиля реферата: объективность, логическая последовательность изложения, текстовая связность; стремление автора к точности изложения фактов и терминологии, сжатости и однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/ академического стиля.
Litaliano e le carenze linguistiche: perché non è (solo) colpa della scuola
di Filomena Fuduli Sorrentino
Oggi lanalfabetismo è 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 dellitaliano odierno dobbiamo ricordare che, fino al tempo dellunità dItalia, litaliano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta lItalia erano tra il 2,5% e il 10% dellintera 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 linglese nelle scuole di New York, dove per rimediare hanno attivato corsi di recupero di scrittura e lettura con docenti specializzati.
Il tema delluso dellitaliano 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 nellArno del Manzoni, fino a giungere a Calvino e al neo-italiano industriale di Pasolini. Lunità 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 litaliano si sviluppò dal fiorentino, ma il percorso non fu facile. Allinizio 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 litaliano 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 larticolo 34: “La scuola è aperta a tutti. Listruzione 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: laccesso allistruzione superiore e alluniversità 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 litaliano nella vita quotidiana. Lanalfabetismo e il semi-analfabetismo erano ampiamente presenti nella popolazione.
Eppure bisogna aspettare il decreto statale del 2007 affinché letà 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 litaliano in tutta la penisola durante gli anni, bensì lintroduzione della televisione nel 1954, anche se aveva un solo canale. I programmi televisivi cominciarono a essere trasmessi dalla RAI, lemittente 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 dellalfabetizzazione 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 sullistruzione 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 unistruzione 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 dellitaliano 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 limportante è farsi capire”. Lettere da una tarantata costituisce un importante punto di riferimento per capire la nascita e lo sviluppo dellitaliano popolare. In seguito, il libro diventerà un testo di riferimento per gli studi sulluso 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 allantropologa Annabella Rossi. Anna rappresenta per Tullio De Mauro lincarnazione della volontà di comunicare delle classi inferiori. Egli ha apprezzato il suo stile, definendolo vivace e originale, e ha invece criticato litaliano insegnato nella scuola, paragonandolo a un “rullo compressore” che rende la lingua piatta e vuota.
Letichetta di italiano popolare fu introdotta nel 1970 da Tullio De Mauro e Manlio Cortelazzo. Lo storico De Mauro lo aveva definito “il modo desprimersi 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 leffettiva presenza di un italiano standard, e hanno valutato positivamente litaliano 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 allitaliano standard, e hanno evidenziato linferiorità dellitaliano popolare, sostenendo la necessità di estirparlo.
Per capire bene il problema dellitaliano odierno dobbiamo ricordare che, fino al tempo dellunità dItalia, litaliano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta lItalia erano tra il 2,5% e il 10% dellintera 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 dellitaliano regionale presentano alterazioni della morfologia e della sintassi dellitaliano 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, litaliano 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 litaliano 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à litaliano 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.23.0.6
- `nubes`: 2.0.2, 2.0.3, 2.0.5, 2.0.6
- `nubes-test`: 0.0.1, 5.0.15.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,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 сек)
- Таймауты
- Сигнатуры функций
+65
View File
@@ -0,0 +1,65 @@
# Sonnet: ответы по тестированию stages без реальных инстансов
## 1. Мок API
Достаточно мокать только `/instanceOperations/{uid}?fields=...`.
`waitForOperationFinish` делает ровно один тип запроса — `doRequest GET /instanceOperations/{uid}?fields=...`. Создаёшь `httptest.NewServer`, настраиваешь нужные ответы, конструируешь клиент:
```go
c := &UniversalClient{
HttpClient: &http.Client{},
ApiEndpoint: testServer.URL,
ApiToken: "test-token",
}
```
Мокать create → params → run **не нужно** — вызываешь `waitForOperationFinish` напрямую с любым `opUid`.
## 2. Время
Проблема: `time.NewTicker(5 * time.Second)` захардкожен, таймаут 30 минут.
**Решение: добавить поле `PollInterval time.Duration` в `UniversalClient`.**
Нулевое значение → default 5s. В тесте ставить 1ms.
`time.Now()` мокать **не нужно**`timeout` уже параметр функции. В тесте передаёшь `200ms`, дедлайн считается `time.Now().Add(200ms)` — реальное время, работает нормально.
## 3. Перехват вывода tty
**Рекомендуемый подход: добавить поле `Writer io.Writer` в `UniversalClient`.**
В `waitForOperationFinish` заменить `ttyOut()` на: `if c.Writer != nil { w = c.Writer } else { w = ttyOut() }`.
В тесте: `c.Writer = &bytes.Buffer{}` → проверяешь что именно напечатано. В проде: оставляешь nil → поведение не меняется.
`os.Pipe()` + подмена `os.Stderr` **не рекомендует** — глобальное состояние, ломается при параллельных тестах.
## 4. Конкретные тест-кейсы
| # | Сценарий | Mock отдаёт | Ожидаем |
|---|----------|-------------|---------|
| 1 | Stages по одному | poll 1: 1 stage без dtFinish; poll 2: тот же stage с dtFinish; poll 3: операция с dtFinish+isSuccessful=true | `err == nil` |
| 2 | Все stages сразу | poll 1: dtFinish + isSuccessful=true + все stages завершены | `err == nil`, Writer содержит `[OK]` для каждого |
| 3 | Fail на этапе 3 | stages[0,1] OK, stages[2] isSuccessful=false, операция dtFinish + isSuccessful=false + errorLog="disk error" | `err` содержит `"disk error"` |
| 4 | API 500 | всегда 500 (doRequest уже делает 3 retry внутри) | `err != nil` |
| 5 | Таймаут | никогда не отдаёт dtFinish | `err` содержит `"не завершилась за установленный таймаут"` |
| 6 | Отмена ctx | poll завис | `err` содержит `"отменена"` |
## 5. Рефакторинг
**Да, минимально — два новых поля в `UniversalClient`:**
```go
type UniversalClient struct {
// ... существующие поля ...
PollInterval time.Duration // 0 = default 5s
Writer io.Writer // nil = ttyOut()
}
```
В `waitForOperationFinish` (~3 строки правки):
- `interval := c.PollInterval; if interval == 0 { interval = 5*time.Second }`
- `w := c.Writer; if w == nil { w = ttyOut(); defer w.(*os.File).Close() }`
Не ломает существующие вызовы (оба поля zero-value совместимы с текущим поведением).
**Что НЕ нужно менять:** сигнатуру функции, логику дедлайна через `time.Now()`, retry в `doRequest`.
@@ -0,0 +1,48 @@
# DeepSeek Flash: ответы по тестированию stages
См. HISTORY/SONNET/stages_testing_question.md — исходные вопросы.
## 1. Мок API
**Да, достаточно httptest.NewServer.** Только `/instanceOperations/{uid}?fields=...`. Цепочку create→params→run не мокать.
```go
client := &UniversalClient{
HttpClient: srv.Client(),
ApiEndpoint: srv.URL,
ApiToken: "test",
}
```
## 2. Время
**Поле PollInterval time.Duration** в UniversalClient. 0 → default 5s. В тесте 1ms.
Таймаут: передать маленький timeout параметром (например 30ms). time.Now мокать не нужно.
## 3. Перехват tty
Вариант B — os.Pipe + подмена os.Stderr. Минус: не потокобезопасно (без t.Parallel() ок).
Вариант C (чище) — вынести печать в метод printStages(w io.Writer, ...), тогда ttyOut для продешкна, bytes.Buffer для теста.
## 4. Тест-кейсы (5 шт)
| # | Кейс | Выдача сервера |
|---|------|----------------|
| 1 | stages по одному | счётчик вызовов: stage1 без dtFinish → stage1 с dtFinish, stage2 без → всё dtFinish |
| 2 | все сразу | первый ответ: всё dtFinish, isSuccessful=true |
| 3 | fail на этапе 3 | stage3: isSuccessful=false, errorLog="boom" |
| 4 | API 500 → retry | 500 → 200. ⚠️ doRequest retry 2s→4s→8s, нужен RetryBaseDelay |
| 5 | таймаут | всегда isInProgress=true, dtFinish пустой |
## 5. Рефакторинг
**Да, минимально. НО:** в коде стоит ⛔ запрет на правку без оператора.
Безопасный минимум:
- PollInterval (0 → 5s default)
- printStages(w io.Writer) — вынос вывода
- RetryBaseDelay (0 → 2s default) — для кейса 500
Альтернатива без рефакторинга: тестировать с реальными 5s тиками — медленно.
@@ -0,0 +1,34 @@
# Sonnet v2: ответы по тестированию stages (после исправленного брифа)
## Q1. Мок API
Только `/instanceOperations/{uid}?fields=...`. `httptest.NewServer` + `HttpClient`/`ApiEndpoint`.
## Q2. Время
Два hardcoded таймера: `time.NewTicker(5s)` и `doRequest` delays (2s→4s→8s).
- Без рефакторинга: тесты медленные (~5-19s)
- С рефакторингом: поле `tickerInterval` — но ⛔ запрещает менять частоту опроса
## Q3. Перехват tty
- Без рефакторинга: вывод в stderr, не ломает тесты
- С рефакторингом: поле `ttyWriter io.Writer`
## Q4. Тест-кейсы (5 шт)
1. stages по одному — `atomic.Int32` счётчик
2. все сразу
3. фейл — `IsSuccessful=false`, `ErrorLog="disk error"`
4. **503 (не 500!)** — 500 не retryable. 503 — retryable. Тест ~19s без рефакторинга
5. таймаут — `timeout=6s`
## Q5. Рефакторинг
Без рефакторинга — всё тестируемо, но медленно.
С рефакторингом — 6 строк, не в protected zone, но `tickerInterval` требует согласования (⛔).
## Итог
| Тест | Без рефакторинга | С рефакторингом |
|---|---|---|
| sequential | 15s | 150ms |
| all_at_once | 5s | 50ms |
| fail | 5s | 50ms |
| 503_retry | 19s | быстро |
| timeout | 5s | 100ms |
| tty capture | ❌ | ✅ |
+53
View File
@@ -0,0 +1,53 @@
# Sonnet: тестирование stages без реальных инстансов
Реализовали вывод этапов в `waitForOperationFinish()`. Код в репе.
Нужно протестировать БЕЗ создания реальных инстансов в облаке.
---
## ⛔ Файлы — ПРОЧИТАТЬ ОБЯЗАТЕЛЬНО (иначе ответ будет неполным)
| # | Файл | Строки | Что искать |
|---|------|--------|------------|
| 1 | `provider/internal/core/client.go` | 2955 | `UniversalClient` struct: поля `HttpClient`, `ApiEndpoint`, `ApiToken` |
| 2 | `provider/internal/core/client.go` | 8951010 | `waitForOperationFinish()` — ВСЯ функция от сигнатуры до закрывающей `}` |
| 3 | `provider/internal/core/client.go` | 895–910 | ⛔ «ЗАПРЕЩЕНО ПРАВИТЬ ЭТОТ КОД БЕЗ ЯВНОГО СОГЛАСОВАНИЯ» |
| 4 | `provider/internal/core/client.go` | 900930 | `ttyOut()`, `opStage` struct, `operationStatusResponse` struct |
| 5 | `provider/internal/core/client.go` | 10301140 | `doRequest()` — retry-логика, паузы **2s → 4s → 8s**, `maxRetries=3` |
| 6 | `provider/internal/core/client_test.go` | весь файл | как создаётся `UniversalClient`, структура существующих тестов |
---
## Вопросы
### 1. Мок API
Достаточно ли `httptest.NewServer` ТОЛЬКО для `/instanceOperations/{uid}?fields=...`?
Или нужно мокать всю цепочку create → params → run → poll?
### 2. Время
`time.NewTicker(5*time.Second)`, таймаут 30 мин. Как ускорить тест?
Dependency injection? Mock `time.Now`?
### 3. Перехват tty
`ttyOut()``/dev/tty` с fallback на `os.Stderr`. Как перехватить вывод?
### 4. Тест-кейсы
Какие тесты написать:
- stages по одному (прогресс)
- stages все сразу
- фейл на этапе N
- API 500 → retry (**учти задержки в `doRequest`: 2s → 4s → 8s**)
- таймаут операции
### 5. Рефакторинг
Стоит ли рефакторить `waitForOperationFinish`?
**Учти ⛔ запрет на правку (стр. 895–910).**
---
## Ожидаемый ответ
1. Конкретный ответ на каждый вопрос
2. Unified diff минимальных изменений (если нужен рефакторинг)
3. Псевдокод каждого тест-кейса
4. Оценка: что можно без рефакторинга, что потребует правок
+108
View File
@@ -0,0 +1,108 @@
# Prompt для Gemini: диаграмма пайплайна IoT Demo
## Задача
Создай красивую диаграмму (flowchart/архитектурную схему) пайплайна данных для IoT-демонстрации «Умный дом». Нужна **одна картинка** с 6 блоками сервисов и стрелками между ними.
---
## Сервисы (блоки)
### 1. Producer (верхний левый угол)
- **Название:** Producer
- **Подпись:** Node.js · Генератор IoT-событий
- **Форма:** прямоугольник, синий/голубой (#3B82F6 или похожий)
- **Иконка:** ⚙️ или 📡
- **Что делает:** Эмулирует 8 IoT-датчиков (температура °C, влажность %, энергопотребление kW), каждые 3 секунды генерирует случайное JSON-событие
### 2. RabbitMQ (центр-верх)
- **Название:** RabbitMQ
- **Подпись:** Очередь сообщений · iot-events
- **Форма:** прямоугольник, оранжевый (#FF6600 — брендовый цвет RabbitMQ)
- **Иконка:** 🐰 или 📬
- **Что делает:** Буфер сообщений AMQP. Принимает от Producer, отдаёт Consumer. Durable — не теряет при рестарте.
### 3. Consumer (правый верхний)
- **Название:** Consumer
- **Подпись:** Node.js · Обработчик
- **Форма:** прямоугольник, зелёный (#10B981)
- **Иконка:** 📥 или 🔄
- **Что делает:** Читает очередь RabbitMQ, параллельно пишет в Redis и MongoDB
### 4. Redis (левый-центр, под Producer)
- **Название:** Redis
- **Подпись:** Кэш · latest + counters + recent
- **Форма:** прямоугольник, красный (#DC2626 — брендовый цвет Redis)
- **Иконка:** ⚡ или 🗄️
- **Что делает:** Оперативный кэш для Dashboard. Хранит: последние значения датчиков (hash), счётчики по типам (hash), ленту 1000 событий (zset).
### 5. MongoDB (центр-низ)
- **Название:** MongoDB
- **Подпись:** Архив · TTL 7 дней
- **Форма:** прямоугольник, тёмно-зелёный (#00684A — брендовый цвет MongoDB)
- **Иконка:** 🍃 или 💾
- **Что делает:** Постоянный архив всех событий в коллекции iot_events. Автоудаление через 7 дней (TTL-индекс).
### 6. Dashboard (правый-низ)
- **Название:** Dashboard
- **Подпись:** Node.js + Chart.js · Веб-интерфейс
- **Форма:** прямоугольник, фиолетовый (#8B5CF6)
- **Иконка:** 📊 или 🖥️
- **Что делает:** Читает Redis, отдаёт JSON API и HTML с графиками. Bar chart (значения), Doughnut (счётчики), таблица событий. Автообновление каждые 3 сек.
---
## Стрелки (потоки данных)
| От | К | Протокол | Подпись на стрелке |
|---|---|---|---|
| Producer | RabbitMQ | AMQP (порт 5672) | JSON-события → очередь iot-events |
| RabbitMQ | Consumer | AMQP (порт 5672) | JSON-события ← очередь iot-events |
| Consumer | Redis (latest) | TCP (порт 6379) | HSET sensor_id → значение |
| Consumer | Redis (counters) | TCP (порт 6379) | HINCRBY device_type |
| Consumer | Redis (recent) | TCP (порт 6379) | ZADD лента событий |
| Consumer | MongoDB | TCP (порт 27017) | insertOne → коллекция iot_events |
| Redis | Dashboard | TCP (порт 6379) | HGETALL + ZRANGE (только чтение) |
---
## Расположение на схеме
```
Producer ──→ RabbitMQ ──→ Consumer
┌──────────────┼──────────────┐
↓ ↓ ↓
Redis MongoDB Redis
(latest, (архив) (counters,
recent) ...)
│ │ │
└──────────────┼──────────────┘
Dashboard
```
---
## Стиль
- **Тёмный фон** (#0F172A или похожий) — как у самого дашборда
- **Блоки** с закруглёнными углами (border-radius 812px)
- **Цвета блоков** — брендовые цвета каждого сервиса (см. выше)
- **Текст в блоках** — белый или светлый
- **Стрелки** — с подписями протоколов (AMQP, TCP) и названиями данных
- **Иконки** в блоках приветствуются
- **Легенда** внизу: протоколы (AMQP — оранжевая стрелка, TCP — серая стрелка)
- Заголовок схемы: «IoT Demo — Умный дом» и подзаголовок «6 сервисов Nubes Cloud»
---
## Дополнительно
Можно добавить разделительную линию (или фон) показывающую, что все сервисы внутри Kubernetes-кластера, а наружу торчит только Dashboard (HTTP/HTTPS) и Producer/Consumer (для health-check).
---
## Формат
Сгенерируй **SVG** или **PNG**, который можно вставить в README.md.
@@ -0,0 +1,41 @@
# Промпт для Gemini — ФИНАЛЬНАЯ ПРАВКА
У тебя уже есть схема. В ней **две ошибки**, исправь только их:
## Ошибка 1: Dashboard соединён с MongoDB — УБРАТЬ
Никакой связи между Dashboard и MongoDB нет. Dashboard читает ТОЛЬКО Redis. Убери эту стрелку.
## Ошибка 2: Нет связи Consumer → MongoDB
Consumer пишет в MongoDB (insertOne). Добавь стрелку Consumer → MongoDB с подписью "TCP · insertOne · архив 7 дн".
---
## Правильные связи (ВСЕ, ничего лишнего):
```
Producer ──AMQP──→ RabbitMQ ──AMQP──→ Consumer
┌───────────┼───────────┐
│ TCP │ TCP │ TCP
▼ ▼ ▼
Redis MongoDB Redis
(HSET/ZADD) (insertOne) (HINCRBY)
│ TCP (только чтение)
Dashboard ──HTTP──→ Браузер
```
---
## Что МЕНЯТЬ в имеющейся схеме:
1. **Удали** стрелку между Dashboard и MongoDB (в любую сторону)
2. **Добавь** стрелку Consumer → MongoDB (TCP, insertOne)
3. **Проверь** что Consumer → Redis (TCP, три команды: HSET, HINCRBY, ZADD)
4. **Проверь** что Redis → Dashboard (TCP, только чтение)
5. Всё. Больше ничего не трогай.
Никаких других изменений. Только эти две правки.
+145
View File
@@ -0,0 +1,145 @@
# Как собрать и залить провайдер
## ⛔ Схема версий
**Первая цифра версии жёстко привязана к стенду. НЕ ПУТАТЬ.**
| Стенд | Namespace | Первая цифра | Профиль |
|---|---|---|---|
| **PROD** | `nubes` | `2.*` | `TOOLS/config/prod` |
| **DEV** | `nubes-dev` | `3.*` | `TOOLS/config/dev` |
| **TEST** | `nubes-test` | `5.*` | `TOOLS/config/test` |
## Архитектура конфигурации
```
TOOLS/config/
├── registry.env ← ЕДИНЫЙ реестр (НЕ зависит от стенда)
│ REGISTRY_HOSTNAME = tf-registry.containerk8s.services.ngcloud.ru
│ S3_ENDPOINT = https://s3.msk-1.ngcloud.ru
│ S3_BUCKET = nubes-terraform-registry
├── dev/profile.env ← стенд-специфика
│ NUBES_API_ENDPOINT = ...dev...
│ TOKEN_FILE = secrets/dev.token
│ NAMESPACE = nubes-dev
│ VERSION = 3.x.x
├── test/profile.env
└── prod/profile.env
```
Скрипты подгружают ОБА файла: `registry.env` → общие настройки реестра, `profile.env` → стенд-специфика.
**Чтобы сменить реестр** — правишь только `TOOLS/config/registry.env`.
## Полный пайплайн (3 шага)
### Требования
- Токены в `secrets/{dev,test,prod}.token`
- `secrets/private_key.asc` — GPG-ключ для подписи
- `secrets/.s3cfg_registry` — S3-креды (или `S3_ACCESS_KEY`/`S3_SECRET_KEY` в env)
- `go` установлен
- `mc` (MinIO Client) установлен, алиас `prod-s3`
### Шаг 1 — Сгенерировать YAML из API
```bash
cd ~/tf_provider
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
```
Запрашивает спецификации сервисов из API стенда → пишет YAML в `generated/dev/resources_yaml/`.
### Шаг 2 — Сгенерировать Go-ресурсы и документацию
```bash
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
```
Из YAML генерирует:
- `generated/dev/go/` — Go-код ресурсов
- `generated/dev/docs/` — Markdown-документацию
### Шаг 3 — Собрать и залить в реестр
```bash
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
```
Компилирует (linux/windows/darwin), подписывает GPG, заливает в S3.
### Одной командой (для ленивых)
```bash
./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
```
## Быстрая заливка (без перегенерации YAML/Go)
Если YAML'ы и Go-код уже сгенерированы и не менялись — только шаг 3:
```bash
# DEV
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
# TEST
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test 5.1.17
# PROD
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/prod 2.1.23
```
Креды S3 подхватываются из `secrets/.s3cfg_registry`. Или через env:
```bash
export S3_ACCESS_KEY=...
export S3_SECRET_KEY=...
```
## Структура S3
```
nubes-terraform-registry/
└── tf-registry.containerk8s.services.ngcloud.ru/
└── {namespace}/
└── nubes/
└── {version}/
├── terraform-provider-nubes_{ver}_linux_amd64.zip
├── terraform-provider-nubes_{ver}_windows_amd64.zip
├── terraform-provider-nubes_{ver}_darwin_amd64.zip
├── terraform-provider-nubes_{ver}_SHA256SUMS
└── terraform-provider-nubes_{ver}_SHA256SUMS.sig
```
## Проверка после заливки
```bash
# DEV
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-dev/nubes/versions | python3 -m json.tool
# TEST
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-test/nubes/versions | python3 -m json.tool
# PROD
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes/nubes/versions | python3 -m json.tool
```
## Актуальные версии
Файл [`VERSIONS.md`](VERSIONS.md) — единственный источник правды. После каждой заливки — обновить.
## Terraform-конфиг пользователя
```hcl
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "3.1.13"
}
}
}
```
+106
View File
@@ -0,0 +1,106 @@
# План: чистка реестра + новая нумерация версий по стендам
> Для Flash. Цель — убрать ВСЕ старые залитые версии (легаси) и ввести единый
> принцип нумерации, чтобы старое (5.1.17 и т.п.) больше нигде не всплывало.
## Новый принцип нумерации (ЗАФИКСИРОВАТЬ)
| Стенд | 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.1`) — ЛЕГАСИ.
> Никогда больше не использовать.
## Текущее состояние в S3 (нужно УДАЛИТЬ ВСЁ)
Бакет `nubes-terraform-registry`, префикс `tf-registry.containerk8s.services.ngcloud.ru/<ns>/nubes/`:
- `nubes-dev`: `3.0.2 3.0.3 3.0.4 3.0.5 3.0.6`
- `nubes` (prod): `2.0.2 2.0.3 2.0.5 2.0.6`
- `nubes-test`: `0.0.1 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.1.17`
## Шаг 1 — Удалить все залитые версии из S3
Креды на запись: subuser `super` аккаунта `1112_terraform`,
передаются через переменные окружения `S3_ACCESS_KEY` и `S3_SECRET_KEY`.
```bash
mc alias set super-s3 https://s3.msk-1.ngcloud.ru "$S3_ACCESS_KEY" "$S3_SECRET_KEY" --api S3v4
# Удалить ВСЕ версии каждого стенда (рекursивно, включая подфайлы)
mc rm --recursive --force super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes/
mc rm --recursive --force super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes/
mc rm --recursive --force super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes/
```
Проверка после удаления (должно быть пусто):
```bash
mc ls super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes/
mc ls super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes/
mc ls super-s3/nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes/
```
## Шаг 2 — Зафиксировать новую нумерацию в конфигах и док-файлах
Обновить (каждый файл — по новому принципу prod=1.*, dev=2.*, test=3.*):
1. **`VERSIONS.md`** — таблица версий по стендам + схема:
- PROD → `1.*` (первая `1.0.0`)
- DEV → `2.*` (первая `2.0.0`)
- TEST → `3.*` (первая `3.0.0`)
2. **`TOOLS/config/prod/profile.env`** → `VERSION="1.0.0"`
3. **`TOOLS/config/dev/profile.env`** → `VERSION="2.0.0"`
4. **`TOOLS/config/test/profile.env`** → `VERSION="3.0.0"`
5. **`DOCS_PIPELINE/README.md`** — раздел про нумерацию версий (схема выше).
6. **`docs/30_registry/guides/getting-started.md`** — `version = "..."` в примере привести
к актуальной (или оставить как «подставьте нужную», но НЕ 5.0.5 и не 5.1.17).
> ⚠️ Проверить, что в этих файлах нигде не осталось `5.1.17`, `5.0.x`, `3.0.x`
> (кроме новой схемы), `2.0.x` (кроме новой `1.x` для prod). Сделать `grep -rn`.
## Шаг 3 — Перегенерировать провайдеры по новой схеме (01→02→03)
Для каждого стенда (порядок test → dev → prod), версия = первая по новой схеме:
```bash
cd /home/naeel/TF/tf_provider
# TEST → 3.0.0
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/test
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/test
S3_ACCESS_KEY="$S3_ACCESS_KEY" S3_SECRET_KEY="$S3_SECRET_KEY" \
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test 3.0.0
# DEV → 2.0.0
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
S3_ACCESS_KEY="$S3_ACCESS_KEY" S3_SECRET_KEY="$S3_SECRET_KEY" \
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 2.0.0
# PROD → 1.0.0
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/prod
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/prod
S3_ACCESS_KEY="$S3_ACCESS_KEY" S3_SECRET_KEY="$S3_SECRET_KEY" \
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/prod 1.0.0
```
> Документацию (04) НЕ запускать — пользователь пока не просил.
## Предусловия (ПРОВЕРЕНО)
- Go 1.23.1, docker, `mc`, GPG-ключи — на месте.
- Токены API `secrets/{dev,test,prod}.token` — ОБНОВЛЕНЫ 2026-09-03 (валидны, exp 2027-03-02).
- `operation_timeouts.json` в каждом профиле — на месте.
- S3-креды на запись бинарников — `super` subuser (см. выше).
- `registry.env`: hostname `tf-registry.containerk8s.services.ngcloud.ru`, bucket `nubes-terraform-registry`.
## Контроль
После каждого `03` — сообщение `Done. Version X.Y.Z uploaded.`
После всех — в S3 должны остаться ТОЛЬКО:
- `nubes/nubes/1.0.0/`
- `nubes-dev/nubes/2.0.0/`
- `nubes-test/nubes/3.0.0/`
+78
View File
@@ -0,0 +1,78 @@
# План: перегенерация провайдеров всех стендов (версия 0.0.1)
> Для Flash. Генерацию выполняет Flash по этому плану. Документацию НЕ трогать.
## Цель
Перегенерировать код Terraform-провайдера Nubes для 3 стендов из API и залить
бинарники **версии 0.0.1**. Документацию не генерировать и не публиковать.
## Версия
`0.0.1` — для всех трёх стендов.
## Порядок стендов
`test``dev``prod`
## Команды (для каждого стенда, по порядку)
```bash
cd /home/naeel/TF/tf_provider
# стенд = test | dev | prod
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/<стенд>
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/<стенд>
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/<стенд> 0.0.1
```
Полные команды:
```bash
# TEST
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/test
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/test
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test 0.0.1
# 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 0.0.1
# PROD
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/prod
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/prod
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/prod 0.0.1
```
## Что делает каждый шаг
| Шаг | Результат |
|---|---|
| `01` | тянет YAML-спеки ресурсов из API стенда → `generated/<стенд>/resources_yaml/` |
| `02` | YAML → **Go-код** (`generated/<стенд>/go/`) + `.md`-доки (`generated/<стенд>/docs/`, побочный продукт — НЕ публикуем) |
| `03` | кросс-сборка linux/windows/darwin amd64 (`go build -ldflags "-X main.version=0.0.1 -X main.address=tf-registry.containerk8s.services.ngcloud.ru/<ns>/nubes"`) → `SHA256SUMS` + GPG-подпись → `mc cp` в S3 |
## Namespace и target S3 (автоматически из profile.env + registry.env)
| Стенд | Namespace | Бинарники в S3 |
|---|---|---|
| dev | `nubes-dev` | `nubes-terraform-registry/tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes/0.0.1/` |
| test | `nubes-test` | `.../nubes-test/nubes/0.0.1/` |
| prod | `nubes` | `.../nubes/nubes/0.0.1/` |
## Предусловия — ПРОВЕРЕНО, всё готово
- Go 1.23.1, docker 29.1.3, `mc`, GPG (`secrets/private_key.asc`, `public_key.asc`).
- Токены API: `secrets/{dev,test,prod}.token` на месте.
- API-эндпоинты доступны (HTTP 403 без токена — ожидаемо, токен передаёт 01).
- `TOOLS/config/<стенд>/operation_timeouts.json` на месте.
- `registry.env`: `REGISTRY_HOSTNAME=tf-registry.containerk8s.services.ngcloud.ru`, `S3_BUCKET=nubes-terraform-registry`.
## Чего НЕ делать
- НЕ запускать `04_build_and_publish_docs.sh` (документация не нужна сейчас).
- НЕ менять версию `0.0.1` на другую.
- НЕ трогать `.venv`/mkdocs (для 03 не нужны).
## Контроль успеха
Каждый `03` должен завершиться сообщением `Done. Version 0.0.1 uploaded.`
Проверка версии в реестре после заливки (опционально):
```bash
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/<ns>/nubes/versions
```
(должна появиться `0.0.1`).
+3 -2
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes/nubes" source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
version = "2.1.26" version = "2.1.26"
} }
} }
@@ -20,6 +20,7 @@ variable "api_token" {
provider "nubes" { provider "nubes" {
api_token = var.api_token api_token = var.api_token
api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" # ⛔ LEGACY: deck-api ЗАКРЫВАЕТСЯ. Gateway:
api_endpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
} }
+3 -2
View File
@@ -1,7 +1,7 @@
terraform { terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes/nubes" source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
version = "2.1.12" version = "2.1.12"
} }
} }
@@ -15,6 +15,7 @@ variable "api_token" {
provider "nubes" { provider "nubes" {
api_token = var.api_token api_token = var.api_token
api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" # ⛔ LEGACY: deck-api ЗАКРЫВАЕТСЯ. Gateway:
api_endpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
} }
+3 -2
View File
@@ -2,7 +2,7 @@
terraform { terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes/nubes" source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
version = "2.1.10" version = "2.1.10"
} }
} }
@@ -11,7 +11,8 @@ terraform {
// Провайдер Nubes для создания ресурсов. // Провайдер Nubes для создания ресурсов.
provider "nubes" { provider "nubes" {
api_token = var.api_token api_token = var.api_token
api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" # ⛔ LEGACY: deck-api ЗАКРЫВАЕТСЯ. Gateway:
api_endpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
} }
// Токен доступа к Nubes API. // Токен доступа к Nubes API.
-30
View File
@@ -1,30 +0,0 @@
// 2026-03-19
// main.tf — провайдер и переменные для managed RabbitMQ в облаке Nubes.
// Тест-стенд: deck-api-test.ngcloud.ru
terraform {
required_providers {
nubes = {
source = "terra.k8c.ru/nubes/nubes"
version = "5.0.19"
}
}
}
// Токен доступа к Nubes API.
variable "api_token" {
type = string
sensitive = true
description = "Nubes API token"
}
// Реалм, в котором создаётся RabbitMQ (например, k8s-5.ext.nubes.ru).
variable "realm" {
type = string
sensitive = true
description = "resource_realm для nubes_rabbitmq"
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm"
}
-41
View File
@@ -1,41 +0,0 @@
// 2026-03-19
// resources.tf — managed RabbitMQ в облаке Nubes для sless и других сервисов.
resource "nubes_rabbitmq" "rmq" {
// Общий брокер для sless event-triggers и других сервисов облака.
resource_name = "sless-rabbit"
resource_realm = var.realm
resource_instances = 1
resource_memory = 512
resource_c_p_u = 500
resource_disk = 5
need_external_address_master = false
need_external_address_slave = false
}
locals {
// try() — обходим опечатку в API: поле называется как internalConnect, так и inernalConnect.
rmq_host = try(nubes_rabbitmq.rmq.state_out_flat["internalConnect.master"], nubes_rabbitmq.rmq.state_out_flat["inernalConnect.master"])
rmq_user = nubes_rabbitmq.rmq.vault_secrets["adminUser"]
rmq_password = nubes_rabbitmq.rmq.vault_secrets["adminPass"]
}
output "rabbitmq_host" {
value = local.rmq_host
}
output "rabbitmq_user" {
value = local.rmq_user
sensitive = true
}
output "rabbitmq_password" {
value = local.rmq_password
sensitive = true
}
output "rabbitmq_amqp_url" {
// Готовый URL для передачи в оператор sless (env RABBITMQ_URL).
value = "amqp://${local.rmq_user}:${local.rmq_password}@${local.rmq_host}:5672/"
sensitive = true
}
+9 -1
View File
@@ -9,6 +9,14 @@ This repo root contains the 4 scripts for the full provider build pipeline.
3) Build and upload provider binaries for 3 OS targets 3) Build and upload provider binaries for 3 OS targets
4) Build and publish documentation site 4) Build and publish documentation site
## Documentation publishing instructions
The verified documentation generation and publishing pipeline is documented in
[`HISTORY/2026-09-03_docs_upload_pipeline_verified.md`](HISTORY/2026-09-03_docs_upload_pipeline_verified.md).
It covers the generated docs source, MkDocs build, the separate documentation
S3 bucket, VM upload and mirror steps, stand-specific URLs, and the legacy
script that must not be used.
## Prerequisites ## Prerequisites
- Go 1.22+ - Go 1.22+
@@ -25,7 +33,7 @@ S3 environment:
- `S3_SECRET_KEY` - `S3_SECRET_KEY`
Provider naming defaults: Provider naming defaults:
- `REGISTRY_HOSTNAME`: `terra.k8c.ru` - `REGISTRY_HOSTNAME`: `tf-registry.containerk8s.services.ngcloud.ru`
- `NAMESPACE`: `nubes` - `NAMESPACE`: `nubes`
- `NAME`: `nubes` - `NAME`: `nubes`
+61
View File
@@ -0,0 +1,61 @@
# CRUD Test Stand
Три приложения (Lucee / Flask / Node.js) + общая PostgreSQL. Каждое делает CRUD в одной таблице `crud_items`.
## Как скачать
```bash
git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git
cd tf_examples/CRUD
```
## Что где менять
### 1. `terraform.tfvars` — заполнить `terraform.tfvars.example` и переименовать в `terraform.tfvars`
| Параметр | Где брать |
|---|---|
| `api_token` | ЛК → Профиль → Токены → создать «Технический» |
| `realm` | Кластер Kubernetes: `k8s-3-sandbox-nubes-ru` (песочница) или другой |
| `s3_name` | ЛК → S3 → Имя экземпляра |
| `s3_user_uid` | Там же — UUID. **ИЛИ** `s3_name` — любое одно, провайдер сам найдёт пару |
### 2. `locals.tf` — сменить домены
Каждое приложение должно иметь **уникальный** домен. Замени:
```
lucee_domain = "..." # станет <имя>.luceek8s.dev.nubes.ru
flask_domain = "..." # станет <имя>.pythonk8s.dev.nubes.ru
nodejs_domain = "..." # станет <имя>.<суффикс>.dev.nubes.ru
```
### 3. `locals.tf` — git_revision
При изменении кода в репозиториях (`tfluceecrud`, `tfflaskcrud`, `tfnodejscrud`) — обновить `*_git_revision` на новый хеш коммита. Terraform сам сделает redeploy.
## Порядок запуска
При первом создании PG не успевает отдать пароль юзера до того как стартуют приложения — Lucee/Flask/Node.js падают с ошибкой. Проще всего сделать два apply, чем усложнять `.tf` файлы:
```bash
terraform init
terraform apply # 1. создаст PG + пользователя + БД
terraform apply # 2. создаст Lucee + Flask + Node.js (пароль уже в стейте)
```
**Альтернатива:** закомментировать `lucee.tf`, `flask.tf`, `nodejs.tf``terraform apply` → раскомментировать → `terraform apply`.
Дальше — один `terraform apply` при любых изменениях.
## Состав
| Файл | Ресурс |
|---|---|
| `main.tf` | Провайдер (`nubes-test/nubes`) + переменные |
| `postgres.tf` | `nubes_postgres.main_pg` |
| `postgres_user_db.tf` | Пользователь + БД |
| `lucee.tf` | `nubes_lucee.applucee` |
| `flask.tf` | `nubes_flask.appflask` |
| `nodejs.tf` | `nubes_nodejs.appnodejs` |
| `locals.tf` | Все настраиваемые значения |
+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
adopt_existing_on_create = true
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 = "/"
}
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]
}
+88
View File
@@ -0,0 +1,88 @@
# =============================================================================
# locals.tf — все настраиваемые значения модуля CRUD (PG + Lucee + Flask + Node.js)
# Никакого хардкода в ресурсах — всё здесь.
# =============================================================================
locals {
# ═══════════════════════════════════════════════════════════════════════════
# PostgreSQL — общая БД для всех трёх приложений
# ═══════════════════════════════════════════════════════════════════════════
pg_resource_name = "pg4crud2" # имя ресурса в 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_resource_name = "crud-lucee" # имя ресурса в Nubes
lucee_domain = "tflucee" # домен (станет tflucee.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_resource_name = "crud-flask" # имя ресурса в Nubes
flask_domain = "tfflask" # домен (станет tfflask.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_resource_name = "crud-nodejs" # имя ресурса в Nubes
nodejs_domain = "tfnodejs" # домен (станет tfnodejs.<суффикс>.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
adopt_existing_on_create = true
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
}
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-test/nubes"
version = "5.0.5"
}
}
}
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-test.ngcloud.ru/api/v1/svc"
# log_level = "debug" # none | info | debug, default = "none"
}
+49
View File
@@ -0,0 +1,49 @@
# =============================================================================
# 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
adopt_existing_on_create = true
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 = "/"
}
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-пользователя
+65
View File
@@ -0,0 +1,65 @@
# =============================================================================
# Consumer (Node.js) — читает RabbitMQ, сохраняет в Redis + MongoDB
# Использует amqplib для AMQP-подключения к RabbitMQ
# =============================================================================
locals {
# Подключение к RabbitMQ (AMQP, порт 5672)
cons_rmq_host = nubes_rabbitmq.main.state_out_flat["internalConnect"] # K8s-хост
cons_rmq_user = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminUser"]) # логин
cons_rmq_pass = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminPass"]) # пароль
# Подключение к Redis (порт 6379)
cons_rds_host = nubes_redis.main.state_out_flat["internalConnect.master"] # K8s-хост
cons_rds_pass = nonsensitive(nubes_redis.main.vault_secrets["adminPass"]) # пароль
# Подключение к MongoDB (порт 27017) — без аутентификации (vault пустой)
cons_mgo_host = nubes_mongodb.main.state_out_flat["internalConnect"] # K8s-хост
cons_mgo_user = "admin" # дефолтный пользователь
cons_mgo_pass = "" # без пароля
}
resource "nubes_nodejs" "consumer" {
resource_name = local.cons_resource_name # имя в личном кабинете
startup_configuration = {
resource_realm = var.realm # K8s-кластер
}
cluster_configuration = {
cpu = local.cons_cpu # CPU millicores
memory = local.cons_memory # память MB
replicas = local.cons_replicas # количество подов
}
access_configuration = {
domain = local.cons_domain # поддомен для HTTP
}
app_configuration = {
version = "22" # версия Node.js
git_path = local.cons_git_path # Git-репо с кодом
health_path = "/" # health-check эндпоинт
}
git_revision = local.cons_git_revision # коммит для деплоя
operation_timeout = local.cons_timeout # таймаут
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
# Переменные окружения для K8s пода
json_env = jsonencode({
RMQ_HOST = local.cons_rmq_host # хост RabbitMQ
RMQ_PORT = "5672" # порт AMQP
RMQ_USER = local.cons_rmq_user # логин RabbitMQ
RMQ_PASS = local.cons_rmq_pass # пароль RabbitMQ
RMQ_QUEUE = local.rmq_queue # имя очереди
REDIS_HOST = local.cons_rds_host # хост Redis
REDIS_PORT = "6379" # порт Redis
REDIS_PASS = local.cons_rds_pass # пароль Redis
MONGO_URI = "mongodb://${local.cons_mgo_user}:${local.cons_mgo_pass}@${local.cons_mgo_host}:27017/iot?authSource=admin"
MONGO_DB = "iot" # имя базы MongoDB
})
# Ждать создания всех трёх сервисов
depends_on = [nubes_rabbitmq.main, nubes_redis.main, nubes_mongodb.main]
}
+47
View File
@@ -0,0 +1,47 @@
# =============================================================================
# Dashboard (Node.js + Chart.js) — читает Redis, показывает графики
# API: /api/latest, /api/counters, /api/recent
# =============================================================================
locals {
# Подключение к Redis (только чтение)
dash_rds_host = nubes_redis.main.state_out_flat["internalConnect.master"] # K8s-хост Redis
dash_rds_pass = nonsensitive(nubes_redis.main.vault_secrets["adminPass"]) # пароль Redis
}
resource "nubes_nodejs" "dashboard" {
resource_name = local.dash_resource_name # имя в личном кабинете
startup_configuration = {
resource_realm = var.realm # K8s-кластер
}
cluster_configuration = {
cpu = local.dash_cpu # CPU millicores
memory = local.dash_memory # память MB
replicas = local.dash_replicas # количество подов
}
access_configuration = {
domain = local.dash_domain # поддомен -> iotdash22.nodejsk8s.dev.nubes.ru
}
app_configuration = {
version = "22" # версия Node.js
git_path = local.dash_git_path # Git-репо с кодом
health_path = "/" # health-check
}
git_revision = local.dash_git_revision # коммит для деплоя
operation_timeout = local.dash_timeout # таймаут
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
# Переменные окружения
json_env = jsonencode({
REDIS_HOST = local.dash_rds_host # хост Redis
REDIS_PORT = "6379" # порт Redis
REDIS_PASS = local.dash_rds_pass # пароль Redis
})
depends_on = [nubes_redis.main] # ждать создания Redis
}
+84
View File
@@ -0,0 +1,84 @@
# =============================================================================
# Инфраструктурные сервисы (создаются первым прогоном apply)
# RabbitMQ (очередь) + Redis (кэш) + MongoDB (архив)
# =============================================================================
# --- RabbitMQ: очередь сообщений (сервис 93) ---
# state_out_flat["internalConnect"] = K8s-хост (service.cluster.local)
# vault_secrets["adminUser"] / ["adminPass"] = логин/пароль (прямые строки, не JSON)
resource "nubes_rabbitmq" "main" {
resource_name = local.rmq_resource_name # имя в личном кабинете
startup_configuration = { # на каком K8s-кластере развернуть
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.rmq_cpu # CPU millicores
memory = local.rmq_memory # память MB
disk = local.rmq_disk # диск GB
replicas = local.rmq_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # внутренняя сеть K8s
}
operation_timeout = local.rmq_timeout # макс. время ожидания
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
# --- Redis: кэш дашборда (сервис 91) ---
# state_out_flat["internalConnect.master"] = хост
# vault_secrets["adminPass"] = пароль (прямая строка, не JSON)
resource "nubes_redis" "main" {
resource_name = local.rds_resource_name # имя в личном кабинете (rds = ReDiS)
startup_configuration = { # на каком K8s-кластере
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.rds_cpu # CPU millicores
memory = local.rds_memory # память MB
disk = local.rds_disk # диск GB
replicas = local.rds_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # разрешить всю внутреннюю сеть K8s
slave_ip_space = "no-needed" # slave-нода не нужна
slave_access_list = jsonencode([]) # slave-нода не нужна
}
operation_timeout = local.rds_timeout # макс. время ожидания операций
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
# --- MongoDB: архив событий (сервис 92) ---
# state_out_flat["internalConnect"] = хост
# vault_secrets — пустой (нет дефолтных пользователей), подключаемся без пароля
resource "nubes_mongodb" "main" {
resource_name = local.mgo_resource_name # имя в личном кабинете (mgo = MonGO)
startup_configuration = { # на каком K8s-кластере
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.mgo_cpu # CPU millicores
memory = local.mgo_memory # память MB
disk = local.mgo_disk # диск GB
replicas = local.mgo_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # разрешить всю внутреннюю сеть K8s
}
operation_timeout = local.mgo_timeout # макс. время ожидания
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
+61
View File
@@ -0,0 +1,61 @@
# =============================================================================
# Общие переменные для всех ресурсов демо-стенда IoT
# Пайплайн: Producer -> RabbitMQ -> Consumer -> Redis+MongoDB -> Dashboard
# Все значения вынесены сюда — в ресурсах хардкода нет
# =============================================================================
locals {
# --- RabbitMQ: очередь сообщений (сервис 93) ---
rmq_resource_name = "iot-rmq" # имя инстанса в личном кабинете Nubes
rmq_cpu = 300 # CPU в millicores (1000 = 1 ядро)
rmq_memory = 512 # память в MB
rmq_disk = 5 # диск в GB
rmq_replicas = 1 # количество реплик (нод)
rmq_timeout = "11m" # таймаут ожидания операций create/modify
rmq_queue = "iot-events" # имя очереди: producer кладёт, consumer забирает
# --- Redis: кэш дашборда (сервис 91) ---
rds_resource_name = "iot-rds" # имя инстанса (rds = ReDiS)
rds_cpu = 300 # CPU millicores
rds_memory = 512 # память MB
rds_disk = 5 # диск GB
rds_replicas = 1 # реплик (нод)
rds_timeout = "11m" # таймаут операций
# --- MongoDB: архив всех событий (сервис 92) ---
mgo_resource_name = "iot-mgo" # имя инстанса (mgo = MonGO)
mgo_cpu = 300 # CPU millicores
mgo_memory = 512 # память MB
mgo_disk = 5 # диск GB
mgo_replicas = 1 # реплик (нод)
mgo_timeout = "11m" # таймаут операций
# --- Producer (Node.js): генерирует IoT-события -> RabbitMQ ---
prod_resource_name = "iot-prod" # имя инстанса (prod = PRODucer)
prod_domain = "iotprod22" # поддомен (станет iotprod22.nodejsk8s.dev.nubes.ru)
prod_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-producer.git" # Git-репо с кодом producer
prod_git_revision = "0bf1847" # хеш коммита (менять для редеплоя нового кода)
prod_cpu = 300 # CPU millicores
prod_memory = 256 # память MB
prod_replicas = 1 # количество подов
prod_timeout = "11m" # таймаут операций
# --- Consumer (Node.js): RabbitMQ -> Redis + MongoDB ---
cons_resource_name = "iot-cons4" # имя инстанса (cons = CONSumer, версия 4)
cons_domain = "iotcons23n" # поддомен -> iotcons23n.nodejsk8s.dev.nubes.ru
cons_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-consumer.git" # Git-репо с кодом consumer
cons_git_revision = "03fefc0" # хеш коммита (менять для редеплоя)
cons_cpu = 300 # CPU millicores
cons_memory = 256 # память MB
cons_replicas = 1 # количество подов
cons_timeout = "11m" # таймаут операций
# --- Dashboard (Node.js + Chart.js): Redis -> графики ---
dash_resource_name = "iot-dash2" # имя инстанса (dash = DASHboard, версия 2)
dash_domain = "iotdash23" # поддомен -> iotdash23.nodejsk8s.dev.nubes.ru
dash_git_path = "https://gitea.services.ngcloud.ru/Nail/tf-iot-dashboard.git" # Git-репо с кодом dashboard
dash_git_revision = "bc6f43d" # хеш коммита (менять для редеплоя)
dash_cpu = 300 # CPU millicores
dash_memory = 256 # память MB
dash_replicas = 1 # количество подов
dash_timeout = "11m" # таймаут операций
}
+36
View File
@@ -0,0 +1,36 @@
# =============================================================================
# Terraform-конфиг: IoT Demo стенд
# 6 сервисов Nubes Cloud: RabbitMQ, Redis, MongoDB, 3x Node.js
# Пайплайн: Producer -> RabbitMQ -> Consumer -> Redis+MongoDB -> Dashboard
# =============================================================================
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" # реестр провайдера (test)
version = "5.1.16" # версия провайдера Nubes
}
}
}
# API-токен для доступа к Nubes (из secrets/test.token)
variable "api_token" {
type = string
sensitive = true # не показывать в логах/плане
}
# Кластер Kubernetes для развёртывания (k8s-4-sandbox-nubes-ru)
variable "realm" {
type = string
}
# S3-пользователь для бэкапов (не используется в этом демо)
variable "s3_name" {
type = string
}
# Конфигурация провайдера Nubes
provider "nubes" {
api_token = var.api_token
api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" # тестовый API
}

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