Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33672e05bf | ||
|
|
0464a30642 | ||
|
|
106ddbe092 | ||
|
|
dbaeea5c89 | ||
|
|
7eb8eb8859 | ||
|
|
7a25ad8fc9 | ||
|
|
a222a0dace | ||
|
|
bba6b47dc2 | ||
|
|
ccb458a167 | ||
|
|
aa0f7f6402 | ||
|
|
6bf514e03a | ||
|
|
8d5bbd368d | ||
|
|
423c74d3f1 | ||
|
|
085a310720 | ||
|
|
b76e0d1086 |
@@ -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,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,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,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.
|
||||
@@ -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+
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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/')
|
||||
@@ -65,8 +84,6 @@ if [[ -z "$VERSION" ]]; then
|
||||
fi
|
||||
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-tf-docs.nodejsk8s.dev.nubes.ru}"
|
||||
NAMESPACE="${NAMESPACE:-nubes}"
|
||||
PROVIDER_NAME="${PROVIDER_NAME:-nubes}"
|
||||
export REGISTRY_HOST NAMESPACE PROVIDER_NAME VERSION
|
||||
|
||||
S3CFG_REGISTRY="${S3CFG_REGISTRY:-${ROOT_DIR}/secrets/.s3cfg_registry}"
|
||||
@@ -85,9 +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")"
|
||||
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"
|
||||
@@ -143,37 +166,58 @@ if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Per-стенд подстановка в getting-started (после копирования 30_registry в docs_dir)
|
||||
# Per-стенд подстановка во все скопированные Markdown-файлы.
|
||||
if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then
|
||||
export DOCS_GUIDE_VERSION="$VERSION"
|
||||
export DOCS_GUIDE_API_ENDPOINT="$DOCS_API_ENDPOINT"
|
||||
export DOCS_GUIDE_NAMESPACE="$NAMESPACE"
|
||||
export DOCS_GUIDE_FILE="${MKDOCS_DOCS_DIR}/30_registry/guides/getting-started.md"
|
||||
export DOCS_SUBSTITUTION_ROOT="$MKDOCS_DOCS_DIR"
|
||||
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")
|
||||
# source namespace: .../{{NAMESPACE}}/nubes -> .../<ns>/nubes
|
||||
text = text.replace("{{NAMESPACE}}", os.environ["DOCS_GUIDE_NAMESPACE"])
|
||||
# version (первое вхождение version = "x.y.z" — блок required_providers)
|
||||
text = re.sub(
|
||||
r'(version\s*=\s*")([0-9.]+)(")',
|
||||
lambda m: f'{m.group(1)}{os.environ["DOCS_GUIDE_VERSION"]}{m.group(3)}',
|
||||
text,
|
||||
count=1,
|
||||
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]+(")'
|
||||
)
|
||||
text = re.sub(
|
||||
r'(api_endpoint\s*=\s*")([^"]+)(")',
|
||||
lambda m: f'{m.group(1)}{os.environ["DOCS_GUIDE_API_ENDPOINT"]}{m.group(3)}',
|
||||
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,
|
||||
count=1,
|
||||
)
|
||||
guide_path.write_text(text, encoding="utf-8")
|
||||
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'
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/{{NAMESPACE}}/nubes"
|
||||
version = "0.0.0" # автозамена на версию стенда при сборке (prod=1.x, dev=2.x, test=3.x)
|
||||
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://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,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}}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+1
-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
|
||||
|
||||
Reference in New Issue
Block a user