Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33672e05bf | ||
|
|
0464a30642 | ||
|
|
106ddbe092 | ||
|
|
dbaeea5c89 | ||
|
|
7eb8eb8859 | ||
|
|
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 |
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
|
||||
version = "3.1.13"
|
||||
version = "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.1.16"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
|
||||
version = "3.1.1"
|
||||
version = "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
|
||||
version = "3.1.13"
|
||||
version = "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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,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`).
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.26"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.12"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "2.1.10"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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+
|
||||
|
||||
@@ -12,6 +12,8 @@ locals {
|
||||
resource "nubes_flask" "appflask" {
|
||||
resource_name = local.flask_resource_name
|
||||
|
||||
adopt_existing_on_create = true
|
||||
|
||||
startup_configuration = {
|
||||
resource_realm = var.realm
|
||||
}
|
||||
@@ -32,8 +34,6 @@ resource "nubes_flask" "appflask" {
|
||||
health_path = "/"
|
||||
}
|
||||
|
||||
git_revision = local.flask_git_revision
|
||||
|
||||
json_env = jsonencode({
|
||||
TABLE_NAME = local.crud_table_name
|
||||
PGHOST = local.flask_pg_host
|
||||
|
||||
@@ -9,7 +9,7 @@ locals {
|
||||
# PostgreSQL — общая БД для всех трёх приложений
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
pg_resource_name = "pg4crud" # имя ресурса в Nubes
|
||||
pg_resource_name = "pg4crud2" # имя ресурса в Nubes
|
||||
pg_cpu = 500 # CPU в millicores (500 = 0.5 ядра)
|
||||
pg_memory = 512 # память в MB
|
||||
pg_replicas = 1 # количество реплик
|
||||
@@ -31,8 +31,7 @@ locals {
|
||||
# Lucee — CFML-приложение (сервис 94)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
lucee_git_revision = "82685681" # коммит/тег в git (менять для редеплоя)
|
||||
lucee_resource_name = "luceecrud" # имя ресурса в Nubes
|
||||
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"
|
||||
@@ -50,8 +49,7 @@ locals {
|
||||
# Flask — Python-приложение (сервис 89)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
flask_git_revision = "54746e91" # коммит/тег в git (менять для редеплоя)
|
||||
flask_resource_name = "flaskcrud" # имя ресурса в Nubes
|
||||
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
|
||||
@@ -62,8 +60,7 @@ locals {
|
||||
# Node.js — Express-приложение (сервис 95)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
nodejs_git_revision = "809d303b" # коммит/тег в git (менять для редеплоя)
|
||||
nodejs_resource_name = "nodejscrud" # имя ресурса в Nubes
|
||||
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
|
||||
|
||||
@@ -8,6 +8,8 @@ locals {
|
||||
resource "nubes_lucee" "applucee" {
|
||||
resource_name = local.lucee_resource_name
|
||||
|
||||
adopt_existing_on_create = true
|
||||
|
||||
startup_configuration = {
|
||||
resource_realm = var.realm
|
||||
}
|
||||
@@ -27,8 +29,6 @@ resource "nubes_lucee" "applucee" {
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.1.16"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ locals {
|
||||
resource "nubes_nodejs" "appnodejs" {
|
||||
resource_name = local.nodejs_resource_name
|
||||
|
||||
adopt_existing_on_create = true
|
||||
|
||||
startup_configuration = {
|
||||
resource_realm = var.realm
|
||||
}
|
||||
@@ -31,7 +33,6 @@ resource "nubes_nodejs" "appnodejs" {
|
||||
health_path = "/"
|
||||
}
|
||||
|
||||
git_revision = local.nodejs_git_revision
|
||||
operation_timeout = local.nodejs_timeout
|
||||
|
||||
json_env = jsonencode({
|
||||
|
||||
@@ -8,7 +8,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" # реестр провайдера (test)
|
||||
version = "5.1.16" # версия провайдера Nubes
|
||||
version = "3.0.0" # версия провайдера Nubes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.61"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ terraform destroy
|
||||
## Провайдер
|
||||
|
||||
- **Источник**: `tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes`
|
||||
- **Версия**: `5.0.57`
|
||||
- **Версия**: `3.0.0`
|
||||
- **API**: `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc`
|
||||
|
||||
## Структура параметров
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.64"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.5"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.1.7"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ cp terraform.tfvars.example terraform.tfvars
|
||||
# api_token — Nubes API токен (TEST)
|
||||
# s3_user_uid — UUID S3 Object Storage
|
||||
|
||||
# 4. Инициализация (скачает провайдер v5.0.75 из реестра)
|
||||
# 4. Инициализация (скачает провайдер v3.0.0 из реестра)
|
||||
terraform init
|
||||
|
||||
# 5. Проверка
|
||||
@@ -37,5 +37,5 @@ terraform destroy
|
||||
## Провайдер
|
||||
|
||||
- **Источник**: `tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes`
|
||||
- **Версия**: `5.0.75`
|
||||
- **Версия**: `3.0.0`
|
||||
- **Registry**: `https://tf-registry.containerk8s.services.ngcloud.ru`
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.64"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.68"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
|
||||
version = "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes/nubes"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ TOKEN_FILE="secrets/dev.token"
|
||||
|
||||
# Release versions
|
||||
# Version
|
||||
VERSION="3.0.6"
|
||||
VERSION="2.0.0"
|
||||
|
||||
NAMESPACE="nubes-dev"
|
||||
PROVIDER_NAME="nubes"
|
||||
|
||||
@@ -4,7 +4,7 @@ TOKEN_FILE="secrets/prod.token"
|
||||
|
||||
# Release versions
|
||||
# Version
|
||||
VERSION="2.0.6"
|
||||
VERSION="1.0.0"
|
||||
|
||||
NAMESPACE="nubes"
|
||||
PROVIDER_NAME="nubes"
|
||||
|
||||
@@ -3,7 +3,7 @@ NUBES_API_ENDPOINT="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||||
TOKEN_FILE="secrets/test.token"
|
||||
|
||||
# Version
|
||||
VERSION="5.0.5"
|
||||
VERSION="3.0.0"
|
||||
|
||||
NAMESPACE="nubes-test"
|
||||
PROVIDER_NAME="nubes"
|
||||
|
||||
@@ -70,7 +70,7 @@ func WriteFile(path, content string) {
|
||||
}
|
||||
|
||||
// IndexMD генерирует index.md с группировкой по категориям.
|
||||
func IndexMD(docsDir string, specs []types.ServiceSpec) {
|
||||
func IndexMD(docsDir string, specs []types.ServiceSpec, namespace string) {
|
||||
catMap := map[string][]types.ServiceSpec{}
|
||||
for _, s := range specs {
|
||||
cat := serviceCategory(s.Name)
|
||||
@@ -89,6 +89,22 @@ func IndexMD(docsDir string, specs []types.ServiceSpec) {
|
||||
|
||||
var b bytes.Buffer
|
||||
b.WriteString("# Ресурсы провайдера\n\n")
|
||||
standNames := map[string]string{"nubes-dev": "DEV", "nubes-test": "TEST", "nubes": "PROD"}
|
||||
standURLs := map[string]string{
|
||||
"nubes-dev": "https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/",
|
||||
"nubes-test": "https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-test/",
|
||||
"nubes": "https://tf-docs.nodejsk8s.dev.nubes.ru/nubes/",
|
||||
}
|
||||
standComments := map[string]string{"nubes-dev": "разработка и интеграция", "nubes-test": "проверка перед PROD", "nubes": "рабочий стенд"}
|
||||
b.WriteString(fmt.Sprintf("## Текущий стенд: %s\n\n", standNames[namespace]))
|
||||
b.WriteString("Ниже доступны две другие среды документации:\n\n")
|
||||
for otherNamespace, otherName := range standNames {
|
||||
if otherNamespace == namespace {
|
||||
continue
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("- [%s](%s) — %s.\n", otherName, standURLs[otherNamespace], standComments[otherNamespace]))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
order := []string{"Базы данных", "Очереди", "Хранилище", "K8s", "VMware", "Приложения", "Сеть", "Другие"}
|
||||
for _, cat := range order {
|
||||
@@ -1162,9 +1178,9 @@ func WriteNavFragment(docsDir string, specs []types.ServiceSpec) {
|
||||
b.WriteString(fmt.Sprintf(" - Выходные данные: %s_outputs.md\n", s.Name))
|
||||
b.WriteString(fmt.Sprintf(" - Операции: %s_ops.md\n", s.Name))
|
||||
b.WriteString(fmt.Sprintf(" - Пример: %s_example.md\n", s.Name))
|
||||
if hasCurated[s.Name] {
|
||||
b.WriteString(fmt.Sprintf(" - 💡 Примеры из практики: curated/%s/pg_user_db.md\n", s.Name))
|
||||
}
|
||||
if hasCurated[s.Name] {
|
||||
b.WriteString(fmt.Sprintf(" - 💡 Примеры из практики: curated/%s/pg_user_db.md\n", s.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,9 @@ func main() {
|
||||
servicesListFlag := flag.String("services", "", "Path to TOOLS/config/{stand}/services_list.txt")
|
||||
excludeFlag := flag.String("exclude", "", "Comma-separated resource names to skip")
|
||||
versionFlag := flag.String("version", "", "Provider version for example block")
|
||||
// ⛔ LEGACY DEFAULT (index.cfm) — переопределяется через NUBES_API_ENDPOINT в profile.env.
|
||||
// Никогда не использовать deck-api.ngcloud.ru напрямую.
|
||||
apiEndpointFlag := flag.String("api-endpoint", "https://lk-api-gateway.ngcloud.ru/api/v1/svc", "API endpoint for example block")
|
||||
providerSourceFlag := flag.String("provider-source", "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes", "Provider source for example block")
|
||||
apiEndpointFlag := flag.String("api-endpoint", "", "API endpoint for example block (required)")
|
||||
providerSourceFlag := flag.String("provider-source", "", "Provider source for example block (required)")
|
||||
namespaceFlag := flag.String("namespace", "", "Documentation namespace (required)")
|
||||
opsFlag := flag.Bool("ops", false, "Generate per-service operations docs (resources_ops_yaml → docs/.../operations)")
|
||||
flag.Parse()
|
||||
|
||||
@@ -52,7 +51,17 @@ func main() {
|
||||
panic("--version is required")
|
||||
}
|
||||
apiEndpoint := *apiEndpointFlag
|
||||
if apiEndpoint == "" {
|
||||
panic("--api-endpoint is required")
|
||||
}
|
||||
providerSource := *providerSourceFlag
|
||||
if providerSource == "" {
|
||||
panic("--provider-source is required")
|
||||
}
|
||||
namespace := *namespaceFlag
|
||||
if namespace == "" {
|
||||
panic("--namespace is required")
|
||||
}
|
||||
|
||||
servicesOrder := loadServicesList(servicesList)
|
||||
specs := loadSpecs(resourcesDir, servicesOrder)
|
||||
@@ -74,7 +83,7 @@ func main() {
|
||||
writers.ResourceDocs(docsDir, spec, version, apiEndpoint, providerSource)
|
||||
processedSpecs = append(processedSpecs, spec)
|
||||
}
|
||||
writers.IndexMD(docsDir, processedSpecs)
|
||||
writers.IndexMD(docsDir, processedSpecs, namespace)
|
||||
writers.WriteNavFragment(docsDir, processedSpecs)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
"go/format"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -168,14 +167,13 @@ func EnsureGoIdent(s string) string {
|
||||
return "R" + s
|
||||
}
|
||||
|
||||
// FormatSourceOrWarn форматирует Go-код. При ошибке пишет warning в stderr.
|
||||
func FormatSourceOrWarn(filePath string, src []byte) []byte {
|
||||
// FormatSourceOrWarn форматирует Go-код и возвращает ошибку при сбое gofmt.
|
||||
func FormatSourceOrWarn(filePath string, src []byte) ([]byte, error) {
|
||||
formatted, err := format.Source(src)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: gofmt failed for %s: %v — writing unformatted code\n", filePath, err)
|
||||
return src
|
||||
return nil, fmt.Errorf("gofmt failed for %s: %w", filePath, err)
|
||||
}
|
||||
return formatted
|
||||
return formatted, nil
|
||||
}
|
||||
|
||||
// PlanModifierType возвращает суффикс типа план-модификатора (Bool/Int64/String).
|
||||
|
||||
@@ -94,11 +94,15 @@ func AlignParamTypes(params []types.Param, schema []types.Param) []types.Param {
|
||||
schemaByCode[strings.ToLower(strings.TrimSpace(p.Code))] = p
|
||||
}
|
||||
for i, p := range params {
|
||||
if !p.HasSubParams {
|
||||
key := strings.ToLower(strings.TrimSpace(p.Code))
|
||||
sp, ok := schemaByCode[key]
|
||||
if !ok || !sp.HasSubParams {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(p.Code))
|
||||
if sp, ok := schemaByCode[key]; ok && len(sp.SubParams) > 0 {
|
||||
params[i].HasSubParams = true
|
||||
if len(p.SubParams) == 0 {
|
||||
params[i].SubParams = append([]types.Param(nil), sp.SubParams...)
|
||||
} else if len(sp.SubParams) > 0 {
|
||||
params[i].SubParams = AlignParamTypes(p.SubParams, sp.SubParams)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"resource-generator/internal/types"
|
||||
)
|
||||
|
||||
func TestAlignParamTypesInheritsNestedStructure(t *testing.T) {
|
||||
schema := []types.Param{{
|
||||
Code: "jsonEnv",
|
||||
Type: "string",
|
||||
HasSubParams: true,
|
||||
SubParams: []types.Param{{
|
||||
Code: "DB_PASS",
|
||||
Type: "string",
|
||||
}},
|
||||
}}
|
||||
modify := []types.Param{{
|
||||
Code: "jsonEnv",
|
||||
Type: "map",
|
||||
}}
|
||||
|
||||
got := AlignParamTypes(modify, schema)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected one parameter, got %d", len(got))
|
||||
}
|
||||
if !got[0].HasSubParams {
|
||||
t.Fatal("expected nested structure to be inherited")
|
||||
}
|
||||
if len(got[0].SubParams) != 1 || got[0].SubParams[0].Code != "DB_PASS" {
|
||||
t.Fatalf("expected schema sub-params to be inherited, got %#v", got[0].SubParams)
|
||||
}
|
||||
if got[0].Type != "string" {
|
||||
t.Fatalf("expected type to align with schema, got %q", got[0].Type)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Code generated by TOOLS/resource-generator. DO NOT EDIT.
|
||||
// Service: {{.ServiceName}}
|
||||
// Action: {{.ActionName}}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Code generated by TOOLS/resource-generator. DO NOT EDIT.
|
||||
// Service: {{.Name}}
|
||||
// Service ID: {{.ServiceID}}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Code generated by TOOLS/resource-generator. DO NOT EDIT.
|
||||
// Service: {{.ServiceName}}
|
||||
// Subresource: {{.SubName}}
|
||||
|
||||
@@ -439,4 +439,3 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Configure(_
|
||||
r.client = client
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -59,7 +59,10 @@ func WriteInstanceResource(outDir string, svc types.GenResource) error {
|
||||
return err
|
||||
}
|
||||
|
||||
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
formatted, err := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, formatted, 0644)
|
||||
}
|
||||
@@ -91,7 +94,10 @@ func WriteSubresource(outDir string, sr types.GenSubresource) error {
|
||||
return err
|
||||
}
|
||||
|
||||
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
formatted, err := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, formatted, 0644)
|
||||
}
|
||||
@@ -121,7 +127,10 @@ func WriteActionResource(outDir string, act types.GenAction) error {
|
||||
return err
|
||||
}
|
||||
|
||||
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
formatted, err := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, formatted, 0644)
|
||||
}
|
||||
@@ -131,7 +140,7 @@ func WriteRegistry(outDir string, services []types.GenResource, subs []types.Gen
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("package resources_gen\n\n")
|
||||
buf.WriteString("import \"github.com/hashicorp/terraform-plugin-framework/resource\"\n\n")
|
||||
buf.WriteString("// Code generated by tools/gen_v2. DO NOT EDIT.\n")
|
||||
buf.WriteString("// Code generated by TOOLS/resource-generator. DO NOT EDIT.\n")
|
||||
buf.WriteString("func AllResources() []func() resource.Resource {\n")
|
||||
buf.WriteString("\treturn []func() resource.Resource{\n")
|
||||
for _, svc := range services {
|
||||
@@ -147,7 +156,10 @@ func WriteRegistry(outDir string, services []types.GenResource, subs []types.Gen
|
||||
buf.WriteString("}\n")
|
||||
|
||||
regPath := filepath.Join(outDir, "registry.go")
|
||||
formatted := helpers.FormatSourceOrWarn(regPath, buf.Bytes())
|
||||
formatted, err := helpers.FormatSourceOrWarn(regPath, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(regPath, formatted, 0644)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -23,37 +25,66 @@ import (
|
||||
"resource-generator/internal/writers"
|
||||
)
|
||||
|
||||
type serviceWriteErr struct {
|
||||
kind string
|
||||
name string
|
||||
err error
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
resourcesDir := strings.TrimSpace(os.Getenv("NUBES_RESOURCES_DIR"))
|
||||
if resourcesDir == "" {
|
||||
panic("NUBES_RESOURCES_DIR is required — must point to generated/{stand}/resources_yaml/")
|
||||
return fmt.Errorf("NUBES_RESOURCES_DIR is required — must point to generated/{stand}/resources_yaml/")
|
||||
}
|
||||
outDir := strings.TrimSpace(os.Getenv("NUBES_RESOURCES_GEN_DIR"))
|
||||
if outDir == "" {
|
||||
panic("NUBES_RESOURCES_GEN_DIR is required — must point to generated/{stand}/go/")
|
||||
return fmt.Errorf("NUBES_RESOURCES_GEN_DIR is required — must point to generated/{stand}/go/")
|
||||
}
|
||||
|
||||
instanceResources, subresources, actions, err := loader.LoadSpecs(resourcesDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var writeErrs []serviceWriteErr
|
||||
|
||||
for _, svc := range instanceResources {
|
||||
if err := writers.WriteInstanceResource(outDir, svc); err != nil {
|
||||
panic(err)
|
||||
writeErrs = append(writeErrs, serviceWriteErr{kind: "instance", name: svc.Name, err: err})
|
||||
}
|
||||
}
|
||||
for _, sr := range subresources {
|
||||
if err := writers.WriteSubresource(outDir, sr); err != nil {
|
||||
panic(err)
|
||||
name := fmt.Sprintf("%s_%s", sr.ServiceName, sr.SubName)
|
||||
writeErrs = append(writeErrs, serviceWriteErr{kind: "subresource", name: name, err: err})
|
||||
}
|
||||
}
|
||||
for _, act := range actions {
|
||||
if err := writers.WriteActionResource(outDir, act); err != nil {
|
||||
panic(err)
|
||||
name := fmt.Sprintf("%s_%s", act.ServiceName, act.ActionName)
|
||||
writeErrs = append(writeErrs, serviceWriteErr{kind: "action", name: name, err: err})
|
||||
}
|
||||
}
|
||||
if err := writers.WriteRegistry(outDir, instanceResources, subresources, actions); err != nil {
|
||||
panic(err)
|
||||
|
||||
if len(writeErrs) > 0 {
|
||||
var b strings.Builder
|
||||
b.WriteString("generation failed for one or more resources:\n")
|
||||
for _, we := range writeErrs {
|
||||
b.WriteString(fmt.Sprintf("- %s %s: %v\n", we.kind, we.name, we.err))
|
||||
}
|
||||
return errors.New(strings.TrimSpace(b.String()))
|
||||
}
|
||||
|
||||
if err := writers.WriteRegistry(outDir, instanceResources, subresources, actions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,6 +80,13 @@ if [[ ! -f "$SERVICES_LIST_PATH" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required_var in VERSION NAMESPACE PROVIDER_NAME NUBES_API_ENDPOINT REGISTRY_HOSTNAME; do
|
||||
if [[ -z "${!required_var:-}" ]]; then
|
||||
echo "Error: $required_var is required in profile.env or registry.env" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
cd "$PROVIDER_DIR"
|
||||
|
||||
echo "Generating resources from unified YAML specs..."
|
||||
@@ -96,11 +103,10 @@ cp -R "$TMP_GEN_DIR/." "$GO_OUTPUT_DIR/"
|
||||
|
||||
echo "Generating docs via template generator..."
|
||||
mkdir -p "$DOCS_DIR"
|
||||
find "$DOCS_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
|
||||
# Determine API endpoint for doc examples.
|
||||
# Use NUBES_API_ENDPOINT from profile.env, fall back to production default.
|
||||
# ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Default = Gateway. Override via NUBES_API_ENDPOINT.
|
||||
DOCS_API_ENDPOINT="${NUBES_API_ENDPOINT:-https://lk-api-gateway.ngcloud.ru/api/v1/svc}"
|
||||
# Determine API endpoint for doc examples from the selected profile.
|
||||
DOCS_API_ENDPOINT="$NUBES_API_ENDPOINT"
|
||||
# If endpoint looks like new REST gateway (no index.cfm), keep as-is.
|
||||
# If it's old-style without index.cfm, append it for backward compat in docs.
|
||||
if [[ "$DOCS_API_ENDPOINT" != *"/index.cfm"* ]] && [[ "$DOCS_API_ENDPOINT" != *"/svc"* ]]; then
|
||||
@@ -111,8 +117,9 @@ ${ROOT_DIR}/TOOLS/bin/docs-generator \
|
||||
-resources "$RESOURCES_YAML_DIR" \
|
||||
-docs "$DOCS_DIR" \
|
||||
-services "$SERVICES_LIST_PATH" \
|
||||
-version "${VERSION:-2.x}" \
|
||||
-version "$VERSION" \
|
||||
-api-endpoint "$DOCS_API_ENDPOINT" \
|
||||
-provider-source "${REGISTRY_HOSTNAME}/${NAMESPACE}/${PROVIDER_NAME}"
|
||||
-provider-source "${REGISTRY_HOSTNAME}/${NAMESPACE}/${PROVIDER_NAME}" \
|
||||
-namespace "$NAMESPACE"
|
||||
|
||||
echo "Resources and docs generated in template format."
|
||||
|
||||
@@ -36,8 +36,27 @@ if [[ -n "$PROFILE_DIR" ]]; then
|
||||
source "$PROFILE_ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
REGISTRY_ENV_FILE="${ROOT_DIR}/TOOLS/config/registry.env"
|
||||
if [[ -f "$REGISTRY_ENV_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$REGISTRY_ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$PROFILE_DIR" ]]; then
|
||||
echo "Error: --profile <path> is required" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required_var in VERSION NAMESPACE PROVIDER_NAME NUBES_API_ENDPOINT REGISTRY_HOSTNAME; do
|
||||
if [[ -z "${!required_var:-}" ]]; then
|
||||
echo "Error: $required_var is required in profile.env or registry.env" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
resolve_root_path() {
|
||||
local path_value="$1"
|
||||
if [[ -z "$path_value" ]]; then
|
||||
@@ -51,9 +70,9 @@ resolve_root_path() {
|
||||
echo "${ROOT_DIR}/${path_value}"
|
||||
}
|
||||
|
||||
VERSION="${1:-}"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
VERSION="${VERSION:-}"
|
||||
VERSION_ARG="${1:-}"
|
||||
if [[ -n "$VERSION_ARG" ]]; then
|
||||
VERSION="$VERSION_ARG"
|
||||
fi
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
VERSION=$(grep -E 'version string' "$PROVIDER_MAIN" | sed -E 's/.*"([0-9.]+)".*/\1/')
|
||||
@@ -64,9 +83,7 @@ if [[ -z "$VERSION" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-tf-registry.containerk8s.services.ngcloud.ru}"
|
||||
NAMESPACE="${NAMESPACE:-nubes}"
|
||||
PROVIDER_NAME="${PROVIDER_NAME:-nubes}"
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-tf-docs.nodejsk8s.dev.nubes.ru}"
|
||||
export REGISTRY_HOST NAMESPACE PROVIDER_NAME VERSION
|
||||
|
||||
S3CFG_REGISTRY="${S3CFG_REGISTRY:-${ROOT_DIR}/secrets/.s3cfg_registry}"
|
||||
@@ -74,8 +91,8 @@ S3CFG_REGISTRY="$(resolve_root_path "$S3CFG_REGISTRY")"
|
||||
TMP_DOCS_DIR=""
|
||||
|
||||
if [[ -n "$PROFILE_DIR" ]]; then
|
||||
# ⛔ NEVER merge with docs/ — ONLY generated docs from docs_gen/<stand>/
|
||||
DOCS_GEN_DIR="${DOCS_GEN_DIR:-generated/test}"
|
||||
# ⛔ NEVER merge with docs/ — ONLY generated docs from generated/<stand>/docs/
|
||||
DOCS_GEN_DIR="${DOCS_GEN_DIR:-generated/$(basename "$PROFILE_DIR")/docs}"
|
||||
DOCS_GEN_DIR="$(resolve_root_path "$DOCS_GEN_DIR")"
|
||||
if [[ -d "$DOCS_GEN_DIR" ]]; then
|
||||
export MKDOCS_DOCS_DIR="$DOCS_GEN_DIR"
|
||||
@@ -85,37 +102,15 @@ if [[ -n "$PROFILE_DIR" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Default = Gateway.
|
||||
DOCS_API_ENDPOINT="${NUBES_API_ENDPOINT:-https://lk-api-gateway.ngcloud.ru/api/v1/svc}"
|
||||
DOCS_API_ENDPOINT="$NUBES_API_ENDPOINT"
|
||||
DOCS_API_ENDPOINT="$(normalize_api_endpoint "$DOCS_API_ENDPOINT")"
|
||||
|
||||
if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
export DOCS_GUIDE_VERSION="$VERSION"
|
||||
export DOCS_GUIDE_API_ENDPOINT="$DOCS_API_ENDPOINT"
|
||||
export DOCS_GUIDE_FILE="${MKDOCS_DOCS_DIR}/guides/getting-started.md"
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
guide_path = Path(os.environ["DOCS_GUIDE_FILE"])
|
||||
if guide_path.exists():
|
||||
text = guide_path.read_text(encoding="utf-8")
|
||||
text = re.sub(
|
||||
r'(version\s*=\s*")([0-9.]+)("\s*#\s*Поставьте нужную вам версию провайдера)',
|
||||
lambda m: f'{m.group(1)}{os.environ["DOCS_GUIDE_VERSION"]}{m.group(3)}',
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
text = re.sub(
|
||||
r'(api_endpoint\s*=\s*")([^"]+)(")',
|
||||
lambda m: f'{m.group(1)}{os.environ["DOCS_GUIDE_API_ENDPOINT"]}{m.group(3)}',
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
guide_path.write_text(text, encoding="utf-8")
|
||||
PY
|
||||
DASHBOARD_HOST="deck"
|
||||
if [[ "$NAMESPACE" != "nubes" ]]; then
|
||||
DASHBOARD_HOST="deck-${NAMESPACE#nubes-}"
|
||||
fi
|
||||
DASHBOARD_URL="https://${DASHBOARD_HOST}.ngcloud.ru"
|
||||
PROVIDER_SOURCE="${REGISTRY_HOSTNAME}/${NAMESPACE}/${PROVIDER_NAME}"
|
||||
export DOCS_API_ENDPOINT DASHBOARD_URL PROVIDER_SOURCE
|
||||
|
||||
load_s3cfg_registry() {
|
||||
local cfg="$1"
|
||||
@@ -159,7 +154,7 @@ load_s3cfg_registry() {
|
||||
TMP_CFG="${ROOT_DIR}/.mkdocs.tmp.yml"
|
||||
export ROOT_DIR
|
||||
|
||||
# Copy 30_registry assets and curated content into docs dir
|
||||
# Copy 30_registry assets and curated content into docs dir (ДО подстановки версии)
|
||||
if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
if [[ -d "${ROOT_DIR}/docs/30_registry" ]]; then
|
||||
mkdir -p "${MKDOCS_DOCS_DIR}/30_registry"
|
||||
@@ -171,6 +166,60 @@ if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Per-стенд подстановка во все скопированные Markdown-файлы.
|
||||
if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
export DOCS_SUBSTITUTION_ROOT="$MKDOCS_DOCS_DIR"
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(os.environ["DOCS_SUBSTITUTION_ROOT"])
|
||||
values = {
|
||||
"{{NAMESPACE}}": os.environ["NAMESPACE"],
|
||||
"{{VERSION}}": os.environ["VERSION"],
|
||||
"{{NUBES_API_ENDPOINT}}": os.environ["DOCS_API_ENDPOINT"],
|
||||
"{{DASHBOARD_URL}}": os.environ["DASHBOARD_URL"],
|
||||
"{{PROVIDER_SOURCE}}": os.environ["PROVIDER_SOURCE"],
|
||||
"{{REGISTRY_HOST}}": os.environ["REGISTRY_HOSTNAME"],
|
||||
}
|
||||
legacy_provider_source = re.compile(
|
||||
r'(source\s*=\s*")registry\.kube5s\.ru/[^"\n]+(")'
|
||||
)
|
||||
for path in root.rglob("*.md"):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for placeholder, value in values.items():
|
||||
text = text.replace(placeholder, value)
|
||||
text = legacy_provider_source.sub(
|
||||
lambda match: f'{match.group(1)}{os.environ["PROVIDER_SOURCE"]}{match.group(2)}',
|
||||
text,
|
||||
)
|
||||
path.write_text(text, encoding="utf-8")
|
||||
|
||||
for path in root.rglob("*.md"):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if "{{" in text or "}}" in text:
|
||||
raise SystemExit(f"unresolved documentation placeholder: {path}")
|
||||
PY
|
||||
|
||||
foreign_namespace=""
|
||||
foreign_api=""
|
||||
foreign_dashboard=""
|
||||
case "$NAMESPACE" in
|
||||
nubes) foreign_namespace="nubes-dev|nubes-test"; foreign_api="lk-api-gateway-(dev|test)"; foreign_dashboard="deck-(dev|test)" ;;
|
||||
nubes-dev) foreign_namespace="nubes-test"; foreign_api="lk-api-gateway\.ngcloud\.ru|lk-api-gateway-test\.ngcloud\.ru"; foreign_dashboard="deck\.ngcloud\.ru|deck-test\.ngcloud\.ru" ;;
|
||||
nubes-test) foreign_namespace="nubes-dev"; foreign_api="lk-api-gateway\.ngcloud\.ru|lk-api-gateway-dev\.ngcloud\.ru"; foreign_dashboard="deck\.ngcloud\.ru|deck-dev\.ngcloud\.ru" ;;
|
||||
*) echo "Error: unsupported namespace for documentation validation: $NAMESPACE" >&2; exit 2 ;;
|
||||
esac
|
||||
if grep -RIlE "$foreign_namespace|$foreign_api|$foreign_dashboard|registry\.kube5s\.ru" "$MKDOCS_DOCS_DIR" --include='*.md' 2>/dev/null | while IFS= read -r docs_file; do
|
||||
grep -vE 'https://tf-docs\.nodejsk8s\.dev\.nubes\.ru/(nubes-dev|nubes-test|nubes)/' "$docs_file" | grep -nE "$foreign_namespace|$foreign_api|$foreign_dashboard|registry\.kube5s\.ru" && printf '%s\n' "$docs_file"
|
||||
done >/tmp/docs-stand-contamination.txt; then
|
||||
echo "Error: stand-specific contamination detected in generated docs:" >&2
|
||||
cat /tmp/docs-stand-contamination.txt >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
@@ -187,13 +236,21 @@ cfg_path = root_dir / "mkdocs.yml"
|
||||
text = cfg_path.read_text(encoding="utf-8")
|
||||
text = re.sub(
|
||||
r"^site_url:\s*.*$",
|
||||
f"site_url: https://{registry_host}/docs/{namespace}/{name}/{version}/",
|
||||
f"site_url: https://{registry_host}/{namespace}/",
|
||||
text,
|
||||
flags=re.M,
|
||||
)
|
||||
|
||||
docs_dir = os.environ.get("MKDOCS_DOCS_DIR", "").strip()
|
||||
if docs_dir:
|
||||
# Относительный docs_dir: работает и на хосте (сборка из ROOT_DIR), и в docker (/docs).
|
||||
if os.path.isabs(docs_dir):
|
||||
try:
|
||||
rel = os.path.relpath(docs_dir, os.environ.get("ROOT_DIR", os.path.sep))
|
||||
if not rel.startswith(".."):
|
||||
docs_dir = rel
|
||||
except ValueError:
|
||||
pass
|
||||
if re.search(r"^docs_dir:\s*.*$", text, flags=re.M):
|
||||
text = re.sub(r"^docs_dir:\s*.*$", f"docs_dir: {docs_dir}", text, flags=re.M)
|
||||
else:
|
||||
|
||||
+10
-7
@@ -4,9 +4,9 @@
|
||||
|
||||
| Стенд | Namespace | Версия | Дата заливки |
|
||||
|---|---|---|---|
|
||||
| DEV | `nubes-dev` | `3.0.2` | 2026-08-09 | ✅ из DEV API |
|
||||
| TEST | `nubes-test` | `5.0.1` | 2026-08-09 | ✅ |
|
||||
| PROD | `nubes` | `2.0.1` | — | |
|
||||
| PROD | `nubes` | `1.0.0` | 2026-09-03 | (новая нумерация) |
|
||||
| DEV | `nubes-dev` | `2.0.0` | 2026-09-03 | (новая нумерация) |
|
||||
| TEST | `nubes-test` | `3.0.0` | 2026-09-03 | (новая нумерация) |
|
||||
|
||||
## Как проверить
|
||||
|
||||
@@ -23,8 +23,11 @@ curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes/
|
||||
|
||||
## Схема версионирования
|
||||
|
||||
| Стенд | Первая цифра |
|
||||
> ⛔ ЕДИНСТВЕННАЯ СХЕМА (с 2026-09-03). Старые диапазоны (prod=2.*, dev=3.*,
|
||||
> test=5.*, а также 0.0.x) — ЛЕГАСИ, НЕ ИСПОЛЬЗОВАТЬ.
|
||||
|
||||
| Стенд | Диапазон |
|
||||
|---|---|
|
||||
| PROD | `2.*` |
|
||||
| DEV | `3.*` |
|
||||
| TEST | `5.*` |
|
||||
| PROD | `1.*` |
|
||||
| DEV | `2.*` |
|
||||
| TEST | `3.*` |
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
||||
/* Keep horizontal scrollbars visible for wide tables */
|
||||
.md-typeset__scrollwrap {
|
||||
/* Keep horizontal scrollbars visible for wide tables */.md-typeset__scrollwrap {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
@@ -259,6 +258,30 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- Version badge in header (right corner) ---- */
|
||||
.md-version-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
margin-left: 0.4rem;
|
||||
padding: 0.05rem 0.55rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
color: var(--md-default-bg-color);
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 2rem;
|
||||
cursor: default;
|
||||
}
|
||||
@media screen and (max-width: 44.9375em) {
|
||||
.md-version-badge {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.md-typeset .resource-table-nested th:nth-child(6),
|
||||
.md-typeset .resource-table-nested td:nth-child(6) {
|
||||
width: 37%;
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "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/nubes"
|
||||
version = "2.1.23" # Поставьте нужную вам версию провайдера
|
||||
source = "{{PROVIDER_SOURCE}}"
|
||||
version = "{{VERSION}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "nubes" {
|
||||
api_endpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
|
||||
api_endpoint = "{{NUBES_API_ENDPOINT}}"
|
||||
api_token = var.api_token
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ variable "api_token" {
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "TEST стенд (обязательные адреса)"
|
||||
Эта документация относится к TEST стенду.
|
||||
!!! info "Стенд {{NAMESPACE}} (обязательные адреса)"
|
||||
Эта документация относится к стенду `{{NAMESPACE}}`.
|
||||
|
||||
- Личный кабинет: https://deck-test.ngcloud.ru/dashboard/
|
||||
- API endpoint: https://lk-api-gateway-test.ngcloud.ru/api/v1/svc
|
||||
- Личный кабинет: {{DASHBOARD_URL}}/dashboard/
|
||||
- API endpoint: {{NUBES_API_ENDPOINT}}
|
||||
|
||||
!!! tip "Безопасность"
|
||||
Никогда не храните токен прямо в файле `main.tf`, если планируете загружать код в систему контроля версий (git). Используйте `variables.tf` или файл `terraform.tfvars`.
|
||||
@@ -55,7 +55,7 @@ variable "api_token" {
|
||||
Токен (Access Token) необходим провайдеру для авторизации ваших действий в облаке.
|
||||
|
||||
Если нет ТОКЕНА доступа или хотите создать новый -
|
||||
В Личном Кабинете - на странице Профиля пользователя https://deck.ngcloud.ru/authorization/profile
|
||||
В Личном Кабинете - на странице Профиля пользователя {{DASHBOARD_URL}}/authorization/profile
|
||||
во вкладке Токены - нажать "Выпустить тех-токен"
|
||||
Значение токена показывается только при его создании, надо его сохранить
|
||||
|
||||
@@ -248,7 +248,7 @@ resource "nubes_nodejs" "app3" {
|
||||
Ниже полный пример `resources.tf` для RabbitMQ + Lucee UI + NodeJS воркера.
|
||||
|
||||
Комментарий: UI Lucee отправляет CRUD‑запросы в RabbitMQ, а применение изменений в Postgres выполняет отдельный воркер на NodeJS.
|
||||
Сервис Postgres должен быть запущен заранее. В данном примере используется Postgres из раздела https://registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/docs/nubes/nubes/2.1.7/30_registry/guides/getting-started/#lucee-postgress
|
||||
Сервис Postgres должен быть запущен заранее. В данном примере используется Postgres из раздела https://tf-docs.nodejsk8s.dev.nubes.ru/nubes/30_registry/guides/getting-started/#lucee-postgress
|
||||
|
||||
```hcl title="resources.tf"
|
||||
# RabbitMQ кластер для демо.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
(function () {
|
||||
// ============================================================
|
||||
// Версия документации в правом углу шапки.
|
||||
// ПОВЫШАТЬ ВРУЧНУЮ ПРИ ИЗМЕНЕНИЯХ: v0.1 -> v0.2 -> v0.3 ...
|
||||
// ============================================================
|
||||
var DOCS_VERSION = 'v0.1';
|
||||
|
||||
function inject() {
|
||||
var header = document.querySelector('.md-header__inner');
|
||||
if (!header || document.querySelector('.md-version-badge')) return;
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'md-version-badge';
|
||||
badge.textContent = DOCS_VERSION;
|
||||
badge.title = 'Версия документации';
|
||||
header.appendChild(badge);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', inject);
|
||||
} else {
|
||||
inject();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,138 @@
|
||||
# Оркестрация взаимозависимых ресурсов и пошаговых модификаций (на примере Штурвал)
|
||||
|
||||
## 1. Контекст и проблематика
|
||||
|
||||
### Исходная последовательность развертывания
|
||||
Для развертывания инстанса сервиса **Штурвал** требуется подготовить сетевую и виртуальную инфраструктуру, состоящую из трёх взаимозависимых компонентов:
|
||||
1. `vcOrg/create` — создание виртуальной организации (vCD Org).
|
||||
2. `vcVdc/create` — создание виртуального дата-центра (vDC) внутри организации.
|
||||
3. `vcNsxt/create` — создание сетевого шлюза NSX-T (включение AVI, выделение 4 Service Engine).
|
||||
4. `vcOrg/modify` — модификация организации (добавление 3 внешних IP-адресов).
|
||||
5. `vcNsxt/modify` — повторная модификация NSX-T (включение SNAT, привязка выделенного `ipSpace` из `vcOrg`).
|
||||
6. `Штурвал/create` — создание кластера сервиса «Штурвал».
|
||||
|
||||
### В чём архитектурная сложность для Terraform
|
||||
В стандартной декларативной модели Terraform каждый ресурс управляется монолитно: один блок `resource` соответствует полному жизненному циклу одной сущности (Create -> Read -> Update -> Delete).
|
||||
|
||||
В описанном сценарии возникает **чередующаяся (interleaved) зависимость**:
|
||||
* `vcOrg` должен существовать до `vcVdc` и `vcNsxt`.
|
||||
* Но добавление IP-адресов в `vcOrg` (шаг 4) и настройка SNAT в `vcNsxt` (шаг 5) должны выполняться **после** создания базового `vcNsxt` (шаг 3).
|
||||
* Штурвал (шаг 6) требует, чтобы и IP-адреса, и SNAT уже были применены.
|
||||
|
||||
Если пытаться упаковать шаги 1 и 4 в один ресурс `cloud_vc_org`, а шаги 3 и 5 — в один `cloud_vc_nsxt`, возникает тупик в графе зависимостей Terraform (Directed Acyclic Graph, DAG), либо API вернет ошибку из-за несвоевременного вызова параметров.
|
||||
|
||||
---
|
||||
|
||||
## 2. Архитектурное решение: Паттерн отдельных ресурсов модификации (Subresource / Action Pattern)
|
||||
|
||||
Канонический подход в экосистеме Terraform (аналогично `aws_security_group` + `aws_security_group_rule`, `aws_vpc` + `aws_route`) — **декомпозиция отложенных действий и привязок в отдельные управляемые ресурсы провайдера**.
|
||||
|
||||
### Структура ресурсов
|
||||
1. **Базовые ресурсы жизненного цикла (Core Instances):**
|
||||
* `cloud_vc_org` — создает и держит базу организации.
|
||||
* `cloud_vc_vdc` — создает VDC внутри Org.
|
||||
* `cloud_vc_nsxt` — создает NSX-T шлюз (AVI, 4 SE).
|
||||
2. **Ресурсы отложенной конфигурации / модификаций (Action / Subresources):**
|
||||
* `cloud_vc_org_ip_allocation` (или `cloud_vc_org_modify_ip`) — управляет пулом выделенных IP-адресов организации.
|
||||
* `cloud_vc_nsxt_snat` (или `cloud_vc_nsxt_modify_snat`) — управляет правилом SNAT и связкой с `ip_space`.
|
||||
3. **Целевой сервис:**
|
||||
* `cloud_shturval` — разворачивает кластер Штурвал.
|
||||
|
||||
### Пример манифеста HCL
|
||||
|
||||
```hcl
|
||||
# 1. Создание организации
|
||||
resource "cloud_vc_org" "org" {
|
||||
name = "demo-org"
|
||||
}
|
||||
|
||||
# 2. Создание VDC
|
||||
resource "cloud_vc_vdc" "vdc" {
|
||||
name = "demo-vdc"
|
||||
org_id = cloud_vc_org.org.id
|
||||
}
|
||||
|
||||
# 3. Создание NSX-T (включение AVI и 4 Service Engine)
|
||||
resource "cloud_vc_nsxt" "nsxt" {
|
||||
name = "demo-nsxt"
|
||||
vdc_id = cloud_vc_vdc.vdc.id
|
||||
enable_avi = true
|
||||
service_engines = 4
|
||||
}
|
||||
|
||||
# 4. Модификация vcOrg: добавление 3 IP после готовности NSX-T
|
||||
resource "cloud_vc_org_ip_allocation" "org_ips" {
|
||||
org_id = cloud_vc_org.org.id
|
||||
ip_count = 3
|
||||
|
||||
# Явная зависимость гарантирует выполнение после создания NSX-T
|
||||
depends_on = [cloud_vc_nsxt.nsxt]
|
||||
}
|
||||
|
||||
# 5. Модификация vcNsxt: включение SNAT с ipSpace из vcOrg
|
||||
resource "cloud_vc_nsxt_snat" "snat" {
|
||||
nsxt_id = cloud_vc_nsxt.nsxt.id
|
||||
ip_space = cloud_vc_org_ip_allocation.org_ips.ip_space_id
|
||||
enabled = true
|
||||
}
|
||||
|
||||
# 6. Создание сервиса Штурвал
|
||||
resource "cloud_shturval" "cluster" {
|
||||
name = "demo-shturval"
|
||||
vdc_id = cloud_vc_vdc.vdc.id
|
||||
|
||||
# Зависит от полной готовности сетевой связки
|
||||
depends_on = [
|
||||
cloud_vc_nsxt_snat.snat,
|
||||
cloud_vc_org_ip_allocation.org_ips
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Terraform самостоятельно строит идеальный граф исполнения:
|
||||
```mermaid
|
||||
graph TD
|
||||
A[cloud_vc_org] --> B[cloud_vc_vdc]
|
||||
B --> C[cloud_vc_nsxt]
|
||||
C --> D[cloud_vc_org_ip_allocation]
|
||||
D --> E[cloud_vc_nsxt_snat]
|
||||
E --> F[cloud_shturval]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Интеграция в провайдер
|
||||
|
||||
### Реализация через генератор провайдера
|
||||
Согласно политике репозитория (Immutability Policy), код конкретных ресурсов не правится вручную, а генерируется:
|
||||
1. В схему генератора добавляются описания новых сущностей:
|
||||
* Тип `action` или `subresource` для вызова эндпоинтов модификации.
|
||||
* Контракты входных/выходных атрибутов (`org_id`, `ip_count`, `ip_space_id`, `nsxt_id`, `enabled`).
|
||||
2. Кодогенератор генерирует стандартные CRUD-структуры Terraform Plugin Framework / SDK.
|
||||
|
||||
### Жизненный цикл ресурсов модификации
|
||||
* **Create**:
|
||||
- Вызывает соответствующий API-метод (`POST /api/v1/vcOrg/{id}/modify` или `/api/v1/vcNsxt/{id}/modify`).
|
||||
- Дожидается применения задачи (task tracking / polling).
|
||||
- Сохраняет идентификатор операции или полученный `ip_space_id` в Terraform State.
|
||||
* **Read**:
|
||||
- Запрашивает текущее состояние родительского ресурса через GET API.
|
||||
- Проверяет, выделены ли IP / активен ли SNAT.
|
||||
* **Update**:
|
||||
- Если меняется количество IP или настройки SNAT — отправляет повторный запрос на модификацию.
|
||||
* **Delete (terraform destroy)**:
|
||||
- При уничтожении инфраструктуры порядок разворачивается в обратную сторону.
|
||||
- Сначала удаляется `cloud_shturval`.
|
||||
- Затем `cloud_vc_nsxt_snat` отключает SNAT.
|
||||
- Затем `cloud_vc_org_ip_allocation` освобождает выделенные IP.
|
||||
- И только затем удаляются базовые `vcNsxt`, `vcVdc` и `vcOrg`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Альтернативные подходы
|
||||
|
||||
1. **Smart Provider (комбинированный Create)**:
|
||||
- Если API позволяет вызывать шаги последовательно внутри одного HTTP-сеанса бэкенда, провайдер мог бы скрыть это внутри `Create` ресурса `cloud_shturval`.
|
||||
- *Минус*: теряется гибкость и прозрачность статусов; сбой на промежуточном этапе оставляет "зависшие" ресурсы в облаке без записи в tfstate.
|
||||
2. **Модули Terraform (Module Wrapper)**:
|
||||
- Описанная выше структура ресурсов упаковывается в официальный Terraform-модуль `terraform-nubes-shturval`, скрывая сложность связей от конечного пользователя и предоставляя простой интерфейс ввода параметров.
|
||||
@@ -0,0 +1,237 @@
|
||||
# Архитектурная концепция: Modifier-ресурсы (Идеология, правила и интеграция в Terraform Provider)
|
||||
|
||||
## 1. Введение и архитектурный контекст
|
||||
|
||||
### 1.1. Проблема: Чередующиеся зависимости (Interleaved Lifecycle)
|
||||
В классической декларативной модели Terraform каждый ресурс управляется монолитно: один блок `resource` соответствует полному жизненному циклу одной сущности (Create -> Read -> Update -> Delete).
|
||||
|
||||
Однако при комплексном развертывании инфраструктуры у облачного провайдера (например, цепочка для сервиса **Штурвал** `k8s_sthutrval_cluster`) возникает жесткая **чередующаяся зависимость**:
|
||||
1. `vcOrg/create` — создание тенанта (Организации).
|
||||
2. `vcVdc/create` — создание виртуального датацентра внутри Организации.
|
||||
3. `vcNsxt/create` — создание базового сетевого шлюза (Edge Gateway) с включением AVI ALB и 4 Service Engine.
|
||||
4. `vcOrg/modify` — выделение пула из 3 внешних IP-адресов в Организации (требует, чтобы NSX-T уже существовал).
|
||||
5. `vcNsxt/modify` — включение правила SNAT на шлюзе с привязкой `ipSpace`, созданного на шаге 4 (требует наличия свободных IP).
|
||||
6. `k8sSthutrvalCluster/create` — развертывание кластера Штурвал (требует настроенного SNAT, AVI и свободных IP).
|
||||
|
||||
Попытка «зашить» шаги 4 и 5 внутрь основных ресурсов `vc_org` и `vc_nsxt` приводит к тупику в графе зависимостей Terraform (DAG) или к ошибкам API из-за несвоевременного вызова параметров.
|
||||
|
||||
### 1.2. Решение: Класс Modifier-ресурсов
|
||||
Для разрешения таких зависимостей в архитектуру провайдера вводится специальный класс сущностей — **Modifier-ресурсы (Модификаторы)**.
|
||||
|
||||
* **Instance-ресурс (базовый сервис)** — отвечает за владение и жизненный цикл инстанса в облаке (`POST /create`, `GET /state`, `DELETE /delete`).
|
||||
* **Modifier-ресурс (модификатор)** — отвечает за выполнение отложенной операции конфигурирования/связывания над уже созданным инстансом (`POST /modify`), являясь самостоятельным блоком в графе Terraform.
|
||||
|
||||
---
|
||||
|
||||
## 2. Идеология Terraform: Почему это каноничный подход
|
||||
|
||||
Разделение базовой сущности и отложенных настроек/связей на отдельные ресурсы — это официальный архитектурный паттерн Terraform (**Resource Association / Separate Resource Pattern**), используемый во всех провайдерах первого эшелона:
|
||||
* **AWS**: `aws_security_group` (базовый контейнер) + `aws_security_group_rule` (отдельные правила привязки).
|
||||
* **AWS**: `aws_vpc` + `aws_route_table_association` / `aws_vpn_gateway_attachment`.
|
||||
* **GCP**: `google_project` + `google_project_iam_binding`.
|
||||
|
||||
### Преимущества подхода:
|
||||
1. **Естественный граф зависимостей (DAG)**: Terraform выстраивает порядок шагов исключительно между блоками `resource`. Вынос модификаций в отдельные ресурсы позволяет вклинивать промежуточные сервисы между созданием родителя и его донастройкой.
|
||||
2. **Симметричный и безопасный `destroy`**: При удалении стека Terraform автоматически разворачивает порядок:
|
||||
* Сначала удаляется `k8s_sthutrval_cluster`.
|
||||
* Затем Modifier шлюза отключает SNAT.
|
||||
* Затем Modifier организации освобождает выделенные IP.
|
||||
* И только потом удаляются базовые шлюз, VDC и организация.
|
||||
3. **Предсказуемый `plan` и локализация сбоев**: Любая ошибка настройки локализуется в блоке модификатора, не повреждая стейт базового инстанса.
|
||||
|
||||
---
|
||||
|
||||
## 3. Правила определения входных данных Modifier-ресурса
|
||||
|
||||
Входные данные Modifier-ресурса определяются строго детерминированно на основе официальной YAML-спецификации сервиса из API (`operations` -> `name: modify`).
|
||||
|
||||
### Правило 1: Якорь привязки (`instance_id` / `<service>_id`)
|
||||
Каждый модификатор обязан содержать ровно один обязательный атрибут привязки:
|
||||
* Имя: `instance_id` (или семантическое имя, например `org_id`, `nsxt_id`).
|
||||
* Тип: `string` (UUID).
|
||||
* В манифесте `.tf` значение передаётся как ссылка на атрибут родительского ресурса:
|
||||
```hcl
|
||||
org_id = nubes_vc_org.main.id
|
||||
```
|
||||
Это гарантирует, что Terraform выполнит модификатор **строго после** создания родителя.
|
||||
|
||||
### Правило 2: Строгая функциональная группа параметров
|
||||
Операция `modify` в API может содержать множество разнородных параметров. Модификатор инкапсулирует **только одну целевую функциональную задачу**:
|
||||
* **Для модификатора IP организации (`vc_org_ip_modifier`)**:
|
||||
* Входные параметры берутся из секции `modify` YAML `vc_org`: массив `vIPConfigure` (`name`, `count`).
|
||||
* **Для модификатора SNAT шлюза (`vc_nsxt_snat_modifier`)**:
|
||||
* Входные параметры берутся из секции `modify` YAML `vc_nsxt`: `ipSpaceName`, `needEnableAVI`, `virtualServicesCount`, `routedNetConfiguration`.
|
||||
|
||||
Все параметры операции `modify`, не относящиеся к данной задаче, в схему конкретного модификатора **не включаются**.
|
||||
|
||||
### Правило 3: Наследование типов и валидаций из YAML
|
||||
Схема атрибутов модификатора строится по существующей универсальной таблице типов провайдера:
|
||||
* Обязательность (`required`), значения по умолчанию (`default`), регулярные выражения (`regex`) и диапазоны значений наследуются напрямую из спецификации параметров YAML.
|
||||
|
||||
### Правило 4: Экспорт вычисляемых атрибутов (Computed Outputs)
|
||||
Если модификатор формирует сущность, необходимую последующим шагам, он экспортирует её как `Computed`:
|
||||
* `vc_org_ip_modifier` экспортирует `ip_space_name`.
|
||||
* Модификатор шлюза может сослаться на него напрямую:
|
||||
```hcl
|
||||
ip_space_name = nubes_vc_org_ip_modifier.ips.ip_space_name
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Жизненный цикл Modifier-ресурса в провайдере (CRUD)
|
||||
|
||||
| Метод Terraform | Вызов API облака | Поведение |
|
||||
|---|---|---|
|
||||
| **Create** | `POST /api/v1/svc/{service_id}/{instance_id}/modify` | Отправляет payload с целевыми параметрами модификации. Запускает polling задачи до статуса успешного завершения. Сохраняет ID и параметры в State. |
|
||||
| **Read** | `GET /api/v1/svc/{service_id}/{instance_id}` | Читает текущее состояние родительского инстанса. Извлекает значения целевых параметров (например, текущие IP или статус SNAT) и сверяет с State. |
|
||||
| **Update** | `POST /api/v1/svc/{service_id}/{instance_id}/modify` | Вызывается при изменении атрибутов модификатора в `.tf` файле. Отправляет обновлённый payload и ожидает завершения задачи. |
|
||||
| **Delete** | `POST /api/v1/svc/{service_id}/{instance_id}/modify` | **Откат настройки**: отправляет запрос на деактивацию конкретного функционала (отключение SNAT, обнуление/освобождение пула IP), не удаляя сам родительский инстанс. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Схема интеграции в конвейер провайдера
|
||||
|
||||
Провайдер сохраняет архитектурную чистоту и принцип неизменяемости кода конкретных сервисов (**Immutability Policy**):
|
||||
|
||||
```
|
||||
[ API Облака ]
|
||||
│
|
||||
▼
|
||||
TOOLS/scripts/01_generate_yamls.sh
|
||||
│
|
||||
▼
|
||||
[ generated/<stand>/resources_yaml/ ]
|
||||
(Спецификации стандартных сервисов)
|
||||
│
|
||||
┌───────────────────┴───────────────────┐
|
||||
▼ ▼
|
||||
[ Универсальный Генератор ] [ Модуль Модификаторов ]
|
||||
(Генерирует стандартные (Описывает схему и CRUD
|
||||
*_resource.go сервисов) для Modifier-ресурсов)
|
||||
│ │
|
||||
└───────────────────┬───────────────────┘
|
||||
▼
|
||||
[ Точка сборки: provider.go ]
|
||||
(Регистрация всех ресурсов в
|
||||
едином списке Resources(ctx))
|
||||
│
|
||||
▼
|
||||
TOOLS/scripts/03_build_...
|
||||
│
|
||||
▼
|
||||
[ Единый бинарный провайдер Nubes ]
|
||||
```
|
||||
|
||||
### Шаги интеграции:
|
||||
1. **Генерация стандартных ресурсов**: Универсальный генератор штатно обрабатывает YAML-спецификации сервисов, создавая основные ресурсы инстансов.
|
||||
2. **Добавление кода модификаторов**:
|
||||
* Файлы модификаторов реализуют интерфейс `resource.Resource` (Terraform Plugin Framework) и размещаются в кодовой базе провайдера.
|
||||
* Они используют общее ядро клиента (`provider/core/`) для отправки запросов и трекинга асинхронных операций.
|
||||
3. **Регистрация в провайдере**:
|
||||
* В функции `Resources(ctx)` провайдера фабричные методы модификаторов (например, `NewVcOrgIpModifierResource`, `NewVcNnxtSnatModifierResource`) добавляются в общий срез доступных ресурсов наряду со стандартными ресурсами сервисов.
|
||||
4. **Сборка**:
|
||||
* Провайдер компилируется в один исполняемый файл. Для пользователя Terraform новые ресурсы доступны нативно: `nubes_vc_org_ip_modifier`, `nubes_vc_nsxt_snat_modifier`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Пример сквозного использования в HCL
|
||||
|
||||
Итоговый пользовательский сценарий развертывания выглядит чисто, декларативно и прозрачно:
|
||||
|
||||
```hcl
|
||||
# 1. Создание Организации
|
||||
resource "nubes_vc_org" "org" {
|
||||
organization_type = "iaas"
|
||||
resource_realm = "sandbox.nubes.ru"
|
||||
}
|
||||
|
||||
# 2. Создание VDC
|
||||
resource "nubes_vc_vdc" "vdc" {
|
||||
organization_uid = nubes_vc_org.org.id
|
||||
network_provider = "default"
|
||||
provider_vdc = "fast-2.8"
|
||||
cpu_allocated = 80
|
||||
mem_allocated = 200
|
||||
|
||||
storage_config = [
|
||||
{
|
||||
name = "fast"
|
||||
size = 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 3. Создание базового Edge NSX-T (включение AVI и 4 SE)
|
||||
resource "nubes_vc_nsxt" "edge" {
|
||||
vdc_type = "vdc"
|
||||
vdc_uid = nubes_vc_vdc.vdc.id
|
||||
need_enable_avi = true
|
||||
virtual_services_count = 4
|
||||
|
||||
routed_net_configuration = {
|
||||
ip_addr_pool = "10.10.102.0/24"
|
||||
main_dns = "8.8.8.8"
|
||||
second_dns = "8.8.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Модификатор Org: выделение 3 IP (выполняется после Edge)
|
||||
resource "nubes_vc_org_ip_modifier" "org_ips" {
|
||||
org_id = nubes_vc_org.org.id
|
||||
|
||||
vip_configure = [
|
||||
{
|
||||
name = "shturval-ip-space"
|
||||
count = 3
|
||||
}
|
||||
]
|
||||
|
||||
# Явная зависимость гарантирует готовность Edge
|
||||
depends_on = [nubes_vc_nsxt.edge]
|
||||
}
|
||||
|
||||
# 5. Модификатор Edge: включение SNAT с ipSpace из шага 4
|
||||
resource "nubes_vc_nsxt_snat_modifier" "edge_snat" {
|
||||
nsxt_id = nubes_vc_nsxt.edge.id
|
||||
ip_space_name = nubes_vc_org_ip_modifier.org_ips.vip_configure[0].name
|
||||
|
||||
need_enable_avi = true
|
||||
virtual_services_count = 4
|
||||
|
||||
routed_net_configuration = {
|
||||
ip_addr_pool = "10.10.102.0/24"
|
||||
main_dns = "8.8.8.8"
|
||||
second_dns = "8.8.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
# 6. Развертывание кластера Штурвал
|
||||
resource "nubes_k8s_sthutrval_cluster" "cluster" {
|
||||
startup_configuration = {
|
||||
vdc_uid = nubes_vc_vdc.vdc.id
|
||||
nsxt_uid = nubes_vc_nsxt.edge.id
|
||||
cluster_name = "k8s-prod-cluster"
|
||||
}
|
||||
|
||||
control_plane_configuration = {
|
||||
count = 1
|
||||
sizing_policy = "standard-cp"
|
||||
sizing_disk = 50
|
||||
}
|
||||
|
||||
worker_configuration = [
|
||||
{
|
||||
count = 2
|
||||
sizing_policy = "standard-worker"
|
||||
sizing_disk = 50
|
||||
label_deck = true
|
||||
}
|
||||
]
|
||||
|
||||
# Требует полной готовности сетевой связки и свободных IP
|
||||
depends_on = [
|
||||
nubes_vc_nsxt_snat_modifier.edge_snat,
|
||||
nubes_vc_org_ip_modifier.org_ips
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,86 @@
|
||||
# Спецификация цепочки развертывания: vcOrg -> vcVdc -> vcNsxt -> k8sSthutrvalCluster (DEV Stand)
|
||||
|
||||
Документ описывает точные параметры и операции сервисов DEV-стенда из `generated/dev/resources_yaml/`, необходимые для оркестрации цепочки развертывания кластера Штурвал (`k8s_sthutrval_cluster`, ID 150).
|
||||
|
||||
---
|
||||
|
||||
## 1. Сводная таблица шагов
|
||||
|
||||
| Шаг | Действие | Сервис (ID) | Операция | Ключевые параметры |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `vcOrg/create` | `vc_org` (19) | `create` (136) | `resourceRealm`, `organizationType = "iaas"`, `orgSuffix` |
|
||||
| 2 | `vcVdc/create` | `vc_vdc` (21) | `create` (9) | `organizationUid` (ссылка на Org), `providerVdc`, `networkProvider`, `storageConfig`, `cpuAllocated`, `memAllocated` |
|
||||
| 3 | `vcNsxt/create` | `vc_nsxt` (22) | `create` (10) | `vdcType = "vdc"`, `vdcUid` (ссылка на VDC), `needEnableAVI = true`, `virtualServicesCount = 4`, `routedNetConfiguration` |
|
||||
| 4 | `vcOrg/modify` | `vc_org` (19) | `modify` (207) | `vIPConfigure`: `name` (ipSpace), `count = 3` |
|
||||
| 5 | `vcNsxt/modify` | `vc_nsxt` (22) | `modify` (111) | `ipSpaceName` (имя из шага 4), `needEnableAVI = true`, `virtualServicesCount = 4`, `routedNetConfiguration` |
|
||||
| 6 | `k8sSthutrvalCluster/create` | `k8s_sthutrval_cluster` (150) | `create` (108) | `startupConfiguration`: `vdcUid`, `nsxtUid`, `clusterName`; `controlPlaneConfiguration`; `workerConfiguration` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Детальная спецификация параметров из YAML DEV
|
||||
|
||||
### Шаг 1: `vc_org` (ID 19) — `create` (id: 136)
|
||||
*Источник: `generated/dev/resources_yaml/19_vc_org.yaml`*
|
||||
* `resourceRealm` (`string`, required, default: `sandbox.nubes.ru`) — целевое облако.
|
||||
* `organizationType` (`string`, required, default: `iaas`, values: `iaas`, `saas`) — тип тенанта (`iaas` для доступа в Keycloak).
|
||||
* `orgSuffix` (`string`, optional, regex: `^[0-9a-z]+$`, 3–10 символов) — суффикс организации.
|
||||
|
||||
### Шаг 2: `vc_vdc` (ID 21) — `create` (id: 9)
|
||||
*Источник: `generated/dev/resources_yaml/21_vc_vdc.yaml`*
|
||||
* `organizationUid` (`uuid`, required, ref: 19) — UUID созданной организации `vc_org`.
|
||||
* `networkProvider` (`string`, required) — сетевой провайдер платформы.
|
||||
* `providerVdc` (`string`, required) — пул ресурсов Cloud Director.
|
||||
* `storageConfig` (`array-map-fixed`, required):
|
||||
* `name` (`string`, required) — имя storage-политики.
|
||||
* `size` (`integer > 0`, required, default: `2000`) — размер хранилища в ГБ.
|
||||
* `cpuGuaranteed` (`integer >= 0`, required, values: `0`, `50`, `80`, default: `0`).
|
||||
* `cpuAllocated` (`integer > 0`, required, default: `80`).
|
||||
* `memAllocated` (`integer > 0`, required, default: `200`).
|
||||
|
||||
### Шаг 3: `vc_nsxt` (ID 22) — `create` (id: 10)
|
||||
*Источник: `generated/dev/resources_yaml/22_vc_nsxt.yaml`*
|
||||
* `vdcType` (`string`, required, default: `vdc`, values: `vdc`, `vdcGroup`).
|
||||
* `vdcUid` (`string`, required при `vdcType == "vdc"`, ref: 21) — UUID инстанса `vc_vdc`.
|
||||
* `needEnableAVI` (`boolean`, required, default: `false`) — **значение: `true`** (активация AVI Load Balancer).
|
||||
* `virtualServicesCount` (`integer > 0`, 1..4, default: `1`) — **значение: `4`** (Service Engine / резерв VS).
|
||||
* `routedNetConfiguration` (`map-fixed`, required):
|
||||
* `ipAddrPool` (`string`, default: `10.10.102.0/24`) — CIDR routed-сети.
|
||||
* `mainDns` (`string`, default: `8.8.8.8`).
|
||||
* `secondDns` (`string`, default: `8.8.4.4`).
|
||||
|
||||
### Шаг 4: `vc_org` (ID 19) — `modify` (id: 207)
|
||||
*Источник: `generated/dev/resources_yaml/19_vc_org.yaml`*
|
||||
* `vIPConfigure` (`array-map-fixed`, required) — добавление внешних IP:
|
||||
* `name` (`string`, required) — имя пула / ipSpace.
|
||||
* `count` (`integer > 0`, required) — **значение: `3`**.
|
||||
* *Условие API*: выполняется строго после создания VDC и Edge Gateway.
|
||||
|
||||
### Шаг 5: `vc_nsxt` (ID 22) — `modify` (id: 111)
|
||||
*Источник: `generated/dev/resources_yaml/22_vc_nsxt.yaml`*
|
||||
* `ipSpaceName` (`string`, optional) — **имя ipSpace**, заданное на шаге 4 (`vIPConfigure[].name`). Включает SNAT.
|
||||
* `needEnableAVI` (`boolean`, optional) — `true`.
|
||||
* `virtualServicesCount` (`integer > 0`, 1..4, optional) — `4`.
|
||||
* `routedNetConfiguration` (`map-fixed`, required):
|
||||
* `ipAddrPool`, `mainDns`, `secondDns`.
|
||||
* *Условие API*: создание правила SNAT требует наличия свободных IP в организации.
|
||||
|
||||
### Шаг 6: `k8s_sthutrval_cluster` (ID 150) — `create` (id: 108)
|
||||
*Источник: `generated/dev/resources_yaml/150_k8s_sthutrval_cluster.yaml`*
|
||||
* `startupConfiguration` (`map-fixed`, required):
|
||||
* `vdcUid` (`string`, required) — UUID инстанса `vc_vdc`.
|
||||
* `nsxtUid` (`string`, required) — UUID инстанса `vc_nsxt` (после настройки SNAT).
|
||||
* `clusterName` (`string`, required, regex: `(?=^.{1,63}$)^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`).
|
||||
* Флаги расширений (`boolean`, defaults: `true`): `exIngress`, `exLogging`, `exMonitoring`, `exVip`, `exNamedCsi`, `exLocalCsi`, `exUpdate`.
|
||||
* `controlPlaneConfiguration` (`map-fixed`, required):
|
||||
* `count` (`integer > 0`, values: `1`, `3`, `5`, default: `1`).
|
||||
* `sizingPolicy` (`string`, required).
|
||||
* `sizingDisk` (`integer > 0`, required, default: `50`).
|
||||
* `workerConfiguration` (`array-map-fixed`, required):
|
||||
* `count` (`integer > 0`, required, default: `2`).
|
||||
* `sizingPolicy` (`string`, required).
|
||||
* `sizingDisk` (`integer > 0`, required, default: `50`).
|
||||
* `labelDeck` (`boolean`, required, default: `true`).
|
||||
* `autoscale` (`boolean`, optional, default: `false`).
|
||||
* `autoscaleMin` (`integer > 0`, optional, default: `2`).
|
||||
* `autoscaleMax` (`integer > 0`, optional, default: `3`).
|
||||
* *Условие API*: перед разворачиванием кластера в пуле должно быть не менее 2 свободных невыделенных IP.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Архитектурный паттерн Terraform: Ресурсы привязок и модификаций (Resource Association Pattern)
|
||||
|
||||
## 1. Канонический стандарт Terraform
|
||||
|
||||
Разделение базовой сущности и её отложенных настроек/модификаций на самостоятельные ресурсы в Terraform является индустриальным стандартом (**Resource Association / Separate Resource Pattern**), рекомендованным HashiCorp и повсеместно используемым в провайдерах первого эшелона (AWS, Google Cloud, Azure, OpenStack).
|
||||
|
||||
### Примеры из мировой практики:
|
||||
* **AWS Security Groups**:
|
||||
* Базовый ресурс: `aws_security_group` (создание пустой группы).
|
||||
* Ресурс настройки: `aws_security_group_rule` (отдельное правило ingress/egress).
|
||||
* *Причина*: разрыв взаимных и циклических зависимостей, когда правила одной группы ссылаются на другую.
|
||||
* **AWS VPC & Routing**:
|
||||
* Базовые ресурсы: `aws_vpc`, `aws_route_table`, `aws_subnet`.
|
||||
* Ресурсы привязок: `aws_route_table_association`, `aws_vpn_gateway_attachment`.
|
||||
* **IAM (GCP / AWS)**:
|
||||
* Базовые сущности: `aws_iam_user`, `aws_iam_role`.
|
||||
* Ресурсы привязок прав: `aws_iam_user_policy_attachment`, `google_project_iam_binding`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Почему идеология Terraform требует именно отдельных ресурсов
|
||||
|
||||
### 1. Управление графом зависимостей (DAG — Directed Acyclic Graph)
|
||||
Terraform строит граф вычислений и определяет строгий порядок выполнения исключительно на уровне **декларативных блоков `resource`**.
|
||||
* Если операция (например, добавление внешних IP в `vcOrg` или активация SNAT в `vcNsxt`) «спрятана» внутри одного монолитного ресурса, движок Terraform не может вклинить между этапами создание промежуточных объектов (`vcVdc`, базовый `vcNsxt`).
|
||||
* Выделение модификации в отдельный ресурс даёт Terraform возможность явно связать зависимости:
|
||||
```
|
||||
vcOrg (создание)
|
||||
└── vcVdc (создание)
|
||||
└── vcNsxt (базовое создание)
|
||||
└── vcOrg_ip_allocation (модификация Org, зависит от nsxt)
|
||||
└── vcNsxt_snat (модификация Edge, зависит от ip_allocation)
|
||||
└── k8s_sthutrval_cluster (зависит от snat)
|
||||
```
|
||||
|
||||
### 2. Симметричный и безопасный `terraform destroy`
|
||||
В монолитном подходе удаление инфраструктуры часто приводит к сбоям: родительский ресурс пытается удалиться раньше дочерних привязок.
|
||||
В паттерне отдельных ресурсов Terraform автоматически обращает граф вспять:
|
||||
1. Удаляется кластер `k8s_sthutrval_cluster`.
|
||||
2. Ресурс `vcNsxt_snat` отключает SNAT на шлюзе.
|
||||
3. Ресурс `vcOrg_ip_allocation` освобождает выделенные IP-адреса.
|
||||
4. Удаляются базовые `vcNsxt`, `vcVdc` и `vcOrg`.
|
||||
|
||||
### 3. Предсказуемость плана и изоляция сбоев
|
||||
* Любые изменения видны пользователю в `terraform plan` как точечные действия над конкретными ресурсами.
|
||||
* Если падает сетевая модификация, ошибка локализуется в конкретном блоке ресурса привязки, а инфраструктура в Terraform State не переходит в поврежденное («зависшее») состояние.
|
||||
|
||||
---
|
||||
|
||||
## 3. Точки изменений в пайплайне генерации провайдера Nubes
|
||||
|
||||
Архитектура провайдера строго следует **Immutability Policy**: код конкретных ресурсов генерируется автоматически из универсальных шаблонов.
|
||||
|
||||
Изменения для поддержки данного паттерна вносятся строго в универсальные слои генератора:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. TOOLS/yaml-generator/ │
|
||||
│ Выделение операций modify/настроек в схеме YAML: │
|
||||
│ kind: subresource / kind: association_resource │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ (генерация YAML)
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 2. generated/<stand>/resources_yaml/*.yaml │
|
||||
│ Декларативное описание схемы привязок и их параметров │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ (вход для генератора кода)
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. TOOLS/resource-generator/ │
|
||||
│ - templates/: универсальные шаблоны для association-ресурсов │
|
||||
│ - Генерация Create (вызов modify), Read (GET инстанса), │
|
||||
│ Delete (откат настройки) │
|
||||
│ - Автоматическая регистрация новых ресурсов в provider.go │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ (компиляция)
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. provider/core/ │
|
||||
│ Универсальный CRUD-слой для ожидания тасок модификации (polling) │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1. `TOOLS/yaml-generator/`
|
||||
* Модификации, содержащие отложенные сетевые/квотные параметры (`vIPConfigure`, `ipSpaceName/snat`), размечаются как отдельные дочерние сущности (ассоциации) родительского сервиса.
|
||||
* Формируются контракты параметров: ссылка на родителя (`instance_id`), изменяемые параметры, возвращаемые идентификаторы.
|
||||
|
||||
### 2. `TOOLS/resource-generator/`
|
||||
* Добавляется универсальный кодогенератор ресурсов-модификаторов (association/attachment resources).
|
||||
* Логика CRUD:
|
||||
* **Create**: отправка запроса `POST /api/v1/svc/{service_id}/{instance_id}/modify`.
|
||||
* **Read**: запрос текущего состояния родителя `GET /api/v1/svc/{service_id}/{instance_id}` и извлечение привязанных настроек.
|
||||
* **Update**: повторный `modify` при изменении полей.
|
||||
* **Delete**: запрос `modify` с возвратом к дефолтному состоянию (отключение SNAT / освобождение пула IP).
|
||||
* Ресурсы регистрируются в едином перечне провайдера.
|
||||
|
||||
### 3. `provider/core/`
|
||||
* Универсальное ядро уже содержит абстракции работы с API и polling-задач. Проверяется корректность обработки асинхронных операций `modify` до их полного перехода в статус готовности.
|
||||
|
||||
### Скрипты конвейера остаются неизменными:
|
||||
* `01_generate_yamls.sh`
|
||||
* `02_generate_resources_and_docs_v2.sh`
|
||||
* `03_build_and_upload_provider.sh`
|
||||
Порядок сборки и публикации не меняется.
|
||||
@@ -27,8 +27,8 @@ s3_name = "my-s3" # ИМЯ экземпляра S3 (не UUID
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.5"
|
||||
source = "{{PROVIDER_SOURCE}}"
|
||||
version = "{{VERSION}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ variable "s3_name" { type = string }
|
||||
|
||||
provider "nubes" {
|
||||
api_token = var.api_token
|
||||
api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||||
api_endpoint = "{{NUBES_API_ENDPOINT}}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
site_name: Провайдер Terraform Nubes
|
||||
site_url: https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes/nubes/2.0.1/
|
||||
site_url: /
|
||||
|
||||
exclude_docs: |
|
||||
README.md
|
||||
@@ -35,6 +35,7 @@ extra_css:
|
||||
|
||||
extra_javascript:
|
||||
- 30_registry/javascripts/fix-slash.js
|
||||
- 30_registry/javascripts/version-badge.js
|
||||
|
||||
markdown_extensions:
|
||||
- md_in_html
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Проверка решения бага Dev-генератора
|
||||
|
||||
Ты выполняешь короткий read-only review. Ничего не меняй, не запускай генерацию,
|
||||
не собирай и не публикуй провайдер.
|
||||
|
||||
## Задача
|
||||
|
||||
Проверь, правильно ли диагностирован баг и правильно ли предложено решение:
|
||||
|
||||
1. `create.jsonEnv` может быть nested (`sub_params`), а `modify.jsonEnv` — без
|
||||
`sub_params`.
|
||||
2. `Merge` формирует каноническую схему из параметров операций.
|
||||
3. `AlignParamTypes` выравнивает типы, но не переносит `HasSubParams/SubParams`,
|
||||
если у operation-параметра `HasSubParams` изначально false.
|
||||
4. Шаблон `Update` поэтому генерирует scalar-вызовы для поля, которое в модели
|
||||
является nested-структурой.
|
||||
5. Универсальное решение — нормализовать каждый набор operation params
|
||||
относительно канонической `SchemaParams`, рекурсивно наследуя структурные
|
||||
свойства, без условий по стенду или сервису.
|
||||
|
||||
## Прочитать только эти файлы
|
||||
|
||||
1. `TOOLS/resource-generator/internal/params/params.go`
|
||||
2. `TOOLS/resource-generator/internal/loader/loader.go`
|
||||
3. `TOOLS/resource-generator/internal/helpers/helpers.go` — только функции
|
||||
`IsNested` и связанные с nested-моделями
|
||||
4. `TOOLS/resource-generator/internal/templates/instance.go` — только участки
|
||||
`Update` и проверки `IsNested`
|
||||
5. `TOOLS/resource-generator/internal/types/types.go`
|
||||
6. `generated/dev/resources_yaml/95_nodejs.yaml` — только `jsonEnv` в create и modify
|
||||
7. `generated/dev/go/95_nodejs_resource.go` — только модель `JsonEnv` и `Update`
|
||||
|
||||
Не изучай остальные сервисы, стенды, историю проекта или API вне этих файлов.
|
||||
|
||||
## Формат ответа
|
||||
|
||||
Ответь максимум в 5 коротких пунктах:
|
||||
|
||||
- **Вердикт:** прав / частично прав / неправ.
|
||||
- **Доказательство:** одна конкретная цепочка от YAML до ошибочного Go-кода.
|
||||
- **Решение:** корректно ли выравнивать operation params по канонической схеме.
|
||||
- **Риск:** один главный риск предлагаемого решения.
|
||||
- **Итог:** что именно нужно изменить или что менять не следует.
|
||||
|
||||
Не предлагай реализацию, diff, рефакторинг или дополнительные исследования.
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -90,6 +91,25 @@ var ctxKeyLogLevel = ctxKeyLogLevelType{}
|
||||
// Go-http-client по умолчанию блокируется фильтром ddos-guard.
|
||||
const userAgent = "Mozilla/5.0"
|
||||
|
||||
func debugLogPath(fileName string) string {
|
||||
if dir := strings.TrimSpace(os.Getenv("NUBES_DEBUG_DIR")); dir != "" {
|
||||
return filepath.Join(dir, fileName)
|
||||
}
|
||||
return filepath.Join(os.TempDir(), fileName)
|
||||
}
|
||||
|
||||
func isHTTPDebugEnabled() bool {
|
||||
return os.Getenv("NUBES_DEBUG_HTTP") == "1"
|
||||
}
|
||||
|
||||
func sanitizeAuthHeader(h http.Header) http.Header {
|
||||
cloned := h.Clone()
|
||||
if cloned.Get("Authorization") != "" {
|
||||
cloned.Set("Authorization", "Bearer [REDACTED]")
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
// CtxWithLogLevel возвращает ctx с переопределённым уровнем логирования.
|
||||
func CtxWithLogLevel(ctx context.Context, level string) context.Context {
|
||||
return context.WithValue(ctx, ctxKeyLogLevel, level)
|
||||
@@ -621,7 +641,10 @@ type InstanceStateResponse struct {
|
||||
// Если найдено больше одного non-deleted инстанса — возвращает ошибку.
|
||||
func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, serviceId int, displayName string) (*InstanceStateResponse, error) {
|
||||
debug := func(format string, args ...interface{}) {
|
||||
f, err := os.OpenFile("/tmp/nubes_find_debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if !isHTTPDebugEnabled() {
|
||||
return
|
||||
}
|
||||
f, err := os.OpenFile(debugLogPath("nubes_find_debug.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -684,6 +707,8 @@ func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, service
|
||||
// Если быстрый поиск не дал результатов — пагинированный fallback
|
||||
if len(found) == 0 {
|
||||
debug("[FIND-DEBUG] search path failed, entering fallback (serviceId=%d, name=%q)", serviceId, displayName)
|
||||
const maxFallbackPages = 100
|
||||
hitPageLimit := false
|
||||
page := 1
|
||||
for {
|
||||
reqURL := c.buildURL(fmt.Sprintf("/instances?page=%d&size=100", page))
|
||||
@@ -753,10 +778,15 @@ func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, service
|
||||
}
|
||||
|
||||
page++
|
||||
if page > 100 {
|
||||
if page > maxFallbackPages {
|
||||
hitPageLimit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hitPageLimit && len(found) == 0 {
|
||||
return nil, fmt.Errorf("поиск инстанса по display_name достиг лимита пагинации (%d страниц); сузьте фильтр или повторите с более точными параметрами", maxFallbackPages)
|
||||
}
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
@@ -778,58 +808,14 @@ func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, service
|
||||
}
|
||||
|
||||
func (c *UniversalClient) GetInstanceState(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
|
||||
const maxRetries = 3
|
||||
baseDelay := 2 * time.Second
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
select {
|
||||
case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
url := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if c.ApiToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
|
||||
}
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 401 {
|
||||
lastErr = fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Instance InstanceStateResponse `json:"instance"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateInstanceStatus(&res.Instance); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Instance, nil
|
||||
state, err := c.getInstanceStateWithRetry(ctx, instanceUid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("GetInstanceState failed after %d retries: %w", maxRetries, lastErr)
|
||||
if err := validateInstanceStatus(state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func isInstanceDeleted(state *InstanceStateResponse) bool {
|
||||
@@ -845,54 +831,23 @@ func isInstanceDeleted(state *InstanceStateResponse) bool {
|
||||
// GetInstanceStateRaw получает состояние инстанса БЕЗ валидации статуса.
|
||||
// Используется для проверки ref-параметров: нужно читать даже deleted/suspended инстансы.
|
||||
func (c *UniversalClient) GetInstanceStateRaw(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
|
||||
const maxRetries = 3
|
||||
baseDelay := 2 * time.Second
|
||||
return c.getInstanceStateWithRetry(ctx, instanceUid)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
select {
|
||||
case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
reqURL := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if c.ApiToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
|
||||
}
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 401 {
|
||||
lastErr = fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var res struct {
|
||||
Instance InstanceStateResponse `json:"instance"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Instance, nil
|
||||
func (c *UniversalClient) getInstanceStateWithRetry(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
|
||||
respBody, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instances/%s", instanceUid), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("GetInstanceStateRaw failed after %d retries: %w", maxRetries, lastErr)
|
||||
|
||||
var res struct {
|
||||
Instance InstanceStateResponse `json:"instance"`
|
||||
}
|
||||
if err := json.Unmarshal(respBody, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Instance, nil
|
||||
}
|
||||
|
||||
// ===== UNIVERSAL OPERATION WAIT (APPEND-ONLY) =====
|
||||
@@ -1096,14 +1051,16 @@ func (c *UniversalClient) doRequest(ctx context.Context, method, path string, pa
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
if os.Getenv("NUBES_DEBUG_HTTP") == "1" {
|
||||
dump, _ := httputil.DumpRequestOut(req, body != nil)
|
||||
if isHTTPDebugEnabled() {
|
||||
reqForDump := req.Clone(req.Context())
|
||||
reqForDump.Header = sanitizeAuthHeader(req.Header)
|
||||
dump, _ := httputil.DumpRequestOut(reqForDump, body != nil)
|
||||
fmt.Fprintf(os.Stderr, "\n>>> REQ %s %s\n%s\n", method, path, dump)
|
||||
}
|
||||
|
||||
// DEBUG в файл
|
||||
if os.Getenv("NUBES_DEBUG_HTTP") == "1" {
|
||||
f, _ := os.OpenFile("/tmp/nubes_debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if isHTTPDebugEnabled() {
|
||||
f, _ := os.OpenFile(debugLogPath("nubes_debug.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if f != nil {
|
||||
fmt.Fprintf(f, ">>> %s %s\n", method, req.URL.String())
|
||||
f.Close()
|
||||
@@ -1112,8 +1069,8 @@ func (c *UniversalClient) doRequest(ctx context.Context, method, path string, pa
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
// Сетевые ошибки — retry (кроме последней попытки)
|
||||
if attempt < maxRetries {
|
||||
// Сетевые ошибки: retry только для идемпотентного GET.
|
||||
if attempt < maxRetries && method == "GET" {
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
@@ -1149,8 +1106,7 @@ func isRetryable(statusCode int) bool {
|
||||
return statusCode == http.StatusTooManyRequests || // 429
|
||||
statusCode == http.StatusServiceUnavailable || // 503
|
||||
statusCode == http.StatusBadGateway || // 502
|
||||
statusCode == http.StatusGatewayTimeout || // 504
|
||||
statusCode == http.StatusUnauthorized // 401 — Gateway иногда отбрасывает валидный JWT
|
||||
statusCode == http.StatusGatewayTimeout // 504
|
||||
}
|
||||
|
||||
func (c *UniversalClient) postIgnoreResponse(ctx context.Context, path string, payload interface{}, returnLocation bool) (string, error) {
|
||||
@@ -1197,7 +1153,7 @@ func extractUIDFromLocation(loc string) string {
|
||||
// Однако в state UUID должен сохраняться в том регистре, который написал пользователь
|
||||
// (иначе plan != state → "Provider produced inconsistent result after apply").
|
||||
// Восстановление регистра делается в Create/Read/Update generated resource, см. шаблон
|
||||
// instanceTemplate в tools/gen_v2/generate_resources_v2.go (блоки "Restore user-provided casing").
|
||||
// instanceTemplate в TOOLS/resource-generator/internal/templates/instance.go (блоки "Restore user-provided casing").
|
||||
|
||||
func (c *UniversalClient) resolveRefSvcParamValues(ctx context.Context, opParams []universalCfsParam, params map[int]string) (map[int]string, error) {
|
||||
if len(params) == 0 || len(opParams) == 0 {
|
||||
|
||||
@@ -52,6 +52,7 @@ class CloudInstanceChecker:
|
||||
|
||||
# ⛔ LEGACY: deck-api ЗАКРЫВАЕТСЯ. Использовать lk-api-gateway.
|
||||
BASE_URL = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
|
||||
REQUEST_TIMEOUT = 30
|
||||
|
||||
def __init__(self, token: str):
|
||||
"""
|
||||
@@ -88,7 +89,11 @@ class CloudInstanceChecker:
|
||||
"pageSize": "100"
|
||||
}
|
||||
|
||||
response = self.session.get(f"{self.BASE_URL}/instances", params=params)
|
||||
response = self.session.get(
|
||||
f"{self.BASE_URL}/instances",
|
||||
params=params,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
@@ -120,7 +125,11 @@ class CloudInstanceChecker:
|
||||
"fields": "instanceConfigDtCreated,instanceUid,displayName,descr,svc,state,operations,availableOperations,uptime,isDeleted,updaterLogin,updaterShortname,explainedStatus,man,dependencies,dependentInstances,svcExtendedName"
|
||||
}
|
||||
|
||||
response = self.session.get(f"{self.BASE_URL}/instances/{instance_uid}", params=params)
|
||||
response = self.session.get(
|
||||
f"{self.BASE_URL}/instances/{instance_uid}",
|
||||
params=params,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json().get("instance")
|
||||
@@ -184,7 +193,7 @@ class CloudInstanceChecker:
|
||||
operations = instance_data.get("operations", [])
|
||||
if operations:
|
||||
last_op = operations[-1]
|
||||
if last_op.get("isSuccessful") == False:
|
||||
if last_op.get("isSuccessful") is False:
|
||||
return InstanceState.OPERATION_FAILED
|
||||
|
||||
return InstanceState.UNKNOWN
|
||||
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Заливка собранного MkDocs-сайта (site/) в S3-реестр (bucket terraform-registry).
|
||||
# Без версии: docs/<namespace>/<name>/ — перезаписывается (mc mirror).
|
||||
|
||||
# Usage: publish-docs.sh <site-dir> <registry-host> <namespace> <name>
|
||||
SITE_DIR=${1:-site}
|
||||
REGISTRY_HOST=${2:-tf-registry.containerk8s.services.ngcloud.ru}
|
||||
NAMESPACE=${3:-nubes}
|
||||
NAME=${4:-nubes}
|
||||
|
||||
# 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}/"
|
||||
|
||||
# Перезапись + удаление лишнего (без версии)
|
||||
mc mirror --overwrite --remove "$SITE_DIR/" "$TARGET"
|
||||
# Публичная политика на бакет
|
||||
mc policy set public "$TARGET" || true
|
||||
|
||||
echo "Published docs to: https://${REGISTRY_HOST}/${NAMESPACE}/"
|
||||
@@ -63,10 +63,18 @@ if [[ -z "$ACCESS_KEY" || -z "$SECRET_KEY" || -z "$HOST_BASE" ]]; then
|
||||
fi
|
||||
|
||||
mc alias rm "$ALIAS_NAME" >/dev/null 2>&1 || true
|
||||
mc alias set "$ALIAS_NAME" "https://$HOST_BASE" "$ACCESS_KEY" "$SECRET_KEY" >/dev/null
|
||||
|
||||
MC_CONFIG_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$MC_CONFIG_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
export MC_HOST_${ALIAS_NAME}="https://${ACCESS_KEY}:${SECRET_KEY}@${HOST_BASE}"
|
||||
mc --config-dir "$MC_CONFIG_DIR" alias ls >/dev/null
|
||||
|
||||
echo "[1/3] Текущие notification-правила в бакете $BUCKET:"
|
||||
mc event ls "$ALIAS_NAME/$BUCKET" || true
|
||||
mc --config-dir "$MC_CONFIG_DIR" event ls "$ALIAS_NAME/$BUCKET" || true
|
||||
|
||||
echo "[2/3] Добавление правила: events=$EVENTS target=$TARGET_ARN"
|
||||
EVENT_FLAGS=()
|
||||
@@ -83,9 +91,9 @@ if [[ -n "$SUFFIX" ]]; then
|
||||
FILTER_FLAGS+=("--suffix" "$SUFFIX")
|
||||
fi
|
||||
|
||||
mc event add "$ALIAS_NAME/$BUCKET" "$TARGET_ARN" "${EVENT_FLAGS[@]}" "${FILTER_FLAGS[@]}"
|
||||
mc --config-dir "$MC_CONFIG_DIR" event add "$ALIAS_NAME/$BUCKET" "$TARGET_ARN" "${EVENT_FLAGS[@]}" "${FILTER_FLAGS[@]}"
|
||||
|
||||
echo "[3/3] Проверка после добавления:"
|
||||
mc event ls "$ALIAS_NAME/$BUCKET"
|
||||
mc --config-dir "$MC_CONFIG_DIR" event ls "$ALIAS_NAME/$BUCKET"
|
||||
|
||||
echo "OK: правило уведомлений настроено"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
Ntazetdinov@nubes.ru
|
||||
https://api.aillm.ru/
|
||||
sk-ucI5YvOticoOQ9Kuj5K9mQ
|
||||
|
||||
|
||||
Deepseek flash
|
||||
sk-78ec529c1eba4ba69995091046c9fa33
|
||||
|
||||
cicd
|
||||
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImV4cCI6MTc5ODI3MTUyMSwiaWF0IjoxNzgyNzE5NTIxLCJqdGkiOiI5NjQ2MDlmYy05ZGZiLTQ1YjMtYjk0NS1lNmE0NmUzMTA0MzQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAxMzI1IiwiY29tcGFueV9pZCI6IjNlNjRhYWM2LWRjZmMtNDA4Mi04OGRjLWRhMTljODY1NTVhNSIsImNvbXBhbnlfbmFtZSI6ItCi0LXRgdGCIiwidG9rZW5fdHlwZSI6InRlY2giLCJpZHBfdXNyX3VpZCI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImxvZ2luIjoidGF6ZXRAbmFyb2QucnUiLCJmaXJzdG5hbWUiOiLQndCw0LjQu9GMIiwibWlkZGxlbmFtZSI6ItCk0LDRgNC40YHQvtCy0LjRhyDQotC10YHRgtC-0LLQsNGPINGD0YfQtdGC0LrQsCIsImxhc3RuYW1lIjoi0KLQsNC30LXRgtC00LjQvdC-0LIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRAbmFyb2QucnUifQ.T2cSkKGlorUTr_ICpInwrZZ2Sqk_D-RHpibrj1VI-7Bg7CPvIKJ7n1QF9bJc9uqWH9cwQczrNsA8sROU3lnqUaa88hl_rMfP7UM_u8X_iG-_pKYD8tsckmmcos6keh2I9muSZ9Viy9LvLCZv3fY6nzMp2YT-KCQh-EDGZPgHSAToWs1uqiaKi99K-OcqckvaFNUsYbpLPVfnD_6UnDDKUmPjP4Ib24R4Z5qlmwUAxmgC6BfUcuqgk-2Mdj37ulWvdlBLd9ZoJv4jAvRffzclv2w-Qa8p3BEooC8wlZjTC3PU-ULR-Cd_N61Y31lkd953kkKE3_yGIQCbwCPYus1TiA
|
||||
|
||||
gitea
|
||||
1999993c70d97cc588cca420e59ac45d5290b734
|
||||
|
||||
f59211fe7716b5f881051127c6cb8afcb7ec0df1- for IOT&company
|
||||
Reference in New Issue
Block a user