Compare commits
182
Commits
api-gateway
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a25ad8fc9 | ||
|
|
a222a0dace | ||
|
|
bba6b47dc2 | ||
|
|
ccb458a167 | ||
|
|
aa0f7f6402 | ||
|
|
6bf514e03a | ||
|
|
8d5bbd368d | ||
|
|
423c74d3f1 | ||
|
|
085a310720 | ||
|
|
b76e0d1086 | ||
|
|
9ebe5b19d6 | ||
|
|
62d8d7b45d | ||
|
|
02b7d7b701 | ||
|
|
9090488731 | ||
|
|
9e02b696ba | ||
|
|
9fd7334a60 | ||
|
|
72a8a491c6 | ||
|
|
dc469c6dce | ||
|
|
211143980c | ||
|
|
2414647337 | ||
|
|
97fd77c2e9 | ||
|
|
b3342bc0c5 | ||
|
|
ed568a867a | ||
|
|
86871498a2 | ||
|
|
75c868e0b5 | ||
|
|
d61c8d5cb7 | ||
|
|
05694a3446 | ||
|
|
657157527b | ||
|
|
a21cbd9cd2 | ||
|
|
5a718a3ec2 | ||
|
|
25047c7607 | ||
|
|
af5431d6db | ||
|
|
dc5bac376c | ||
|
|
b97edf9f57 | ||
|
|
b8c1508140 | ||
|
|
2ee1eb98e8 | ||
|
|
c77e0327d0 | ||
|
|
04d9c4a191 | ||
|
|
195f153860 | ||
|
|
21b92f4631 | ||
|
|
35aa50f76d | ||
|
|
8d53a4c499 | ||
|
|
2af2d2af16 | ||
|
|
5f81e4a664 | ||
|
|
f6494d038e | ||
|
|
40a95cc4a6 | ||
|
|
c9a881aa84 | ||
|
|
ac2ce0ea53 | ||
|
|
90c5418bb1 | ||
|
|
5ce7853817 | ||
|
|
52bd80ee36 | ||
|
|
fda819cd81 | ||
|
|
b6b25f7548 | ||
|
|
bf36c5b42c | ||
|
|
86b44cc141 | ||
|
|
8b60145506 | ||
|
|
3a4eddbfb9 | ||
|
|
cfd1235d04 | ||
|
|
260766e0b2 | ||
|
|
621d8c2738 | ||
|
|
96594fae24 | ||
|
|
08ffeedd66 | ||
|
|
f41446de02 | ||
|
|
16c7e05b4b | ||
|
|
18f954b7ab | ||
|
|
46e9169300 | ||
|
|
5cec3ed55b | ||
|
|
5f502bcf3d | ||
|
|
2b5964799a | ||
|
|
8b888666d7 | ||
|
|
b72c1e0517 | ||
|
|
25011eb783 | ||
|
|
3dac50b78a | ||
|
|
837a663367 | ||
|
|
a37fc22bcd | ||
|
|
0adb379a54 | ||
|
|
55d4109eee | ||
|
|
e434a44fb5 | ||
|
|
336e6e3795 | ||
|
|
399745c177 | ||
|
|
f0e01dad6e | ||
|
|
5d338bbd02 | ||
|
|
b093bff148 | ||
|
|
3d831b16a0 | ||
|
|
1d1258a83b | ||
|
|
9ee16ce371 | ||
|
|
052c412466 | ||
|
|
5ec3c8aceb | ||
|
|
fb6df52a3b | ||
|
|
89163e9609 | ||
|
|
eb36731d3e | ||
|
|
ecace0311b | ||
|
|
a4a3f8e63c | ||
|
|
4ea4cc82b0 | ||
|
|
6dfe156b39 | ||
|
|
664ab51b0d | ||
|
|
f56c0c0cf4 | ||
|
|
33c5844ede | ||
|
|
6185a47a0e | ||
|
|
b1e7c6cce9 | ||
|
|
678f24346e | ||
|
|
4465180616 | ||
|
|
50778683a9 | ||
|
|
3a24690c3f | ||
|
|
b05969aad0 | ||
|
|
4c5a61cbbf | ||
|
|
757aa2de98 | ||
|
|
d05456c4f9 | ||
|
|
53edffd255 | ||
|
|
67ce3e4ab9 | ||
|
|
4aa1c83bea | ||
|
|
b137c93869 | ||
|
|
377542eb45 | ||
|
|
9fb23af151 | ||
|
|
1208104f77 | ||
|
|
a99fac5a86 | ||
|
|
196d55a945 | ||
|
|
6660008af1 | ||
|
|
4d14568852 | ||
|
|
38d278427e | ||
|
|
dd61b1b08d | ||
|
|
65b78f2a78 | ||
|
|
0a027c2ebf | ||
|
|
35792091bd | ||
|
|
b6f5b481af | ||
|
|
92cb40e476 | ||
|
|
7edea3f472 | ||
|
|
6f0e2a9b81 | ||
|
|
cfb546651b | ||
|
|
97bd580626 | ||
|
|
84a3513f02 | ||
|
|
8e2c3845f1 | ||
|
|
40f2714df6 | ||
|
|
b770d55d4c | ||
|
|
b0555d625a | ||
|
|
ab7ab7abf2 | ||
|
|
0f82e08e81 | ||
|
|
284f5b2c4f | ||
|
|
ad3b986d89 | ||
|
|
1276143973 | ||
|
|
d29f0fa98d | ||
|
|
53dbcbe91c | ||
|
|
e8f78155d8 | ||
|
|
3476feaf61 | ||
|
|
f4127a3755 | ||
|
|
ea3e53849d | ||
|
|
6beac52e92 | ||
|
|
073c37b549 | ||
|
|
e822009cfb | ||
|
|
205fd02c39 | ||
|
|
dc9134e515 | ||
|
|
d602212236 | ||
|
|
cf8cccc199 | ||
|
|
5dbb2acda4 | ||
|
|
82f639dc31 | ||
|
|
80aa0dc814 | ||
|
|
58675ccff2 | ||
|
|
6d69ae2c7c | ||
|
|
f9ae51d854 | ||
|
|
50dfa3c31d | ||
|
|
b7a0a4bfe5 | ||
|
|
12645b0a02 | ||
|
|
d7e61abf2b | ||
|
|
dca79d3e16 | ||
|
|
6da49113a1 | ||
|
|
29d0de4658 | ||
|
|
ae64ee5931 | ||
|
|
e097d0dae9 | ||
|
|
3d22758385 | ||
|
|
ef72486bd7 | ||
|
|
6929562fc9 | ||
|
|
f31bedfe05 | ||
|
|
d7e75c78b4 | ||
|
|
bfb427671b | ||
|
|
c5e2b77ab5 | ||
|
|
c197fb8b0c | ||
|
|
af65106a05 | ||
|
|
a0706c50fe | ||
|
|
3591ea227a | ||
|
|
ce92ccb7de | ||
|
|
ec1d933221 | ||
|
|
f5547c0465 |
+17
-40
@@ -1,34 +1,5 @@
|
||||
# Правила работы агента
|
||||
|
||||
## Файловая система
|
||||
|
||||
`~/remote_dev/` (локально) примонтирован через sshfs к `~/terra/` на ВМ — **одна ФС**.
|
||||
Файлы, сохранённые локально, мгновенно видны на ВМ. SCP не нужен.
|
||||
|
||||
Монтирование может слетать. Признак: файлы рассинхронизированы.
|
||||
|
||||
```bash
|
||||
# Размонтировать
|
||||
fusermount -u ~/remote_dev
|
||||
# Если завис: sudo umount -l /home/naeel/remote_dev
|
||||
|
||||
# Примонтировать
|
||||
sshfs naeel@5.172.178.213:/home/naeel/terra ~/remote_dev \
|
||||
-o cache=no -o no_readahead -o reconnect \
|
||||
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
|
||||
-o IdentityFile=~/.ssh/naeel_vm_id_ed25519
|
||||
```
|
||||
|
||||
## SSH
|
||||
|
||||
Все команды — только через SSH на ВМ. Локально — только читать и редактировать файлы.
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
|
||||
```
|
||||
|
||||
Запрещено локально: `go`, `docker`, `kubectl`, `helm`, `terraform`, `curl/wget`, `git push/pull`, любые скрипты проекта.
|
||||
|
||||
## Документация
|
||||
|
||||
- `doc/thinking/` — лог рассуждений агента (обязательно)
|
||||
@@ -37,20 +8,26 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=
|
||||
|
||||
## Git
|
||||
|
||||
Коммитить и пушить через SSH после каждого завершённого этапа.
|
||||
|
||||
Версионирование тегами: `vMAJOR.MINOR.PATCH`
|
||||
- Patch — любое изменение кода
|
||||
- Minor — новая фича / компонент
|
||||
- Major — breaking change
|
||||
|
||||
```bash
|
||||
git tag vX.Y.Z && git push origin vX.Y.Z
|
||||
```
|
||||
- Коммитить и пушить после каждого завершённого этапа
|
||||
- **Синхронизация: `git pull` (НЕ `reset --hard`).** `reset --hard` стирает локальные правки. Для синхронизации после пуша — только `git pull`.
|
||||
- **⛔ .gitignore — ДО генерации:** генераторы по сервисам (`yaml-generator`, `resource-generator`, `docs-generator` в `TOOLS/bin/`) создают файлы в папках-приёмниках. Эти папки-приёмники — СРАЗУ добавлять в `.gitignore`, ДО запуска генераторов. НЕ после. Что именно: `resources_yaml/`, `resources_gen/`, `generated/`, `docs/30_registry/resources/` и любые новые.
|
||||
- **Аудит при старте:** перед началом работы — `git ls-files` на сгенерированное, сверить с `.gitignore`
|
||||
- **⛔ Симлинки запрещены.** Никаких `ln -s`. Разные стенды — разные файлы. Общий код — через импорты, не через симлинки.
|
||||
|
||||
## Поведение агента
|
||||
|
||||
- **⛔ СНАЧАЛА АНАЛИЗ — ПОТОМ ДЕЙСТВИЕ.** Перед ЛЮБЫМ действием: прочитать связанные файлы, понять архитектуру, проверить зависимости. НЕ запускать скрипты/сборки не поняв что они делают и что им нужно.
|
||||
- Не трогать рабочий код без явного указания
|
||||
- Не делать ничего сверх того, о чём явно попросили
|
||||
- Деструктивные операции (`kubectl delete`, `rm -rf`, `terraform destroy` и др.) — только после явного подтверждения с указанием конкретных объектов
|
||||
- Отвечать кратко, без вступлений, извинений, благодарностей и прочей воды
|
||||
- **⛔ НИКОГДА без прямого приказа:** `git reset --hard`, `git push --force`, `rm -rf`, нестандартные флаги, обход скриптов — только после явного «делай» с указанием конкретной команды.
|
||||
|
||||
## Билд провайдера (TEST)
|
||||
|
||||
Порядок на ВМ (`5.172.178.213`):
|
||||
```bash
|
||||
cd /home/naeel/tf_provider
|
||||
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test
|
||||
```
|
||||
Скрипт сам: копирует generated Go + YAML во временную папку, собирает linux/windows/darwin, подписывает GPG, заливает в S3.
|
||||
НЕ вызывать `build-provider.sh` напрямую — он не читает `profile.env`.
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
env:
|
||||
NAMESPACE: nubes
|
||||
NAME: nubes
|
||||
DEFAULT_HOST: terra.k8c.ru
|
||||
DEFAULT_HOST: tf-registry.containerk8s.services.ngcloud.ru
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
||||
+23
@@ -8,9 +8,28 @@
|
||||
# === Generated files (NOT code — regenerate from API) ===
|
||||
provider/resources_yaml/
|
||||
provider/internal/resources_gen/
|
||||
|
||||
# === External repos (managed separately) ===
|
||||
tfluceecrud/
|
||||
tfflaskcrud/
|
||||
tfnodejscrud/
|
||||
tf_examples/
|
||||
gateway/
|
||||
generated/
|
||||
TOOLS/bin/
|
||||
universal_rebuild/docs-gen/
|
||||
artifacts/
|
||||
provider/artifacts/
|
||||
provider/generated/
|
||||
|
||||
# === Binaries (compiled from source) ===
|
||||
*.bin
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# === Build artifacts (generated by devops scripts) ===
|
||||
devops/profiles/*/generated/
|
||||
@@ -54,7 +73,9 @@ secrets/id_ed25519.txt
|
||||
|
||||
# === MkDocs ===
|
||||
site/
|
||||
site_test/
|
||||
.mkdocs.tmp.yml
|
||||
.mkdocs.docs_test.yml
|
||||
|
||||
# === Generated universal_rebuild artifacts ===
|
||||
universal_rebuild/universal_rebuild/
|
||||
@@ -91,3 +112,5 @@ universal_rebuild/service_params_gen
|
||||
terraform-provider-mycloud
|
||||
artifacts/api-meta/*/errors.log
|
||||
TOOLS/bin/
|
||||
TOOLS/docs-generator/bin/
|
||||
docs/30_registry/resources/
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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-экземпляра
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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 = "*"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 = "[]" # пусто = доступ всем
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
Executable
+19
@@ -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."
|
||||
@@ -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`).
|
||||
@@ -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
|
||||
```
|
||||
Executable
+34
@@ -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}/"
|
||||
@@ -0,0 +1,28 @@
|
||||
# FIYR_MGU — Материалы для поступления в магистратуру ФИЯР МГУ
|
||||
|
||||
Собрано по официальным источникам (http://www.ffl.msu.ru) и демо-вариантам вступительных испытаний по направлению «Лингвистика» (иностранный язык).
|
||||
|
||||
## Содержание
|
||||
|
||||
| Файл | Описание |
|
||||
|---|---|
|
||||
| `Структурированный_материал_для_подготовки.md` | **Главный документ**: формат экзамена, анализ реальных текстов, алгоритм написания реферата, клише, темы, план подготовки |
|
||||
| `материалы/ANGL_2021.docx` | Демо-вариант, английский, 2021 (реферат по семиотике) |
|
||||
| `материалы/ANGL_2023.docx` | Демо-вариант, английский, 2023 (реферат по концептуальной метафоре) |
|
||||
| `материалы/ITAL_2021.docx` | Демо-вариант, итальянский, 2021 |
|
||||
| `материалы/Английский_2018.pdf` | Английский, 2018 (старый формат — эссе + тест) |
|
||||
| `материалы/Английский_2019.pdf` | Английский, 2019 (старый формат) |
|
||||
| `материалы/Test_Cultura_2018.pdf` | Культурология, 2018 (демо) |
|
||||
| `материалы/Test_RegRos_2018.pdf` | Регионоведение России, 2018 (демо) |
|
||||
|
||||
## Ключевые официальные ссылки
|
||||
|
||||
- Магистратура ФИЯР: http://www.ffl.msu.ru/study/master
|
||||
- Раздел «Поступление»: http://www.ffl.msu.ru/apply/
|
||||
- ЦПК МГУ: http://www.cpk.msu.ru
|
||||
- Правила приёма МГУ 2026: https://cpk.msu.ru/files/2026/rules.pdf
|
||||
|
||||
## Контакты приёмной комиссии
|
||||
|
||||
- +7 (925) 108-85-68, +7 (495) 932-88-66 (с 20 июня по 25 августа)
|
||||
- pk.ffl@org.msu.ru
|
||||
@@ -0,0 +1,220 @@
|
||||
# Подготовка к вступительному испытанию в магистратуру ФИЯР МГУ
|
||||
## Направление «Лингвистика» (иностранный язык: английский)
|
||||
|
||||
> Составлено на основе официальных демо-вариантов ФИЯР МГУ за доступные годы
|
||||
> (2018, 2019, 2021, 2023 — английский; 2021 — итальянский; 2018, 2019 — культурология,
|
||||
> регионоведение). Дата подготовки: 2026-08-07.
|
||||
|
||||
---
|
||||
|
||||
## 1. Общая схема поступления (приём 2026)
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Направление | 45.03.02 «Лингвистика» |
|
||||
| Вступительное испытание | **лингвистика на иностранном языке** (англ./франц./нем./исп./ит.), письменно |
|
||||
| Минимальный балл | 40 (устанавливается МГУ) |
|
||||
| Подача документов | с 20 июня по 10 августа 2026 |
|
||||
| Согласие на зачисление | до 24 августа 2026, 12:00 |
|
||||
| Ссылка на экзамен | приходит на почту личного кабинета на Госуслугах, не позднее чем за сутки |
|
||||
| Контакт приёмной комиссии | +7 (925) 108-85-68, pk.ffl@org.msu.ru |
|
||||
|
||||
Полезные ссылки:
|
||||
- Страница магистратуры: http://www.ffl.msu.ru/study/master
|
||||
- Регламент ВИ 2026 (PDF на странице магистратуры)
|
||||
- Презентация магистерских программ (PDF там же)
|
||||
- Правила приёма МГУ 2026: https://cpk.msu.ru/files/2026/rules.pdf
|
||||
|
||||
---
|
||||
|
||||
## 2. Формат вступительного испытания по английскому
|
||||
|
||||
> ⚠️ Важно: формат **изменился**. В 2018–2019 экзамен состоял из 2 частей
|
||||
> (эссе по культурологическим темам + лексико-грамматический тест с 10 пропусками).
|
||||
> Начиная с 2021 г. (подтверждено демо 2021 и 2023) экзамен — **один реферат по тексту**.
|
||||
|
||||
### Современный формат (2021, 2023) — ОДНО задание
|
||||
|
||||
**Задание:** «Прочитайте текст и изложите его содержание **в научном стиле своими словами**, соблюдая классическую структуру: введение, основная часть, заключение. Напишите **реферат прочитанного текста в количестве 500 слов**».
|
||||
|
||||
**Реферат должен содержать два смысловых блока:**
|
||||
|
||||
1. **Объективная / авторская информация:**
|
||||
- проблематика, обсуждаемая автором (предмет и объект исследования);
|
||||
- выбранный способ её обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.);
|
||||
- методы исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный/качественный анализ; сравнительно-сопоставительный анализ и пр.).
|
||||
|
||||
2. **Интерпретация прочитанного (аргументация выводов обязательна):**
|
||||
- текст/автор подтверждает или опровергает, обновляет/расширяет/дополняет прочитанное ранее;
|
||||
- в чём текст вызывает недоверие (изложено противоречиво/бездоказательно/без примеров и пр.) и/или одобрение (привести аргументы).
|
||||
|
||||
**Структура работы:**
|
||||
- Введение — жанр текста, смысл названия, общая тема.
|
||||
- Основная часть — два указанных блока.
|
||||
- Заключение — место и значение исследования данной темы в современной лингвистике.
|
||||
|
||||
**Жёсткие требования:**
|
||||
- Объём ≈ **500 слов**.
|
||||
- **Копирование 4 слов подряд** из исходного текста или других источников = некорректное цитирование (плагиат). При обнаружении работа оценивается **только на предмет языковой грамотности** — то есть фактически завалена по содержанию.
|
||||
- Научный стиль: логическая последовательность, текстовая связность, точность, сжатость, однозначность; выбор лексики и грамматических структур формального/академического стиля.
|
||||
|
||||
---
|
||||
|
||||
## 3. Анализ реальных текстов демо-вариантов
|
||||
|
||||
### 3.1. Английский 2021 — «A Brief Introduction to Semiotics»
|
||||
- **Автор:** Barbara Brownie, *Semiotics of Typography* (2009).
|
||||
- **Тема:** введение в семиотику — науку о знаках.
|
||||
- **Ключевое содержание:**
|
||||
- Связь семиотики со структурализмом.
|
||||
- История изучения знаков: Гиппократ, Аристотель → Соссюр, Пирс → Барт, Эко.
|
||||
- Модель знака Соссюра: означающее (signifier) / означаемое (signified), произвольность связи, денотация/коннотация.
|
||||
- Модель Пирса: репрезентамен / объект / интерпретант; иконы, индексы, символы.
|
||||
- langue / parole, синтагматические и парадигматические отношения.
|
||||
- Якорение (anchorage), коды, первичные коды и субкоды, «аберрантное декодирование» (У. Эко).
|
||||
- **Примечание:** текст содержит все понятия семиотики — идеален для понимания логики «автор → теория → примеры → следствия».
|
||||
|
||||
### 3.2. Английский 2023 — «Some Consequences for Theories of Conceptual Structure»
|
||||
- **Авторы:** George Lakoff & Mark Johnson, *Metaphors We Live By* (2004).
|
||||
- **Тема:** теория концептуальной метафоры (метафора как способ мышления и категоризации опыта).
|
||||
- **Ключевое содержание:**
|
||||
- Концептуальная система человека в значительной степени метафорически структурирована.
|
||||
- Сравнение трёх теорий: **абстракционизм**, **омонимия** (сильная/слабая), **теория метафорических концептов** авторов.
|
||||
- Примеры: AN ARGUMENT IS A BUILDING, LOVE IS A JOURNEY, IDEAS ARE FOOD, HAPPY IS UP.
|
||||
- Внутренняя и внешняя систематичность, укоренённость (grounding), частичное метафорическое структурирование.
|
||||
- Критика абстракционизма и сильной омонимии; слабая омонимия ближе к позиции авторов, но не объясняет укоренённость в опыте.
|
||||
- **Примечание:** текст — фрагмент научного рассуждения с последовательным опровержением альтернативных теорий. Хорош для демонстрации «логики аргументации».
|
||||
|
||||
### 3.3. Старые тексты (2018, 2019) — для понимания прошлого формата
|
||||
- **2018:** темы эссе — цивилизационный подход (Шпенглер/Тойнби); восприятие западноевропейского искусства русской культурой. Тест — статья о художнике Уистлере.
|
||||
- **2019:** темы — психоаналитическое направление (Фрейд/Юнг); культурные преобразования петровской эпохи. Тест — письмо Э. Золя о деле Дрейфуса.
|
||||
|
||||
---
|
||||
|
||||
## 4. Как писать реферат (пошаговый алгоритм)
|
||||
|
||||
### Шаг 1. Быстрое понимание текста (5–7 мин)
|
||||
- Определите жанр (научный фрагмент, очерк, статья, отрывок монографии).
|
||||
- Найдите главный тезис автора (обычно в 1–2 абзацах).
|
||||
- Выделите структуру: что вводят, что доказывают, какие примеры приводят, какой вывод делают.
|
||||
|
||||
### Шаг 2. Составление «скелета» реферата (план)
|
||||
- **Introduction:** жанр + название + общая тема + проблематика.
|
||||
- **Body (блок 1):** предмет/объект, метод/способ рассуждения, ключевые положения.
|
||||
- **Body (блок 2):** критическая оценка — что подтверждается/опровергается, что вызывает доверие/недоверие, с аргументами.
|
||||
- **Conclusion:** значение темы для современной лингвистики.
|
||||
|
||||
### Шаг 3. Написание своими словами
|
||||
- НЕ копируйте пассажи из текста (даже 4 слова подряд = плагиат).
|
||||
- Пересказывайте идеи, перефразируйте термины и конструкции.
|
||||
- Соблюдайте научный стиль.
|
||||
|
||||
### Шаг 4. Проверка (~5 мин оставшихся)
|
||||
- Подсчёт слов (~500).
|
||||
- Логические связки между абзацами.
|
||||
- Отсутствие явных заимствований.
|
||||
|
||||
### Тайминг (на весь экзамен):
|
||||
- Чтение и понимание: ~15–20 мин
|
||||
- План: ~10 мин
|
||||
- Написание: ~60–70 мин
|
||||
- Проверка/подсчёт: ~10 мин
|
||||
|
||||
---
|
||||
|
||||
## 5. Клише и научный стиль (шпаргалка)
|
||||
|
||||
### Введение
|
||||
- *The text under analysis is an extract from …*
|
||||
- *The title of the text is …*
|
||||
- *The text deals with / focuses on / is devoted to the problem of …*
|
||||
- *The author addresses the issue of …*
|
||||
- *The subject matter of the text is …*
|
||||
|
||||
### Основная часть (объективная информация)
|
||||
- *The author argues / claims / maintains / points out that …*
|
||||
- *The problem is approached through … (analysis, comparison, critical review)*
|
||||
- *The author employs such methods as … (deduction, induction, classification, typology)*
|
||||
- *Particular attention is paid to …*
|
||||
- *The author illustrates the point with the example of …*
|
||||
- *According to the author, … / As the author states, …*
|
||||
|
||||
### Основная часть (интерпретация/оценка)
|
||||
- *The ideas presented here are consistent with / contradict …*
|
||||
- *The author’s argument is convincing because …*
|
||||
- *However, the reasoning appears somewhat inconsistent since …*
|
||||
- *The text is well supported by examples, although some claims lack evidence.*
|
||||
- *Personally, I find the author’s position on … convincing / debatable.*
|
||||
|
||||
### Заключение
|
||||
- *To sum up / In conclusion, …*
|
||||
- *The study is of considerable importance for modern linguistics because …*
|
||||
- *The issues raised open new perspectives for further research in …*
|
||||
|
||||
### Логические связки
|
||||
- причинность: *therefore, thus, consequently, as a result, due to, owing to*
|
||||
- противопоставление: *however, nevertheless, whereas, on the contrary, although*
|
||||
- добавление: *moreover, furthermore, in addition, besides*
|
||||
- пример: *for instance, for example, such as, namely*
|
||||
- обобщение: *in general, overall, on the whole*
|
||||
|
||||
---
|
||||
|
||||
## 6. Ключевые лингвистические темы для подготовки
|
||||
(исходя из текстов прошлых лет и профиля факультета)
|
||||
|
||||
1. **Семиотика и структурализм** — знак, означающее/означаемое, модели Соссюра и Пирса, иконы/индексы/символы, langue/parole, синтагма/парадигма, коды и субкоды, коннотация/денотация. *(была в 2021)*
|
||||
2. **Когнитивная лингвистика** — концептуальная метафора, категоризация, укоренённость в опыте, схемы образов. *(была в 2023)*
|
||||
3. **Теория перевода** — эквивалентность, адекватность, трансформации, прагматика перевода.
|
||||
4. **Социолингвистика** — языковая ситуация, диалекты/стандарт, языковая норма, функциональная грамотность, «языковой вопрос». *(итальянский 2021 — про итальянский и диалекты)*
|
||||
5. **Лингводидактика / методика обучения ИЯ** — подходы, компетенции, ИИ в обучении, персонализация.
|
||||
6. **Психолингвистика и нейролингвистика** — речевая деятельность, порождение/восприятие речи.
|
||||
7. **Дискурс-анализ и коммуникативистика** — типы дискурса, PR, межкультурная коммуникация.
|
||||
|
||||
**Совет:** читайте англоязычные научно-популярные и академические тексты по этим темам, учитесь пересказывать их устно и письменно за 500 слов.
|
||||
|
||||
---
|
||||
|
||||
## 7. План подготовки (пример, 1 учебный год / интенсив)
|
||||
|
||||
1. **База (2–3 месяца):** повторить грамматику (времена, страдательный залог, герундий/инфинитив, условные, модальные), расширить академическую лексику; практика пересказов — 1 текст в неделю.
|
||||
2. **Формат (2 месяца):** разбор демо-вариантов 2021 и 2023; написание рефератов по 1–2 в неделю с самопроверкой на 500 слов и отсутствие плагиата.
|
||||
3. **Тематика (1 месяц):** чтение и реферирование текстов по темам п. 6.
|
||||
4. **Скорость (последний месяц):** тренировки на время (полный экзамен за 2 ч), отработка тайминга, психологическая подготовка.
|
||||
|
||||
### Самопроверка перед сдачей
|
||||
- [ ] Точно ~500 слов.
|
||||
- [ ] Есть введение, 2 блока, заключение.
|
||||
- [ ] Нет копирования 4+ слов подряд.
|
||||
- [ ] Связки между абзацами.
|
||||
- [ ] Научный/академический стиль, без разговорных оборотов.
|
||||
|
||||
---
|
||||
|
||||
## 8. Демо-варианты: где лежат файлы
|
||||
|
||||
В папке `материалы/` этой директории (`FIYR_MGU/материалы/`):
|
||||
- `ANGL_2021.docx` — английский, 2021 (совр. формат — реферат по семиотике)
|
||||
- `ANGL_2023.docx` — английский, 2023 (реферат по концептуальной метафоре)
|
||||
- `ITAL_2021.docx` — итальянский, 2021 (реферат по итальянскому языку и диалектам)
|
||||
- `Английский_2018.pdf` — английский, 2018 (старый формат: эссе + тест, скан)
|
||||
- `Английский_2019.pdf` — английский, 2019 (старый формат, скан)
|
||||
- `Test_Cultura_2018.pdf`, `Test_RegRos_2018.pdf` — демо по культурологии/регионоведению
|
||||
|
||||
> Примечание: полных демо за 2020, 2022, 2024, 2025 гг. на официальном сайте ФИЯР в
|
||||
> открытом доступе нет; сторонние площадки (форумы, телеграм-каналы, вк) полные
|
||||
> тексты экзаменационных материалов не публикуют. Рекомендуется сверяться с
|
||||
> официальной страницей магистратуры ФИЯР.
|
||||
|
||||
---
|
||||
|
||||
## 9. Чего ждать на самом экзамене (по регламенту 2026)
|
||||
|
||||
- Проводится дистанционно (2026 — платформа МТС-link, «Яндекс-Телемост» для регионоведения).
|
||||
- Для «Лингвистики» язык сдачи можно выбрать заранее (форма на сайте).
|
||||
- Ссылка на экзамен — на почту Госуслуг за сутки.
|
||||
- Оценивается по 100-балльной шкале, проходной минимум — 40.
|
||||
|
||||
---
|
||||
|
||||
*Документ носит справочно-учебный характер и основан на официальных материалах ФИЯР МГУ (ffl.msu.ru). Перед подачей документов проверяйте актуальную информацию на официальном сайте.*
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
МГУ имени М.В. Ломоносова
|
||||
Вступительные испытания по иностранному языку
|
||||
Английский язык
|
||||
2021 год
|
||||
стр. 1 из 5
|
||||
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
|
||||
Реферат должен содержать два смысловых блока:
|
||||
1. объективная/авторская информация
|
||||
₋ проблематика, обсуждаемая автором (предмет и объект исследования);
|
||||
₋ выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
|
||||
2. интерпретация прочитанного (аргументация предложенных выводов обязательна)
|
||||
₋ текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее;
|
||||
₋ текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите аргументы) в Вашем понимании поднимаемых вопросов и темы в целом.
|
||||
Структура работы:
|
||||
Введение (определение жанра текста, смысла названия и общей темы).
|
||||
Основная часть (два указанных выше смысловых блока).
|
||||
Заключение (место и значение исследования данной темы в современной лингвистике).
|
||||
Важные аспекты:
|
||||
Напишите реферат прочитанного текста в количестве 500 слов.
|
||||
Копирование 4 слов подряд из исходного текста или других источников расценивается некорректным цитированием, в случае обнаружения чего работа оценивается только на предмет языковой грамотности.
|
||||
Характеристики научного стиля реферата: логическая последовательность изложения, текстовая связность; стремление автора к точности, сжатости, однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/академического стиля.
|
||||
стр. 2 из 5
|
||||
A Brief Introduction To Semiotics
|
||||
(extract)
|
||||
By Barbara Brownie
|
||||
From Semiotics of Typography, 2009.
|
||||
In its origins, semiotics is deeply entwined with Structuralism, an “analytical method”which seeks to find “deep structures” underlying systems of signs. However,contemporary semiotics is concerned more with the social dimensions of the use ofsigns. Contemporary semioticians focus on processes of communication, and how westructure our understanding of our environments.
|
||||
The study of signs dates back as far as Hippocrates (460-377BC), who noted thatsymptoms are signs for underlying illness, thereby establishing that a sign “stands forsomething other than itself”. Outside of medicine, philosophers includingAristotle (384-32BC) established that and a sign can be divided into:
|
||||
a) its physical self;
|
||||
b) the thing to which it directly refers; and
|
||||
c) its meaning, which may vary due to social and personal experience.
|
||||
Much later, these ideas formed the basis for studies of signs and sign systems inlanguage and media, conducted by, among others, Ferdinand de Saussure (1857-1913, alinguist who established many fundamental ideas of structuralist semiotics) and CharlesSaunders Pierce (1839-1914), then later Roland Barthes (1915-1980, concerned largely with poststructuralism) and Umberto Eco (1932-). Although Pierce’s ideas will be brieflyintroduced below, this thesis will focus on the Saussurean tradition. Although Saussurehimself did not publish writing on the topic of semiotics, his Course de LinguistiqueGénérale survives in a text compiled by his students after his death. Focusing onlinguistic communication, this text establishes “the course that semiotic inquiry was totake during the first half of the twentieth century”, forming “the groundbase on whichmost contemporary structuralist theory now rests”. From the late 1960s, Barthesapplied semiotic theory in the field of cultural studies, and paved the way for theunderstanding of the linguistic sign as a material object, comparable to any other formor object that we may encounter in any media.
|
||||
A sign is anything that communicates meaning beyond itself. The idea of the sign, how itcan be broken down, and the systems into which signs are organised, form the basis forthe study of semiotics.
|
||||
For Ferdinand de Saussure, a sign is a “union” of two “equivalent” parts: the“signifier” (or “signal”) and “signified” (or “signification”), where the “signifier” isthe signs physical presence, and the “signified” is the concept evoked in the mind ofthe receiver. He stresses, however, that the “material” part of the sign (the signifier) andthe concept (the signified) are both “psychological” experiences of the receiver, so thateven the physical parts of a sign are only “sensory impressions”. In practice, these two parts of the sign are “always integrated into each other”, only divisible during the process of analysis.
|
||||
стр. 3 из 5
|
||||
An audience perceives the whole sign, and does not consciouslyseparate signifier from signified.
|
||||
Saussure suggests that the connection between the two parts of the sign are establishedarbitrarily (as with linguistics), although he does concede that “certain signifiers [are]appropriate for their signifieds, as in onomatopoeia”. More recent theorists note thatmany signs are in fact “motivated”. Levi-Strauss, for example, observed that therelationship between a spoken sound and its written equivalent is “conventional”, or“rational”. Some conventions are established over time. Though initially arbitrary,they are ultimately adopted as “natural” after the relationship between signifier andsignified has been established in society for a long time.
|
||||
Where Saussure discusses signifieds, he focuses on denotation, the initial, literal“referent a sign intends to capture”. Barthes, however, focuses on connotation. Theconnotative meaning of a sign involves associations that are established through socialconvention. The range of connotations that a sign evokes may vary, being specific toculture, and the knowledge and experience of the audience. Connotations areextensions of the denotative meaning. Barthes suggested, therefore, that connotation isa “second-order of signification”, in a chain of possible meanings. The form of thesignifier can contribute to the connotative meaning, so that the same signified can havedifferent meaning when presented in a different manner or style.
|
||||
Pierce proposed a slightly different model of the sign, identifying its parts as“representamen”, “object” and “interpretant”. In this model, the “representamen” is therepresentational object or form, equating to Saussure’s signifier, the “object” is the thing to which is directly represented, and the “interpretant” is the meaning that is achievedonce the sign has been “evaluated” by the audience. Though Piercian and Saussureanmodels are both in use today, this text will use the Saussurean model of the sign.Pierce also suggested that signs represent their subjects in different ways, thereby fallinginto categories. In the first category of signs, “icons”, representamen resemble theirobject so that not much additional knowledge is required for a correct interpretation ofa sign, as in photographs. Indexical signs (“indices”) are directly connected to theobject, but do not resemble it. Most “natural signs”, such as footprints, fall into thiscategory. In the third category, “symbols”, the relationship between the representamenand object is established arbitrarily, as in “images, diagrams, and metaphors”.Saussure, however, feels that the term “symbol” is misleading when discussing language,since many symbols do display “a vestige of natural connection” between the signifierand signified, and are therefore never entirely arbitrary.
|
||||
Saussure noted that, although in semiotic analysis we assess each sign individually, wegenerally do not encounter signs alone. This illustrates the structuralist view that “thenature of every element in any given situation has no significance by itself, and in fact isdetermined by its relationship to all other elements involved in that situation”.Therefore the context, the relationship between signs, must also be analysed. This vein ofsemiotic analysis investigates the meaning of a sign as developed according to
|
||||
стр. 4 из 5
|
||||
relationships between other signs in a group, alternative signs in the same set, and withinculturally established contexts.
|
||||
Saussure introduced the “systems” within which signs are categorised. The sign is the“basic unit” of any “language”, ranging from words to military signals, and it is within the context of this language that the sign in understood. This context must bedivided into the language itself (a “formal system”, with “rules and conventions”)and individual instances of use, which Saussure termed “langue” and “parole”respectively. Because language is capable of “generating new aspects of itself”,parole, or “the execution of language”, results in unique contexts that are defined by“individual speakers”.
|
||||
In any instance of parole, the meaning of a sign can be affected by syntagmatic andparadigmatic relations. The “syntagm” is the part of the text (in linguistics, thesentence) in which the sign is used alongside other signs from the same langue. Eachsign in such a sequence of signs is understood in terms of its syntagmatic relations withthe other signs that appear alongside it. So, for example, the meaning of a word isaffected by its particular use in a sentence. Saussure presents this relationship as“horizontal”, since language is received in a linear fashion. As well as by thepresence of other signs, meaning is determined by alternative signs, notable in theirabsence. Saussure identified “associative” relations (more commonly, “paradigmatic”relations) with other signs which could have been used in the same context. Thesealternative signs are vertically located within the same “paradigm” (set of signsbelonging to the same category). For example, the use of the word ‘tree’ as opposed to‘bush’ must signify a plant larger than a bush, otherwise ‘bush’ would have been used.The same syntagmatic and paradigmatic analyses can be applied outside of linguistics,as Barthes demonstrates when discussing clothes. Barthes identifies “items which cannotbe worn at the same time on the same part of the body (such as hats, trousers, shoes)” as having paradigmatic relations, while “the syntagmatic dimension is the juxtaposition ofdifferent elements at the same time in a complete ensemble”.When there are many possible readings of a sign (that is, many possible denotations, andhence connotations), context can also reduce the number of likely interpretations througha process of “anchorage”. Barthes identified anchorage in image captions, where “thetext directs the reader through the signifieds of the image, causing him to avoid someand receive others”. By anchoring a sign, it is possible to establish a “preferredmeaning”.
|
||||
A “code” is a way of communicating meaning that has been “conventionalized” by anysociety or group. All signs depend on the receiver being familiar (consciously orunconsciously) with the language, or “code”, to which the sign belongs. SpokenEnglish, for example, requires understanding of the sounds that represent words in the English language; “even an indexical and iconic sign such as a photograph involves atranslation from three dimensions into two”. Codes, therefore, “provide a frameworkwithin which signs make sense”.
|
||||
стр. 5 из 5
|
||||
Societies have “primary” codes (usually the “dominant ‘natural’ language”), and withinany code, “sub-codes” exist. Language, for example, may be subdivided into “spokenand written forms”.
|
||||
Barthes proposed “the notion that we ‘encode’ our experience of the world in order thatwe may experience it”. By a process of encoding, we “invent the world we inhabit”,representing and understanding it in ways that are specific to our social group. Sincecodes vary from culture to culture, the same sign or text may have different meaning todifferent audiences, and interpretations may vary from the message intended by the encoder. Such unintended interpretations are described by Umberto Eco as “aberrantdecoding”. In many instances aberrant decoding is unavoidable, as texts can be madeavailable to diverse, even international, audiences.
|
||||
@@ -0,0 +1,51 @@
|
||||
МГУ имени М.В. Ломоносова
|
||||
Вступительные испытания по иностранному языку
|
||||
Английский язык
|
||||
2023 год
|
||||
стр. 1 из 5
|
||||
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
|
||||
Реферат должен содержать два смысловых блока:
|
||||
1. объективная/авторская информация
|
||||
₋ проблематика, обсуждаемая автором (предмет и объект исследования);
|
||||
₋ выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (обобщение, аналогия; дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
|
||||
2. интерпретация прочитанного (аргументация предложенных выводов обязательна)
|
||||
₋ текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее;
|
||||
₋ текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите аргументы) в Вашем понимании поднимаемых вопросов и темы в целом.
|
||||
Структура работы:
|
||||
Введение (определение жанра текста, смысла названия и общей темы).
|
||||
Основная часть (два указанных выше смысловых блока).
|
||||
Заключение (место и значение исследования данной темы в современной лингвистике).
|
||||
Важные аспекты:
|
||||
Напишите реферат прочитанного текста в количестве 500 слов.
|
||||
Копирование 4 слов подряд из исходного текста или других источников расценивается некорректным цитированием, в случае обнаружения чего работа оценивается только на предмет языковой грамотности.
|
||||
Характеристики научного стиля реферата: логическая последовательность изложения, текстовая связность; стремление автора к точности, сжатости, однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/академического стиля.
|
||||
стр. 2 из 5
|
||||
Some Consequences for Theories of Conceptual Structure
|
||||
(extract)
|
||||
By George Lakoff and Mark Johnson
|
||||
From Metaphors We Live By, 2004.
|
||||
Any adequate theory of the human conceptual system will have to give an account of how concepts are (1) grounded, (2) structured, (3) related to each other, and (4) defined. We have argued that most of our conceptual system is metaphorically structured. There are several strategies that linguists and logicians have used to handle, without any reference to metaphor, what we have called metaphorical concepts.
|
||||
We will look at the strategy of abstraction. To see how it differs from the account we have offered, consider the word buttress in "He buttressed the wall" and "He buttressed his argument with more facts." On our account, we understand buttress in "He buttressed his argument" in terms of the concept BUTTRESS, which is part of the BUILDING gestalt. Since the concept ARGUMENT is comprehended partly in terms of the metaphor AN ARGUMENT IS A BUILDING, the meaning of "buttress" in the concept ARGUMENT will follow from the meaning it has in the concept BUILDING, plus the way that the BUILDING metaphor in general structures the concept ARGUMENT. Thus we do not need an independent definition for the concept BUTTRESS in "He buttressed his argument."
|
||||
Against this, the abstraction view claims that there is a single, very general, and abstract concept BUTTRESS, which is neutral between the BUILDING "buttress" and the ARGUMENT "buttress." According to this view, "He buttressed the wall" and "He buttressed his argument" are both special cases of the same very abstract concept.
|
||||
We would now like to show why the abstraction theory cannot account for the kinds of facts that have led us to the theory of metaphorical concepts—in particular, the facts concerning the metaphorical types (orientational, physical, and structural) and their properties (internal systematicity, external systematicity, grounding, and coherence).
|
||||
The abstraction theory is inadequate in several respects. First, it does not seem to make any sense at all with respect to UP-DOWN orientation metaphors, such as HAPPY IS UP, CONTROL IS UP, MORE IS UP, VIRTUE IS UP, THE FUTURE IS UP, REASON IS UP, etc. What single general concept with any content at all could be an abstraction of HEIGHT, HAPPINESS, CONTROL, MORE, VIRTUE, THE FUTURE, REASON, and NORTH and would precisely fit them all? Moreover, it would seem that UP and DOWN could not be at the same level of abstraction, since UP applies to the FUTURE, while DOWN does not apply to the PAST. We account for this by partial metaphorical structuring, but under the abstraction proposal UP would have to be more abstract in some sense than DOWN, and that does not seem to make sense.
|
||||
стр. 3 из 5
|
||||
Second, the abstraction theory would not distinguish between metaphors of the form A is B and those of the form B is A, since it would claim that there are neutral terms covering both domains. For example, English has the LOVE IS A JOURNEY metaphor but no JOURNEYS ARE LOVE metaphor. The abstraction view would deny that love is understood in terms of journeys, and it would be left with the counterintuitive claim that love and journeys are understood in terms of some abstract concept neutral between them.
|
||||
Third, different metaphors can structure different aspects of a single concept; for example, LOVE IS A JOURNEY, LOVE IS WAR, LOVE IS A PHYSICAL FORCE, LOVE IS MADNESS. Each of these provides one perspective on the concept LOVE and structures one of many aspects of the concept. The abstraction hypothesis would seek a single general concept LOVE abstract enough to fit all of these aspects. Even if this were possible, it would miss the point that these metaphors are not jointly characterizing a core concept LOVE but are separately characterizing different aspects of LOVE.
|
||||
Fourth, if we look at structural metaphors of the form A is B (e.g., LOVE IS A JOURNEY, THE MIND IS A MACHINE, IDEAS ARE FOOD, AN ARGUMENT IS A BUILDING), we find that B (the defining concept) is more clearly delineated in our experience and typically more concrete than A (the defined concept). Moreover, there is always more in the defining concept than is carried over to the defined concept. Take IDEAS ARE FOOD. We may have raw facts and half-baked ideas, but there are no sauteed, broiled, or poached ideas. In AN ARGUMENT IS A BUILDING only the foundation and outer shell play a part in the metaphor, not the inner rooms, corridors, roof, etc. We have explained this asymmetry in the following way: the less clearly delineated (and usually less concrete) concepts are partially understood in terms of the more clearly delineated (and usually more concrete) concepts, which are directly grounded in our experience. The abstraction view has no explanation for this asymmetry, since it cannot explain the tendency to understand the less concrete in terms of the more concrete.
|
||||
Fifth, under the abstraction proposal there are no metaphorical concepts at all and, therefore, no reason to expect the kind of systematicity that we have found. Thus, for example, there is no reason to expect a whole system of food concepts to apply to ideas or a whole system of building concepts to apply to arguments. There is no reason to expect the kind of internal consistency that we found in the TIME IS A MOVING OBJECT cases. In general, the abstraction view cannot explain the facts of internal systematicity.
|
||||
Abstraction also fails to explain external systematicity. Our proposal accounts for the way that various metaphors for a single concept (e.g., the JOURNEY, BUILDING, CONTAINER, and WAR metaphors for arguments) overlap in the way that they do. This is based on the shared purposes and shared entailments of the metaphorical concepts. The way that individual concepts (such as CORE, FOUNDATION, COVER, SHOOT DOWN, etc.) mix with each other is predicted on the basis of shared purposes and entailments in the entire metaphorical system. Since the abstraction proposal does not have any metaphorical systems, it cannot explain why metaphors can mix the way they do.
|
||||
стр. 4 из 5
|
||||
Sixth, since the abstraction proposal has no partial metaphorical structuring, it cannot account for metaphorical extensions into the unused part of the metaphor, as in "Your theory is constructed out of cheap stucco" and many others that fall within the unused portion of the THEORIES ARE BUILDINGS metaphor.
|
||||
Finally, the abstraction hypothesis assumes, in the case of LOVE IS A JOURNEY, for example, that there is a set of abstract concepts, neutral with respect to love and journeys, that can "fit" or "apply to" both of them. But in order for such abstract concepts to "fit" or "apply to" love, the concept LOVE must be independently structured so that there can be such a "fit." As we will show, LOVE is not a concept that has a clearly delineated structure; whatever structure it has it gets only via metaphors. But the abstraction view, which has no metaphors to do the structuring, must assume that a structure as clearly delineated as the relevant aspects of journeys exists independently for the concept LovE. It's hard to imagine how.
|
||||
Another theory, the one of homonymy, looks at metaphorical concepts from a different perspective. The homonymy view takes the opposite to the abstraction tack. Instead of claiming that there is one abstract and neutral concept BUTTRESS, the homonymy view claims that there are two different and independent concepts, BUTTRESS1 and BUTTRESS2.
|
||||
There is a strong homonymy view, according to which BUTTRESS1 and BUTTRESS2 are entirely different and have nothing to do with each other, since one refers to physical objects (building parts) and the other to an abstract concept (a part of an argument).
|
||||
The weak homonymy view maintains that there are distinct and independent concepts BUTTRESS1 and BUTTRESS2 but allows that their meanings may be similar in some respects and that the concepts are related by virtue of this similarity. It denies, however, that either concept is understood in terms of the other. All it claims is that the two concepts have something in common: an abstract similarity. On this point, the weak homonymy view shares an element with the abstraction view, since the abstract similarity would have precisely the properties of the core concept that is hypothesized by the abstraction theory.
|
||||
In general, the strong homonymy view cannot account for the relationships that we have identified in systems of metaphorical concepts; that is, it views as accidental all the phenomena that we explain in systematic terms.
|
||||
In the first place, the strong homonymy position cannot account for any of the internal systematicity that we have described. For example, it would be possible, according to this view, for "I'm feeling up" to mean "I'm happy" and, simultaneously, for "my spirits rose" to mean "I got sadder." Nor can this position account for why the whole system of words used for war should apply in a systematic way to arguments or why a system of food terminology should apply in a systematic way to ideas.
|
||||
Second, the strong homonymy view has the same problems with cases of external systematicity. That is, it cannot account for the overlap of metaphors and the possibility of mixing. It cannot explain, for example, why the "ground covered" in an argument can refer to the same thingas the "content" of the argument. This holds in general for all the examples of mixing that we have given.
|
||||
стр. 5 из 5
|
||||
Third, the strong homonymy view cannot explain extensions of the used (or unused) portion of a metaphor, as in "His theories are Gothic and covered with gargoyles." Since that theory has no general metaphors like AN ARGUMENT IS A BUILDING, it must view such cases as random.
|
||||
The weak homonymy view is superior to the strong view precisely because it does allow for the possibility of such relationships. In particular, it holds that the various concepts expressed by a single word can in many cases be related by similarity. The weak homonymy view takes such similarities as given and assumes that they are sufficient to account for all the phenomena that we have observed, though without the use of any metaphorical structuring.
|
||||
The most obvious difference between the weak homonymy position and ours is that it has no notion of understanding one thing in terms of another and hence no general metaphorical structuring. The reason for this is that most of those who hold this position are not concerned with how our conceptual system is grounded in experience and how understanding emerges from such grounding. Most of the inadequacies we find in the weak homonymy position have to do with its lack of concern for issues of understanding and grounding. These same inadequacies will, of course, apply also to the strong version of the homonymy position.
|
||||
To our knowledge, no one explicitly holds the strong homonymy position, according to which concepts expressed by the same word (like the two senses of "buttress" or the many senses of "in"), are independent and have no significant relationships. Those who hold the homonymy position tend to identify themselves as holding the weak position, where the interdependencies and interrelationships that are observed between concepts are to be accounted for by similarities based on the inherent nature of the concept. However, to our knowledge, no one has ever begun to provide a detailed account of a theory of similarity that could deal with the wide range of examples we have discussed. Although virtually all homonymy theorists espouse the weak version, in practice there seem to be only strong homonymy theories, since no one has attempted to provide the detailed account of similarity necessary to maintain the weak version of the theory. And there is a good reason why no attempt has been made to give such a detailed account of the kinds of examples we have been discussing. The reason is that such an account would require one to address the issue of how we comprehend and understand areas of experience that are not well-defined in their own terms and must be grasped in terms of other areas of experience. In general, philosophers and linguists have not been concerned with such questions.
|
||||
@@ -0,0 +1,35 @@
|
||||
МГУ имени М.В. Ломоносова
|
||||
Вступительные испытания по иностранному языку
|
||||
Магистратура
|
||||
Итальянский язык 2021 год
|
||||
Задание
|
||||
Прочитайте текст и изложите его содержание в научном стиле своими словами, соблюдая классическую структуру: введение, основная часть, заключение.
|
||||
Реферат должен содержать два смысловых блока:
|
||||
1. объективная/авторская информация
|
||||
₋ проблематика, обсуждаемая автором (предмет и объект исследования);
|
||||
₋ выбранный способ ее обсуждения (анализ, синтез, дискуссия, критический обзор, реферативный обзор и пр.) и определение методов исследования (дедукция, индукция; классификация, типология; количественный анализ, качественный анализ, сравнительно-сопоставительный анализ и пр.).
|
||||
2. интерпретация прочитанного (аргументация обязательна)
|
||||
₋ текст/автор подтверждает/опровергает и обновляет/расширяет/дополняет прочитанное ранее (приведите свои аргументы);
|
||||
₋ текст/автор вызывает в каких-то аспектах недоверие (поскольку, например, изложено противоречиво /бездоказательно /нет примеров и пр.) и/или одобрение (приведите свои аргументы) в Вашем понимании поднимаемых вопросов и, в заключение, какую роль в разработке темы играет данное исследование в современной лингвистике (приведите свои аргументы).
|
||||
Структура работы должна включать:
|
||||
Введение (определение жанра текста, смысла названия и общей темы в рамках лингвистического направления/ на стыке лингвистических направлений).
|
||||
Основная часть (два указанных выше смысловых блока).
|
||||
Заключение (место и значение исследования данной темы в современной лингвистике).
|
||||
В реферате должны быть учтены следующие важные аспекты:
|
||||
Напишите реферат прочитанного текста в количестве 500 слов.
|
||||
Копирование 4 слов подряд из исходного текста или других источников расценивается плагиатом, в случае обнаружения которого работа оценивается только на предмет языковой грамотности.
|
||||
Характеристики научного стиля реферата: объективность, логическая последовательность изложения, текстовая связность; стремление автора к точности изложения фактов и терминологии, сжатости и однозначности при сохранении насыщенности содержания, в том числе за счет выбора лексики и грамматических структур формального/ академического стиля.
|
||||
L’italiano e le carenze linguistiche: perché non è (solo) colpa della scuola
|
||||
di Filomena Fuduli Sorrentino
|
||||
Oggi l’analfabetismo è praticamente scomparso ma esiste un gran numero di analfabeti funzionali, cioè, persone che sanno leggere ma non riescono a comprendere un testo scritto o un discorso formale. Per capire bene il problema dell’italiano odierno dobbiamo ricordare che, fino al tempo dell’unità d’Italia, l’italiano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta l’Italia erano tra il 2,5% e il 10% dell’intera popolazione.
|
||||
I docenti si lamentano delle carenze linguistiche dei loro studenti di vario tipo: grammatica, sintassi, e lessico. Il problema della lingua scritta si affronta anche con l’inglese nelle scuole di New York, dove per rimediare hanno attivato corsi di recupero di scrittura e lettura con docenti specializzati.
|
||||
Il tema dell’uso dell’italiano corretto o errato è simile a quello affrontato in passato con la “questione della lingua”. Una storia vecchia che va di pari passo con quella della letteratura italiana: dal De vulgari eloquentia di Dante, alle Prose della volgar lingua del Bembo, il risciacquo dei panni nell’Arno del Manzoni, fino a giungere a Calvino e al neo-italiano industriale di Pasolini. L’unità linguistica iniziò con Dante, che attraverso la sua opera De Vulgari Eloquentia esaminò pregi e difetti delle diverse lingue parlate in tutta la penisola e in seguito l’italiano si sviluppò dal fiorentino, ma il percorso non fu facile. All’inizio del XIX secolo i dialetti italiani erano così diversi da essere reciprocamente incomprensibili, e verso di essi c’è sempre stato pregiudizio; la gente pensava che l’italiano standard fosse la lingua usata dalla borghesia mentre i dialetti venivano usati dagli agricoltori e dalla classe operaia.
|
||||
Il 22 dicembre 1947 venne approvata la Costituzione con 453 voti a favore e 62 contrari, e nel 1948 entrava in vigore il diritto allo studio con l’articolo 34: “La scuola è aperta a tutti. L’istruzione inferiore, impartita per almeno otto anni, è obbligatoria e gratuita. I capaci e meritevoli, anche se privi di mezzi, hanno diritto di raggiungere i gradi più alti degli studi. La Repubblica rende effettivo questo diritto con borse di studio, assegni alle famiglie ed altre provvidenze, che devono essere attribuite per concorso.” Ma in realtà il diritto non era garantito a tutti i ragazzi per vari motivi: l’accesso all’istruzione superiore e all’università era riservato ai ragazzi di famiglie agiate, mentre quelli provenienti da famiglie povere e da classe operaia e agricola erano una risorsa economica per la loro famiglia, quindi dovevano andare a lavorare e non potevano frequentare la scuola. E così, fino agli anni 50-60, molti bambini non finivano nemmeno la scuola elementare. Nel 1950, anche se il paese stava attraversando un periodo di ricostruzione infrastrutturale, economica, sociale e politica, meno del 20% della popolazione italiana parlava correntemente l’italiano nella vita quotidiana. L’analfabetismo e il semi-analfabetismo erano ampiamente presenti nella popolazione.
|
||||
Eppure bisogna aspettare il decreto statale del 2007 affinché l’età di frequenza scolastica obbligatoria sia revocata dai 14 ai 16 anni e tutti gli studenti possano completare almeno 10 anni di istruzione. Comunque, non è stata la scuola a diffondere l’italiano in tutta la penisola durante gli anni, bensì l’introduzione della televisione nel 1954, anche se aveva un solo canale. I programmi televisivi cominciarono a essere trasmessi dalla RAI, l’emittente statale. Negli anni tra il 1958 e il 1962 la televisione divenne un modo per riunire le persone (pochissime persone avevano effettivamente un televisore in casa) e soprattutto un modo per seguire programmi culturali e linguistici.
|
||||
Infatti, tra il 1960 e il 1968 la RAI trasmise uno spettacolo di pomeriggio che si chiamava “Non è mai troppo tardi”, presentato dal maestro Alberto Manzi, responsabile dell’alfabetizzazione della popolazione italiana che non aveva avuto accesso alla scuola ed era rimasta completamente analfabeta. Con il programma del maestro Manzi molti analfabeti hanno imparato a leggere e a scrivere e circa un milione e mezzo di italiani hanno ottenuto il certificato di istruzione primaria (quinta elementare). Eppure, mentre durante i primi 20 anni della sua esistenza la televisione dello Stato ebbe una funzione istruttiva, dagli anni ’80 in poi si concentrò su spettacoli con comportamenti banali, e a volte anche volgari e lontani dalla realtà, ed ebbe un effetto negativo sull’istruzione culturale delle generazioni più giovani. Il linguaggio della televisione è diventato molto più semplice, pieno di slang, privo di sintassi, e spesso errato, e impoverisce la lingua italiana. In altre parole, una forma di “populismo linguistico” progettato per attrarre i giovani e la massa di persone prive di un’istruzione culturale.
|
||||
Nel 1970 apparve il libro “Lettere da una tarantata” con una nota linguistica dello storico della lingua italiana Tullio De Mauro che diceva: “non ha padronanza dell’italiano e trova quindi difficoltà a scrivere. Nelle sue lettere sono presenti numerosi errori ortografici e forti storpiature dialettali, ma lei non si scoraggia e si convince che l’importante è farsi capire”. Lettere da una tarantata costituisce un importante punto di riferimento per capire la nascita e lo sviluppo dell’italiano popolare. In seguito, il libro diventerà un testo di riferimento per gli studi sull’uso dell’“italiano popolare unitario”; il libro raccoglie le 65 lettere che la protagonista Anna, contadina semianalfabeta, nata a Ruffano nel 1898, inviò tra il 1959 e il 1965 all’antropologa Annabella Rossi. Anna rappresenta per Tullio De Mauro l’incarnazione della volontà di comunicare delle classi inferiori. Egli ha apprezzato il suo stile, definendolo vivace e originale, e ha invece criticato l’italiano insegnato nella scuola, paragonandolo a un “rullo compressore” che rende la lingua piatta e vuota.
|
||||
L’etichetta di italiano popolare fu introdotta nel 1970 da Tullio De Mauro e Manlio Cortelazzo. Lo storico De Mauro lo aveva definito “il modo d’esprimersi di un incolto che, sotto la spinta di comunicare e senza addestramento, maneggia quella che ottimisticamente si chiama la lingua nazionale”. Cortelazzo invece lo aveva definito come “il tipo di italiano imperfettamente acquisito da chi ha per madrelingua il dialetto”. Alcuni studiosi, partendo dalla concezione di De Mauro, mettono in dubbio l’effettiva presenza di un italiano standard, e hanno valutato positivamente l’italiano popolare considerandolo la ricchezza di quelle classi sociali con una competenza linguistica minima, ma pura e autentica. Altri studiosi invece hanno seguito la concezione di Cortelazzo riconoscendo ampio vigore all’italiano standard, e hanno evidenziato l’inferiorità dell’italiano popolare, sostenendo la necessità di estirparlo.
|
||||
Per capire bene il problema dell’italiano odierno dobbiamo ricordare che, fino al tempo dell’unità d’Italia, l’italiano era la lingua di una piccolissima minoranza e quelli che parlavano e scrivevano in italiano in tutta l’Italia erano tra il 2,5% e il 10% dell’intera popolazione. In seguito la lingua italiana è arrivata a essere parlata da circa 60 milioni di abitanti sparsi in tutta la penisola, ma il veloce cambiamento ha assorbito tratti dei dialetti locali e dell’italiano regionale presentano alterazioni della morfologia e della sintassi dell’italiano standard, derivante dalla tradizione scritta e adatto agli usi formali. Questa evoluzione graduale della lingua italiana successe senza che la scuola riuscisse a stare al passo dei suoi cambiamenti, resi estremamente veloci anche dai mezzi di comunicazione di massa.
|
||||
Inoltre, l’italiano e il dialetto sono due lingue diverse ma gli italiani, invece di concepire una divisione tra le due lingue, hanno continuato a mischiarle tra loro e così oggi abbiamo dialetti italianizzati, e un italiano dialettizzato e pieno di errori. Eppure, negli ultimi 50 anni molti termini regionali, dalla Toscana, dalla Lombardia, dal Veneto, da Napoli e dalla Sicilia, sono entrati nella lingua nazionale e non sorprende che i dialetti siano stati studiati da linguisti e usati nella letteratura e nella poesia. Però, nonostante l’italiano sia una lingua ricca di termini, espressioni idiomatiche e sfumature semantiche, e i dizionari più completi possono contenere da 80.000 a 250.000 voci, le ricerche condotte dallo storico della lingua italiana, Tullio De Mauro, alcuni anni prima della sua morte (1932-2017), hanno dimostrato che circa la metà della popolazione usa solo 3000 parole nella conversazione di tutti i giorni.
|
||||
Dunque, la lingua evolve e cambia e questo è stato da sempre dimostrato da studiosi, linguisti, e sociolinguisti. Nel 2013 De Mauro aveva affermato: “La lingua italiana – per chi la sa usare leggendo, scrivendo o parlando – sta bene. Stanno male gli italiani che la sanno usare poco. Stanno male non per ragioni puristiche o astratte, ma perché conoscere male la lingua nazionale significa studiare male – se uno ci prova – altre lingue e significa avere una vita di relazione modesta, non capire tante cose che servono sul lavoro, nella produzione”. Il grande e illustre prof. De Mauro aveva ragione, la lingua italiana è ricca di termini e, purtroppo, molti italiani conoscono un numero limitato di vocaboli sia della lingua italiana e sia di quella regionale, e di conseguenza le loro abilità linguistiche sono e rimangono limitate.
|
||||
Per concludere: se nei licei e nelle università l’italiano corretto lo sappiamo scrivere e parlare in pochi, non dipende dalle scuole o dai docenti ma dalle famiglie e dalla società. Le scuole sono aperte a tutti e oggi tutti hanno accesso alle università ma questo non garantisce a tutti gli studenti una correttezza ortografica e grammaticale senza che essi si applicano a migliorare le loro carenze linguistiche e grammaticali. Molti ragazzi non hanno sufficiente possesso degli strumenti linguistici di base nelle medie e nei licei e questa lacuna di apprendimento non si può più colmare nelle università.
|
||||
@@ -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`)
|
||||
@@ -0,0 +1,66 @@
|
||||
# MAN Format Fix — 2026-08-10
|
||||
|
||||
**Проблема:** `service_man` из YAML содержит Markdown (`#`, `##`, `---`, `**`) + HTML (`<br/>`, `"`), но рендерился как сырой текст. Теги `##`, `**`, `---` выводились буквально, не форматируя текст.
|
||||
|
||||
**Корень проблемы:** `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 (больше не нужен) |
|
||||
@@ -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 каталог.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 2026-09-03 — Чистка реестра + новая нумерация версий + баг dev
|
||||
|
||||
## Новая схема нумерации версий (с 2026-09-03)
|
||||
|
||||
| Стенд | Namespace | Диапазон | Первая |
|
||||
|---|---|---|---|
|
||||
| prod | `nubes` | `1.*.*` | `1.0.0` |
|
||||
| dev | `nubes-dev` | `2.*.*` | `2.0.0` |
|
||||
| test | `nubes-test` | `3.*.*` | `3.0.0` |
|
||||
|
||||
⛔ Старые схемы (`prod=2.*`, `dev=3.*`, `test=5.*`, `0.0.x`) — ЛЕГАСИ, не использовать.
|
||||
Обновлено: `VERSIONS.md`, `TOOLS/config/*/profile.env`, `DOCS_PIPELINE/README.md`,
|
||||
`docs/30_registry/guides/getting-started.md`.
|
||||
|
||||
## Чистка реестра
|
||||
|
||||
Из S3 (`nubes-terraform-registry`, креды super `1112_terraform`) удалены ВСЕ старые версии:
|
||||
- `nubes-dev`: 3.0.2–3.0.6
|
||||
- `nubes`: 2.0.2, 2.0.3, 2.0.5, 2.0.6
|
||||
- `nubes-test`: 0.0.1, 5.0.1–5.0.5, 5.1.17
|
||||
|
||||
После чистки в каждом namespace — 0 объектов. Легаси (5.1.17 и т.д.) нигде не осталось.
|
||||
|
||||
## Статус перегенерации (2026-09-03)
|
||||
|
||||
- ✅ **test** `3.0.0` — сгенерирован и загружен (`Done. Version 3.0.0 uploaded`).
|
||||
- ❌ **dev** `2.0.0` — НЕ собирается (пропущен по решению пользователя), см. баг ниже.
|
||||
- ⏳ **prod** `1.0.0` — в работе.
|
||||
|
||||
## Баг dev: nodejs jsonEnv (create vs modify)
|
||||
|
||||
Симптом: `03` dev падает на компиляции сгенерированного кода:
|
||||
```
|
||||
internal/resources_gen/95_nodejs_resource.go:350-353:
|
||||
plan.JsonEnv.IsNull / IsUnknown / ValueString undefined
|
||||
(type *NodejsJsonEnvModel has no field or method ...)
|
||||
```
|
||||
|
||||
Причина: **API dev** для nodejs `jsonEnv`:
|
||||
- в `create` (op id=58) — `map` **с `sub_params`** (типизированные ключи, напр. DB_PASS) → генератор создаёт вложенную модель `NodejsJsonEnvModel`;
|
||||
- в `modify` (op id=59) — `map` **без `sub_params`** → генератор для diff генерирует строковое сравнение (`IsNull/ValueString`).
|
||||
|
||||
У test/prod `jsonEnv` без sub_params в обоих операциях → строка → собирается.
|
||||
|
||||
Корень: `TOOLS/resource-generator/internal/params/params.go`, `AlignParamTypes` —
|
||||
подмешивает `SubParams` из schema в modify только если `HasSubParams` уже true:
|
||||
```go
|
||||
if !p.HasSubParams { continue } // modify-jsonEnv (без sub) пропускается
|
||||
```
|
||||
|
||||
Возможный фикс: наследовать `HasSubParams`/`SubParams` из schema для параметров с тем же
|
||||
code. ⚠️ Нюанс: diff-шаблон исключает nested-поля из `hasServiceParamChanges` — изменение
|
||||
nested jsonEnv не будет триггерить modify (нужно продумать отдельно).
|
||||
|
||||
**Вывод:** сервисы/структуры API стендов отличаются (dev jsonEnv — nested в create).
|
||||
Каждый стенд рассматривать независимо. dev отложен до решения по генератору/API.
|
||||
|
||||
## Прочее (инфраструктура, этот же день)
|
||||
- Токены API `secrets/*.token` были отозваны на стороне IAM (401 IAM error при валидном exp) — обновлены 2026-09-03.
|
||||
- S3-креды `.s3cfg_registry` (docs) не имеют прав на бакет бинарников `nubes-terraform-registry`;
|
||||
заливка бинарников — subuser `super` аккаунта `1112_terraform` (см. `tf_registry/HISTORY/HOWTO-UPLOAD.md`).
|
||||
@@ -0,0 +1,20 @@
|
||||
# TEST and PROD documentation publication
|
||||
|
||||
## Result
|
||||
|
||||
- TEST documentation was regenerated from `TOOLS/config/test` with version `3.0.0`.
|
||||
- PROD documentation was regenerated from `TOOLS/config/prod` with version `1.0.0`.
|
||||
- TEST and PROD builds were executed sequentially because both use the shared local `site/` directory.
|
||||
- TEST active mirror was replaced on the VM at `/var/www/tf-docs/nubes-test/`.
|
||||
- PROD active mirror was replaced on the VM at `/var/www/tf-docs/nubes/`.
|
||||
|
||||
## Verification
|
||||
|
||||
- TEST active mirror contains `714` files and its `index.html` is present.
|
||||
- PROD active mirror contains `344` files and its `index.html` is present.
|
||||
- TEST HTML contains the TEST dashboard/API/provider values.
|
||||
- PROD HTML contains the PROD dashboard/API/provider values.
|
||||
|
||||
## Infrastructure note
|
||||
|
||||
The S3 mirror command reported `unexpected EOF` while listing the registry. Its exit status was not treated as proof of publication. Each generated site was transferred directly to the VM, validated there, and atomically installed into its corresponding active mirror.
|
||||
@@ -0,0 +1,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`.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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 копировать в самом скрипте
|
||||
@@ -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?
|
||||
@@ -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/ или сразу на место?
|
||||
@@ -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, нужен для отладки.
|
||||
@@ -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 сек)
|
||||
- Таймауты
|
||||
- Сигнатуры функций
|
||||
@@ -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 | ❌ | ✅ |
|
||||
@@ -0,0 +1,53 @@
|
||||
# Sonnet: тестирование stages без реальных инстансов
|
||||
|
||||
Реализовали вывод этапов в `waitForOperationFinish()`. Код в репе.
|
||||
Нужно протестировать БЕЗ создания реальных инстансов в облаке.
|
||||
|
||||
---
|
||||
|
||||
## ⛔ Файлы — ПРОЧИТАТЬ ОБЯЗАТЕЛЬНО (иначе ответ будет неполным)
|
||||
|
||||
| # | Файл | Строки | Что искать |
|
||||
|---|------|--------|------------|
|
||||
| 1 | `provider/internal/core/client.go` | 29–55 | `UniversalClient` struct: поля `HttpClient`, `ApiEndpoint`, `ApiToken` |
|
||||
| 2 | `provider/internal/core/client.go` | 895–1010 | `waitForOperationFinish()` — ВСЯ функция от сигнатуры до закрывающей `}` |
|
||||
| 3 | `provider/internal/core/client.go` | 895–910 | ⛔ «ЗАПРЕЩЕНО ПРАВИТЬ ЭТОТ КОД БЕЗ ЯВНОГО СОГЛАСОВАНИЯ» |
|
||||
| 4 | `provider/internal/core/client.go` | 900–930 | `ttyOut()`, `opStage` struct, `operationStatusResponse` struct |
|
||||
| 5 | `provider/internal/core/client.go` | 1030–1140 | `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. Оценка: что можно без рефакторинга, что потребует правок
|
||||
@@ -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 8–12px)
|
||||
- **Цвета блоков** — брендовые цвета каждого сервиса (см. выше)
|
||||
- **Текст в блоках** — белый или светлый
|
||||
- **Стрелки** — с подписями протоколов (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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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/`
|
||||
@@ -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`).
|
||||
@@ -1,7 +1,7 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.26"
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ variable "api_token" {
|
||||
|
||||
provider "nubes" {
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.12"
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ variable "api_token" {
|
||||
|
||||
provider "nubes" {
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.10"
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ terraform {
|
||||
// Провайдер Nubes для создания ресурсов.
|
||||
provider "nubes" {
|
||||
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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,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
|
||||
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
|
||||
|
||||
- Go 1.22+
|
||||
@@ -25,7 +33,7 @@ S3 environment:
|
||||
- `S3_SECRET_KEY`
|
||||
|
||||
Provider naming defaults:
|
||||
- `REGISTRY_HOSTNAME`: `terra.k8c.ru`
|
||||
- `REGISTRY_HOSTNAME`: `tf-registry.containerk8s.services.ngcloud.ru`
|
||||
- `NAMESPACE`: `nubes`
|
||||
- `NAME`: `nubes`
|
||||
|
||||
|
||||
@@ -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` | Все настраиваемые значения |
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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-пользователя
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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" # таймаут операций
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user