docs: пометить отменённый заход модификаторов как LEGACY + исправить ложные факты

- баннеры «ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО» на 4 файла HISTORY/OPUS/2026-09-22_modifier_* и docs/60_strategy/modifier_resources_ideology_and_specification.md
- vIPConfigure: replace-семантика, НЕ накопительная (по тесту docs/ORG_IP_MODIFIER_TEST_2026-09-22.md)
- обновлены ссылки на перенесённые материалы (docs/... -> NOTES/..., HOW_TO/...)
This commit is contained in:
Repinoid
2026-09-24 07:51:25 +03:00
parent d93ff66482
commit 2d8e435dd4
59 changed files with 23 additions and 6 deletions
+139
View File
@@ -0,0 +1,139 @@
# DevOps Runbook: Provider Build Pipeline
This repo root contains the 4 scripts for the full provider build pipeline.
## Overview
1) Generate YAML specs from API
2) Generate Go resources + documentation files from YAML
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+
- `python3`
- `gpg`
- `mc` (MinIO/S3 client)
- Docker (for mkdocs build)
## Shared settings
S3 environment:
- `S3_ENDPOINT` (example: `https://s3.msk-1.ngcloud.ru`)
- `S3_ACCESS_KEY`
- `S3_SECRET_KEY`
Provider naming defaults:
- `REGISTRY_HOSTNAME`: `tf-registry.containerk8s.services.ngcloud.ru`
- `NAMESPACE`: `nubes`
- `NAME`: `nubes`
## Step 1: Generate YAMLs from API
Script: `01_generate_yamls.sh`
Input list of services:
- `services_list.txt` (service_id only)
Token options:
- `TOKEN_FILE=/home/naeel/terra/HH-MM-SS.token`, or
- `NUBES_API_TOKEN` directly
Example:
```bash
export TOKEN_FILE=/home/naeel/terra/08-33-41.token
./01_generate_yamls.sh
```
## Step 2: Generate Go resources and docs
Script: `TOOLS/scripts/02_generate_resources_and_docs_v2.sh`
Example:
```bash
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
```
Outputs:
- Go files in `generated/<stand>/go`
- Docs in `generated/<stand>/docs`
Important:
- The v2 script always rebuilds `resource-generator` and `docs-generator` from source before running.
- Do not invoke stale binaries from `TOOLS/resource-generator/bin/` or `TOOLS/docs-generator/bin/` directly.
## Step 3: Build and upload provider
Script: `03_build_and_upload_provider.sh`
Uses `registry-server-build/build-provider.sh` and signs with:
- `secrets/private_key.asc` (ignored by git)
Example:
```bash
export S3_ENDPOINT=https://s3.msk-1.ngcloud.ru
export S3_ACCESS_KEY=...
export S3_SECRET_KEY=...
./03_build_and_upload_provider.sh 2.0.2
```
## Step 4: Build and publish docs
Script: `04_build_and_publish_docs.sh`
Example:
```bash
export S3_ENDPOINT=https://s3.msk-1.ngcloud.ru
export S3_ACCESS_KEY=...
export S3_SECRET_KEY=...
./04_build_and_publish_docs.sh 2.0.2
```
## Notes
- The GPG private key must remain stable across releases. Do not regenerate per build.
- If the key is regenerated, the registry server must be updated to serve the new public key.
- Terraform will fail with `authentication signature from unknown issuer` if the registry public key does not match the signing key.
- `services_list.txt` is the source of truth for which services are generated.
- If the provider version changes, update `universal_rebuild/main.go`.
## One-time GPG bootstrap (do this once, keep the key stable)
1) Generate and export keys (no passphrase):
```bash
GPG_DIR=${ROOT_DIR}/secrets
GNUPGHOME=$(mktemp -d)
cat > /tmp/gpg_batch <<'EOF'
%no-protection
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: tazet@narod.ru
Name-Email: tazet@narod.ru
Expire-Date: 0
EOF
gpg --batch --homedir "$GNUPGHOME" --gen-key /tmp/gpg_batch
gpg --batch --homedir "$GNUPGHOME" --armor --export-secret-keys > "$GPG_DIR/private_key.asc"
gpg --batch --homedir "$GNUPGHOME" --armor --export > "$GPG_DIR/public_key.asc"
rm -rf "$GNUPGHOME" /tmp/gpg_batch
```
2) Update registry server public key (ASCII Armor) in:
- `registry-server-build/main.go`
- `operator/cmd/registry/main.go`
3) Rebuild and redeploy the registry server (see `docs/50_history/00_system_mechanics.md`).
4) Build and upload provider artifacts as usual.
# check string
+145
View File
@@ -0,0 +1,145 @@
# Как собрать и залить провайдер
## ⛔ Схема версий
**Первая цифра версии жёстко привязана к стенду. НЕ ПУТАТЬ.**
| Стенд | Namespace | Первая цифра | Профиль |
|---|---|---|---|
| **PROD** | `nubes` | `2.*` | `TOOLS/config/prod` |
| **DEV** | `nubes-dev` | `3.*` | `TOOLS/config/dev` |
| **TEST** | `nubes-test` | `5.*` | `TOOLS/config/test` |
## Архитектура конфигурации
```
TOOLS/config/
├── registry.env ← ЕДИНЫЙ реестр (НЕ зависит от стенда)
│ REGISTRY_HOSTNAME = tf-registry.containerk8s.services.ngcloud.ru
│ S3_ENDPOINT = https://s3.msk-1.ngcloud.ru
│ S3_BUCKET = nubes-terraform-registry
│
├── dev/profile.env ← стенд-специфика
│ NUBES_API_ENDPOINT = ...dev...
│ TOKEN_FILE = secrets/dev.token
│ NAMESPACE = nubes-dev
│ VERSION = 3.x.x
│
├── test/profile.env
└── prod/profile.env
```
Скрипты подгружают ОБА файла: `registry.env` → общие настройки реестра, `profile.env` → стенд-специфика.
**Чтобы сменить реестр** — правишь только `TOOLS/config/registry.env`.
## Полный пайплайн (3 шага)
### Требования
- Токены в `secrets/{dev,test,prod}.token`
- `secrets/private_key.asc` — GPG-ключ для подписи
- `secrets/.s3cfg_registry` — S3-креды (или `S3_ACCESS_KEY`/`S3_SECRET_KEY` в env)
- `go` установлен
- `mc` (MinIO Client) установлен, алиас `prod-s3`
### Шаг 1 — Сгенерировать YAML из API
```bash
cd ~/tf_provider
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
```
Запрашивает спецификации сервисов из API стенда → пишет YAML в `generated/dev/resources_yaml/`.
### Шаг 2 — Сгенерировать Go-ресурсы и документацию
```bash
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
```
Из YAML генерирует:
- `generated/dev/go/` — Go-код ресурсов
- `generated/dev/docs/` — Markdown-документацию
### Шаг 3 — Собрать и залить в реестр
```bash
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
```
Компилирует (linux/windows/darwin), подписывает GPG, заливает в S3.
### Одной командой (для ленивых)
```bash
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev && \
./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev && \
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
```
## Быстрая заливка (без перегенерации YAML/Go)
Если YAML'ы и Go-код уже сгенерированы и не менялись — только шаг 3:
```bash
# DEV
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev 3.1.13
# TEST
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/test 5.1.17
# PROD
./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/prod 2.1.23
```
Креды S3 подхватываются из `secrets/.s3cfg_registry`. Или через env:
```bash
export S3_ACCESS_KEY=...
export S3_SECRET_KEY=...
```
## Структура S3
```
nubes-terraform-registry/
└── tf-registry.containerk8s.services.ngcloud.ru/
└── {namespace}/
└── nubes/
└── {version}/
├── terraform-provider-nubes_{ver}_linux_amd64.zip
├── terraform-provider-nubes_{ver}_windows_amd64.zip
├── terraform-provider-nubes_{ver}_darwin_amd64.zip
├── terraform-provider-nubes_{ver}_SHA256SUMS
└── terraform-provider-nubes_{ver}_SHA256SUMS.sig
```
## Проверка после заливки
```bash
# DEV
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-dev/nubes/versions | python3 -m json.tool
# TEST
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-test/nubes/versions | python3 -m json.tool
# PROD
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes/nubes/versions | python3 -m json.tool
```
## Актуальные версии
Файл [`VERSIONS.md`](VERSIONS.md) — единственный источник правды. После каждой заливки — обновить.
## Terraform-конфиг пользователя
```hcl
terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "3.1.13"
}
}
}
```
+221
View File
@@ -0,0 +1,221 @@
# Инструкция по добавлению нового сервиса в Terraform-провайдер Nubes
## 1. Где прописывать
Единственная точка входа — `devops/config/services_list.txt` (или профильный `profiles/{stand}/services_list.txt`).
Формат строки:
```
{service_id} {service_name} # Описание (опционально)
```
Пример:
```
90 postgres # Управляемая база данных PostgreSQL
```
- **service_id** — число, ID сервиса из API Nubes (`/services/{id}`)
- **service_name** — snake_case, латиница. Будет использоваться как имя ресурса `nubes_{name}`
Исключение сервиса — закомментировать строку `#`.
---
## 2. Что происходит после добавления в список
Пайплайн (3 шага):
```
services_list.txt
→ 01_generate_yamls.sh → запрос к API → resources_yaml/{id}_{name}.yaml
→ 02_generate_resources_and_docs_v2.sh → internal/resources_gen/{name}_resource.go + docs
→ 03_build_and_upload_provider.sh → сборка + S3
```
**Никаких ручных правок YAML или сгенерированного Go-кода.** Всё из API.
---
## 3. Структура YAML (что генерируется)
```yaml
name: postgres # snake_case, из services_list.txt
service_id: 90 # ID сервиса
service_display_name: PostgreSQL # человекочитаемое имя
service_short_name: postgres # краткое имя из API
service_man: "описание..." # MAN-руководство (HTML)
lifecycle:
suspend_on_destroy_default: true # есть ли операция suspend
adopt_existing_on_create_default: false
outputs:
params: # выходные параметры (всегда одинаковые)
- code: state_params; type: map
- code: state_out; type: map
- code: vault_secrets; type: map; sensitive: true
- code: vault_url; type: string
...
operations:
- name: create # имя операции из API (snake_case)
id: 19 # svcOperationId
kind: instance # instance | subresource | action
action: create # create | modify | delete | suspend | ...
man: "руководство" # описание операции
params:
- id: 788 # svcOperationCfsParamId
code: clusterConfiguration
data_type: map-fixed
required: true
sort: 20
is_modifiable: true
```
---
## 4. Типы параметров (data_type)
| Тип | Пример | Когда использовать |
|---|---|---|
| `string` | `resourceRealm`, `domain`, `dbName` | Имена, домены, UUID, строки |
| `integer > 0` | `resourceCPU`, `resourceMemory` | Числовые значения больше нуля |
| `integer >= 0` | `autoScaleTechWindow` | Числовые, допускающие 0 |
| `boolean` | `enablePgPoolerMaster`, `allowNoSsl` | Флаги вкл/выкл |
| `uuid` | `s3Uid`, `vdcUid` | Ссылки на другие сервисы (ref) |
| `map-fixed` | `clusterConfiguration` | **Сложные K8s-сервисы**: JSON-объект с фиксированным набором полей |
| `array-map-fixed` | `postgresConf` | Массив JSON-объектов (доп. конфигурации) |
| `json` | `jsonEnv`, `jsonParameters` | Произвольный JSON |
| `map` | `state_params` | Только в outputs |
| `yaml` | — | YAML-строка (редко) |
---
## 5. Виды операций (kind)
### instance — управление жизненным циклом сервиса
| action | Terraform | Пример |
|---|---|---|
| `create` | `resource "nubes_X" "Y" {}` | Создание сервиса |
| `delete` | `terraform destroy` | Удаление |
| `modify` | изменение параметров → `apply` | Модификация |
| `suspend` | `suspend_on_destroy = true` | Остановка (без удаления) |
| `resume` | `adopt_existing_on_create = true` | Запуск остановленного |
**Правило**: если у сервиса есть `suspend` И `resume` → `suspend_on_destroy_default = true`.
### subresource — вложенные объекты
Стандартные subresource'ы:
- **user** → `nubes_{service}_user` (create/delete)
- Параметры: `username` (string, required), `role` (string, required, value_list)
- **database** → `nubes_{service}_database` (create/delete)
- Параметры: `dbName` (string, required, regex), `dbOwner` (string, required)
- **topic** → Kafka
- **backup** → S3, PostgreSQL
- **vdc** → vcOrg
**Правило**: subresource всегда ссылается на родительский ресурс через `{service}_id`.
### action — разовые операции
| action | Когда |
|---|---|
| `reconcile` | Синхронизация состояния (у 20 сервисов) |
| `redeploy` | Переразвёртывание (приложения: flask, nodejs, lucee, ...) |
| `restart` | Перезапуск (postgres, mariadb, ...) |
| `recovery` | Восстановление из бэкапа |
**Правило**: action-ресурсы используют trigger-поле (`run_id`/`nonce`) для идемпотентности.
---
## 6. Валидация параметров (автоматически из API)
| Механизм | Параметров | Пример |
|---|---|---|
| `regex` | 29 | `dbName: ^[A-Za-z0-9]+$` |
| `value_list` | 38 | `role: [app_user, ddl_user]` |
| `minlength`/`maxlength` | 87/74 | `username: min 2, max 62` |
| `minvalue`/`maxvalue` | — | `resourceCPU > 0` |
| `default` | 120 | `deleteS3Bucket: true` |
| `is_modifiable` | 106 | Можно менять после create |
| `is_sensitive` | 2 | `vault_secrets` |
---
## 7. Что запрещено
- ❌ **Править сгенерированные YAML вручную** — источник истины только API
- ❌ **Править `internal/resources_gen/*.go` вручную** — перезапишется при следующей генерации
- ❌ **Использовать кириллицу в `service_name`** — только латиница, snake_case
- ❌ **Менять `embed.go`** — генерируется автоматически
- ❌ **Пропускать сервис через комментарий без причины** — лучше явно указать причину в комментарии: `# 24 DEPRECATED ...`
---
## 8. Быстрый старт: добавляем новый сервис
```bash
# 1. Добавить строку в services_list.txt
echo "200 my_new_service # Моя новая услуга" >> devops/config/services_list.txt
# 2. Сгенерировать YAML (test-стенд)
devops/01_generate_yamls.sh --profile devops/profiles/test
# 3. Сгенерировать Go-код + доки
devops/02_generate_resources_and_docs_v2.sh --profile devops/profiles/test
# 4. Проверить что появился файл
ls devops/profiles/test/generated/resources_yaml/200_my_new_service.yaml
ls devops/profiles/test/generated/go/200_my_new_service_resource.go
# 5. Собрать и задеплоить провайдер
devops/03_build_and_upload_provider.sh --profile devops/profiles/test
# 6. Написать тестовый манифест в TEST_STAND/my_new_service/main.tf
# 7. terraform init && terraform plan && terraform apply
```
---
## 9. Как писать тестовый манифест
### Простой сервис (flat-параметры)
```hcl
resource "nubes_s3bucket" "test" {
resource_name = "Мой бакет"
s3_user_uid = var.s3_uid
bucket_name = "test-bucket"
}
```
### Сложный сервис (map-fixed)
```hcl
resource "nubes_postgres" "test" {
resource_name = "pg-test"
cluster_configuration = jsonencode({
platform = "k8s-3.ext.nubes.ru"
instances = 1
memory = 512
cpu = 500
disk = "1"
})
startup_configuration = jsonencode({
appVersion = "17"
})
access_configuration = jsonencode({
needExternalAddressMaster = false
})
# ... остальные блоки
}
```
**JSON-ключи внутри map-fixed** — camelCase-версии старых плоских названий параметров. Точные имена полей смотри в сгенерированном YAML (поле `code` у параметров в `operations.params`) или в `generated/docs/{service}_params_create.md`.
+282
View File
@@ -0,0 +1,282 @@
# Инструкция для DevOps облака: имплементация нового managed-сервиса
> На основе анализа 43 сервисов Nubes Cloud (июль 2026).
> Цель: единый стандарт, чтобы любой новый сервис был консистентен с существующими.
---
## 1. Классификация сервиса
Выбери один из двух классов ДО начала проектирования параметров:
| Класс | Признак | Размещение | Примеры |
|---|---|---|---|
| **Простой** | Не требует оркестрации K8s | VM, сеть, хранилище | s3bucket, vc_vm, dnszone, vcexternalip, harbor |
| **Сложный (K8s)** | Разворачивается в Kubernetes через оператор | Pods на кластере | postgres, redis, kafka, clickhouse, flask, nextcloud |
**Правило**: если сервис крутится в K8s → используй **map-fixed** блоки (раздел 3). Если нет → плоские параметры (раздел 2).
---
## 2. Простой сервис: плоские параметры
### Обязательный минимум
Каждый сервис ДОЛЖЕН иметь эти параметры в create:
| code | data_type | required | Назначение |
|---|---|---|---|
| `resourceRealm` | `string` | true | Платформа/K8s-кластер для развёртывания |
| `resourceName` | `string` | true | Человекочитаемое имя инстанса (displayName) |
### Стандартные ресурсные параметры
Добавляй по необходимости:
| code | data_type | Назначение |
|---|---|---|
| `resourceInstances` | `integer > 0` | Количество реплик/нод (default: 1) |
| `resourceMemory` | `integer > 0` | Память в MB |
| `resourceCPU` | `integer > 0` | CPU в милликорах (1000 = 1 vCPU) |
| `resourceDisk` | `string` | Диск в GB |
### Прочие частые параметры
| code | data_type | Где используется |
|---|---|---|
| `domain` | `string` | Сервисы с доменным именем (9 из 43) |
| `ipSpaceName` | `string` | Сервисы с внешним IP |
| `appConfiguration` | `map-fixed` | Приложения (nextcloud, superset, harbor, ...) |
| `jsonEnv` | `json` | Переменные окружения (flask, nodejs) |
| `storageConfig` | `map-fixed` | Хранилище (kafka, clickhouse) |
---
## 3. Сложный сервис (K8s): map-fixed блоки
**Правило**: группируй параметры в логические блоки. Используй этот стандартный набор:
### 3.1. startupConfiguration (sort: 10)
**Назначение**: версия ПО, образ, всё что задаётся до старта.
```
appVersion, image, imageTag, ...
```
**required**: true. **is_modifiable**: false (не меняется после создания).
### 3.2. clusterConfiguration (sort: 20)
**Назначение**: размер кластера, ресурсы.
```
platform (= resourceRealm), instances, memory, cpu, disk
```
**required**: true. **is_modifiable**: true.
### 3.3. accessConfiguration (sort: 30)
**Назначение**: сетевой доступ.
```
needExternalAddressMaster, ipSpaceNameMaster,
needExternalAddressSlave, ipSpaceNameSlave,
allowNoSsl
```
**required**: true. **is_modifiable**: true.
### 3.4. {service}Configuration (sort: 40)
**Назначение**: специфичные для сервиса настройки.
```
enablePgPoolerMaster, enablePgPoolerSlave, s3Uid, jsonParameters, ...
```
**required**: true. **is_modifiable**: true.
### 3.5. {service}Conf (sort: 50)
**Назначение**: массив дополнительных конфигураций.
Тип: `array-map-fixed`.
**required**: false. **is_modifiable**: true.
### 3.6. backupConfiguration (sort: 60)
**Назначение**: политика резервного копирования.
```
schedule (cron), numToRetain, ...
```
**required**: true. **is_modifiable**: true.
### 3.7. autoscaleConfiguration (sort: 70)
**Назначение**: автоскейлинг.
```
autoScale (boolean), percentage, techWindow, quotaGb
```
**required**: true. **is_modifiable**: true.
### Пример структуры для нового K8s-сервиса
```
create params (sort order):
10: startupConfiguration map-fixed required
20: clusterConfiguration map-fixed required modifiable
30: accessConfiguration map-fixed required modifiable
40: {name}Configuration map-fixed required modifiable
50: {name}Conf array-map optional modifiable
60: backupConfiguration map-fixed required modifiable
70: autoscaleConfiguration map-fixed required modifiable
```
---
## 4. Именование параметров
### ⛔ Жёсткие правила
- **camelCase** для ВСЕХ кодов параметров: `resourceRealm`, `clusterConfiguration`, `dbName`
- **Никакого snake_case**: ❌ `resource_realm`, ✅ `resourceRealm`
- **Никакого хаотичного нейминга**: если параметр про память — везде `resourceMemory`, не `memoryQuota` или `memLimit`
- **Префиксы**: общие параметры с префиксом `resource*` (resourceRealm, resourceCPU, resourceMemory, resourceDisk, resourceInstances)
### Стандартный словарь
| Концепт | Код параметра |
|---|---|
| Платформа/K8s-кластер | `resourceRealm` |
| Количество реплик | `resourceInstances` |
| Память (MB) | `resourceMemory` |
| CPU (millicore) | `resourceCPU` |
| Диск (GB) | `resourceDisk` |
| Версия ПО | `appVersion` |
| Домен | `domain` |
| S3-ссылка | `s3Uid` |
| IP-space | `ipSpaceName` |
| Внешний IP для master | `needExternalAddressMaster` |
| Внешний IP для slave | `needExternalAddressSlave` |
| PgBouncer master | `enablePgPoolerMaster` |
| PgBouncer slave | `enablePgPoolerSlave` |
| Отключить SSL | `allowNoSsl` |
| Автоскейлинг | `autoScale` |
| Cron бэкапа | `backupSchedule` |
---
## 5. Операции
### Обязательные (каждый сервис)
| operation | kind | action |
|---|---|---|
| `create` | instance | create |
| `delete` | instance | delete |
### Настоятельно рекомендуемые
| operation | kind | action | Зачем |
|---|---|---|---|
| `modify` | instance | modify | Изменение параметров без удаления |
| `suspend` | instance | suspend | Остановка без удаления (биллинг!) |
| `resume` | instance | resume | Запуск после suspend |
**Правило**: если реализовал `suspend` → ОБЯЗАТЕЛЬНО реализовать `resume`. И наоборот.
### Опциональные
| operation | kind | action | У кого есть |
|---|---|---|---|
| `restart` | action | restart | postgres, mariadb, redis, kafka |
| `recovery` | action | recovery | postgres, clickhouse |
| `reconcile` | action | reconcile | 20 сервисов (универсальная синхронизация) |
| `redeploy` | action | redeploy | flask, nodejs, lucee, nifi, superset |
---
## 6. Subresource'ы
### Стандартные
| subresource | operations | Параметры | У скольких сервисов |
|---|---|---|---|
| **user** | create_user, delete_user | `username` (string, regex), `role` (string, value_list) | 16 |
| **database** | create_database, delete_database | `dbName` (string, regex), `dbOwner` (string) | 6 |
### Специфичные
| subresource | Где |
|---|---|
| `topic` | kafka (3 операции) |
| `backup` | s3, postgres |
| `vdc` | vcOrg |
| `sub_user` | openwhisk |
### Правила subresource'ов
- **Именование операций**: `create_{subresource}`, `delete_{subresource}` (snake_case глагол + имя)
- **Именование subresource**: одно слово, snake_case: `user`, `database`, `topic`
- **Ссылка на родителя**: обязательный UUID-параметр, ссылающийся на родительский инстанс
- **Параметр `role` для user**: ОБЯЗАТЕЛЬНО `value_list` с вариантами (например `[app_user, ddl_user]`)
- **Параметр `dbName` для database**: ОБЯЗАТЕЛЬНО `regex: ^[A-Za-z0-9]+$`
---
## 7. Валидация параметров
### Обязательно (где применимо)
| Механизм | Когда | Пример |
|---|---|---|
| `value_list` | Ограниченный набор значений | `role: [app_user, ddl_user]` |
| `regex` | Имена, идентификаторы | `dbName: ^[A-Za-z0-9]+$` |
| `minlength` | Минимальная длина строки | `username: min 2` |
| `maxlength` | Максимальная длина строки | `username: max 62` |
| `minvalue` | Минимальное число | `resourceCPU: > 0` |
| `maxvalue` | Максимальное число | — |
| `default` | Значение по умолчанию | `deleteS3Bucket: true` |
### ⛔ Запрещено
- Параметр без `descr` (описание) — ВСЕГДА заполнять
- Булевы параметры без `default` — если не указан, поведение неопределено
- `required: true` для параметра с `default` — бессмысленно
---
## 8. Модифицируемость (is_modifiable)
### Правило
| Категория параметра | is_modifiable |
|---|---|
| Имя, версия ПО, платформа (startup) | **false** |
| Ресурсы (CPU, память, диск, реплики) | **true** |
| Доступ (IP, SSL, pooler) | **true** |
| Бэкапы, автоскейлинг | **true** |
| Всё что в modify-операции | **true** |
106 из 435 параметров (24%) имеют `is_modifiable: true`.
---
## 9. Outputs
**Не трогать.** Стандартный набор выходных параметров един для всех сервисов:
```
state_params map
state_out map
state_params_flat map
state_out_flat map
vault_secrets map sensitive
vault_url string
vault_user_path string
vault_fields list
```
---
## 10. Чек-лист перед сдачей сервиса
- [ ] `resourceRealm` есть в create (required)
- [ ] `resourceName` есть в create (required)
- [ ] Все коды параметров — camelCase
- [ ] Для K8s-сервиса: 6 стандартных map-fixed блоков с правильными sort
- [ ] `suspend` + `resume` либо есть оба, либо нет ни одного
- [ ] `modify` содержит все `is_modifiable: true` параметры из create
- [ ] У всех параметров заполнен `descr`
- [ ] Булевы параметры имеют `default`
- [ ] `username` для user-subresource имеет regex и minlength/maxlength
- [ ] `role` для user-subresource имеет value_list
- [ ] `dbName` для database-subresource имеет regex
- [ ] MAN (service_man) заполнен: описание, параметры, примеры
- [ ] MAN для каждой операции (operation.man) заполнен
+182
View File
@@ -0,0 +1,182 @@
# Архитектура документации Nubes Terraform Provider
## Источники истины
| Уровень | Источник | Роль |
|---|---|---|
| 1 | **API (YAML-спеки)** | Единственный источник правды. Параметры, типы, defaults, constraints, operations — только из YAML |
| 2 | **MAN (service_man)** | Дополнительный контекст. Может устареть или содержать ошибки. Используется для улучшения формулировок, но НЕ переопределяет YAML |
| 3 | **LLM (gpt-oss-120b)** | Обрабатывает .md файлы: улучшает читаемость, добавляет логику, переводит HTML→Markdown. Меняет ТОЛЬКО текст описаний, НЕ параметры |
## Пайплайн генерации
```
YAML-спеки
│
▼
docs-generator (Go) ─── создаёт структуру: Name.md, Name_example.md, Name_params_create.md, ...
│ HCL-блоки, HTML-таблицы параметров, navigation, index.md
▼
LLM (gpt-oss-120b) ─── улучшает описания: читаемый Markdown, логичные формулировки
│ вход: YAML + MAN + текущие .md
│ выход: переписанные .md (структура и параметры неизменны)
▼
mkdocs-material ─────── собирает статический сайт
│
▼
S3 (terraform-registry) ── хостинг через registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->
```
## Структура страниц (на каждый сервис)
| Файл | Содержание |
|---|---|
| `Name.md` | Главная: краткое описание + MAN в `??? note` (mkdocs-native admonition) |
| `Name_example.md` | HCL-пример с полным манифестом |
| `Name_params_create.md` | Таблицы Create-параметров + вложенные sub_params |
| `Name_params_modify.md` | Таблицы Modify-параметров |
| `Name_outputs.md` | Выходные параметры + реальные ключи из облака |
| `Name_ops.md` | Список операций (create/delete/modify/suspend/resume) |
| `Name_params.md` | Лендинг: ссылки на create/modify params |
| `Name_subresource.md` | Для каждого subresource: параметры |
| `Name_subresource_example.md` | HCL-пример subresource |
## Формат MAN (service_man) — как рендерится
`service_man` из YAML содержит **смесь Markdown и HTML**: заголовки `#`/`##`, списки `-`, bold `**`, горизонтальные линии `---`, а также `<br/>` и HTML-entities.
### Конвертация: `htmlToMarkdown()`
```go
// 1. <br/> → \n
// 2. <h1>/<h2>/<h3> → # / ## / ###
// 3. <strong>/<b> → **...**
// 4. <em>/<i> → *...*
// 5. <code> → `...`
// 6. <a href> → [...](...)
// 7. <ul><li> → - ...
// 8. Strip remaining HTML tags
// 9. Unescape HTML entities (&quot; → ")
// 10. Collapse 3+ blank lines → 2
```
### Рендеринг: `??? note` admonition (НЕ `<details>`!)
**Важно:** `<details>` и `<div markdown="1">` НЕ работают в mkdocs — Markdown внутри них не рендерится.
Вместо этого используется **нативный mkdocs admonition** `??? note`:
```markdown
??? note "Справка (MAN)"
# Инструкция по развертыванию
---
## 1. Общая информация
Текст параграфа.
- **bold** — описание
- `code` — пример
```
**Критические требования:**
1. Пустая строка после `??? note "..."` — обязательно
2. Все строки контента с отступом ровно 4 пробела — включая пустые
3. `pymdownx.details` в `markdown_extensions` (уже есть)
Результат: `<details class="note"><summary>Справка (MAN)</summary><h1>...</h1><hr/><h2>...</h2>...</details>`
## Принципы дизайна (CSS)
- `max-width: 1800px` — лёгкое ограничение (на 2560px поля ~380px)
- 🔵 Синий — переменные верхнего уровня (структуры)
- 🟢 Зелёный — поля внутри структур (sub_params)
- ID — мелкий, серый (техническая информация)
- Default — заметный (юзеру важно что будет если не указать)
- Description/Constraints — мелкий серый (доп. информация)
- Без переносов в коде, description — с переносами
## Сервисы с подробным MAN (для LLM-обработки)
| Сервис | MAN (символов) |
|---|---|
| rabbitmq | 9811 |
| mongodb | 9296 |
| postgres | 8252 |
| gitea | 6639 |
| clickhouse | 6010 |
| flask | 5569 |
| kafka | 5210 |
| vapp | 4659 |
| akhq | 3838 |
## LLM-промпт (на один сервис)
LLM получает ВСЕ .md файлы сервиса + ключевые поля из YAML + MAN и переписывает их.
### Формат входа
```
Сервис: <service_name>
YAML (ключевое): параметры, типы, defaults, constraints
MAN: <service_man>
Файлы:
=== Name.md ===
<содержимое>
...
```
### Формат выхода
```json
{
"Name.md": "полный текст",
"Name_example.md": "полный текст",
...
}
```
### Правила для LLM
1. **YAML = истина. MAN = контекст.** Если противоречат → верить YAML.
2. HTML-таблицы: менять ТОЛЬКО текст внутри `<td>`, НЕ трогать структуру тегов.
3. HCL-блоки: НЕ трогать.
4. Navigation-строки: НЕ трогать.
5. Имена ресурсов (nubes_*): НЕ менять.
6. MAN-секцию: перевести из HTML в читаемый Markdown.
7. Описания: сделать грамотными, логичными, на русском. Добавить контекст из MAN где уместно.
8. **Юзер в первую очередь смотрит на: имя переменной, required/default, что делает параметр.** Это должно быть максимально понятно.
## Сборка mkdocs: слияние статического и динамического nav
mkdocs-material не имеет встроенного `!include` для `nav:`. Решение (рекомендовано): расширить Python pre-build шаг в `04_build_and_publish_docs.sh`:
1. `WriteNavFragment()` генерирует `_nav_fragment.yml` с `resources_nav:` (категории + ресурсы)
2. Python-блок читает `_nav_fragment.yml`, парсит `mkdocs.yml`, вставляет ресурсы в секцию `Ресурсы` внутри `nav:`
3. Одновременно копирует `30_registry/` в `docs_dir` (чтобы guides не ломались при смене `docs_dir`)
4. Результат пишется в `.mkdocs.tmp.yml` → `mkdocs build -f .mkdocs.tmp.yml`
Альтернативы (отвергнуты):
- docs-generator пишет полный mkdocs.yml (слишком хрупко)
- mkdocs-awesome-pages (не решает проблему merge static+dynamic)
## Аудит соответствия YAML ↔ Доки (2026-08-10)
**Метод:** сравнение всех 37 YAML-спеков со сгенерированными `_params_create.md` и `_params_modify.md`.
### Итоги
| Метрика | YAML | Доки | Статус |
|---------|------|------|--------|
| Сервисов | 37 | 37 | ✅ |
| CREATE params (top-level) | 204 | 204 | ✅ 1:1 |
| MODIFY params (top-level) | 102 | 101 | ⚠️ -1 |
| Sub-params (nested) | — | 199 | ✅ развёрнуты |
### Расхождения
| # | Сервис | Проблема | Причина | Действие |
|---|--------|----------|---------|----------|
| 1 | vc_vm_v2 | Нет страниц | Закомментирован в `services_list.txt` (`# нет в TEST UI`) | Не баг |
| 2 | s3, s3bucket, dummy, vc_nsxt, vcexternalip, vc_vm_v3 | 8 пустых типов | Поле `type` не заполнено в YAML | Косметика |
### Вывод
Все параметры из YAML **полностью** присутствуют в документации. CamelCase-имена корректно конвертируются в snake_case через `ToSnake()`. Единственный «missing» сервис (vc_vm_v2) исключён из генерации намеренно. 8 пустых типов — пробелы в исходных YAML-спеках, не влияют на корректность.
+638
View File
@@ -0,0 +1,638 @@
# План миграции в Nubes Managed Kubernetes — Инструкции для агента
**Создан:** 2026-03-13 (Opus 4.6)
**Исполнитель:** Sonnet 4.6
**Статус:** Ожидает исполнения
> **ВАЖНО:** Этот документ — пошаговый план. Каждый пункт содержит:
> - ЧТО делать (цель)
> - ГДЕ делать (файлы)
> - КАК делать (конкретные инструкции)
> - ОГРАНИЧЕНИЯ (что нельзя трогать)
---
## Контекст
Terraform-провайдер Nubes Cloud сейчас работает на self-managed K8s (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 Managed Kubernetes под управление их DevOps.
Nubes (nubes.ru) — российский cloud-провайдер, собственный DC Tier III (Москва), имеет Managed K8s, Harbor, S3.
**Репозиторий:** `/home/naeel/remote_dev/terraform`
**Обязательно прочитать перед работой:**
- `REPO_CONTENTS.md` — карта репозитория
- `.github/copilot-instructions.md` — правила работы (IMMUTABILITY POLICY)
- `docs/CODEBASE_ANALYSIS_AND_ROADMAP.md` — анализ кодовой базы
---
## Группа A: Подготовительные задачи (делать ПЕРВЫМИ)
### A1. Helm Chart для всех K8s-манифестов
**Цель:** Конвертировать raw YAML манифесты в Helm chart.
**Исходные файлы (ТОЛЬКО ЧИТАТЬ, НЕ МЕНЯТЬ):**
- `k8s/registry-deployment-new.yaml`
- `k8s/registry-ingress-new.yaml`
- `operator/manifests/00-namespace.yaml`
- `operator/manifests/00-rbac.yaml`
- `operator/manifests/02-crd.yaml`
- `operator/manifests/03-build-script.yaml`
- `operator/manifests/04-operator-deployment.yaml`
- `operator/manifests/05-registry-server.yaml`
- `operator/manifests/06-registry-server-docs.yaml`
**Создать:**
```
charts/
terraform-registry/
Chart.yaml
values.yaml
values-dev.yaml
values-prod.yaml
templates/
_helpers.tpl
namespace.yaml
rbac.yaml
crd.yaml
build-script-configmap.yaml
operator-deployment.yaml
registry-server-deployment.yaml
registry-server-service.yaml
docs-server-deployment.yaml (optional)
ingress.yaml
pdb.yaml
networkpolicy.yaml
```
**Требования к `values.yaml`:**
```yaml
global:
registryHostname: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->" # Переопределяется при миграции
namespace: "terraform-registry"
registry:
image:
repository: "naeel/terraform-registry-server" # → Harbor при миграции
tag: "latest"
pullPolicy: IfNotPresent
replicas: 1 # → 2 в prod
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
service:
port: 80
targetPort: 8080
healthcheck:
enabled: true
path: /healthz
port: 8080
operator:
image:
repository: "naeel/terraform-registry-operator"
tag: "latest"
replicas: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
ingress:
enabled: true
className: "nginx"
host: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->" # Переопределяется
tls:
enabled: true
issuer: "letsencrypt-prod" # НЕ ТРОГАТЬ LetsEncrypt issuer!
secretName: "registry-tls"
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
s3:
endpoint: "s3.msk-1.ngcloud.ru"
bucket: "terraform-registry"
useSSL: true
# credentials через existingSecret
existingSecret: "s3-credentials"
accessKeyField: "access-key"
secretKeyField: "secret-key"
pdb:
enabled: false # → true в prod
minAvailable: 1
networkPolicy:
enabled: false # → true при миграции
```
**Требования к `values-dev.yaml`:**
```yaml
global:
registryHostname: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->"
registry:
replicas: 1
pdb:
enabled: false
```
**Требования к `values-prod.yaml`:**
```yaml
global:
registryHostname: "registry.nubes.ru" # Целевой домен
registry:
image:
repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-server"
replicas: 2
operator:
image:
repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-operator"
pdb:
enabled: true
minAvailable: 1
networkPolicy:
enabled: true
```
**Ограничения:**
- НЕ менять исходные YAML в `k8s/` и `operator/manifests/` (они могут ещё использоваться)
- НЕ трогать CRD-ресурсы с LetsEncrypt issuerRef
- НЕ запускать `helm install/upgrade` — только создать файлы
- Helm chart — НОВЫЕ файлы в `charts/` (APPEND ONLY)
---
### A2. Externalize hardcoded values
**Цель:** Убрать все hardcoded пути и домены, заменить на env vars.
**Файл 1: `internal/core/client.go` строка ~18**
```go
// СЕЙЧАС:
f, err := os.OpenFile("/home/naeel/terra/debug_nubes.log", ...)
// НОВЫЙ КОД (добавить новую функцию в КОНЕЦ файла):
func debugLogPath() string {
if p := os.Getenv("NUBES_DEBUG_LOG"); p != "" {
return p
}
return filepath.Join(os.TempDir(), "nubes_debug.log")
}
```
**Ограничение:** НЕ менять строку 18 напрямую. Добавить функцию `debugLogPath()` в КОНЕЦ файла. Спросить оператора перед заменой вызова.
**Файл 2: `internal/provider/provider.go` строка ~103**
```go
// СЕЙЧАС:
InsecureSkipVerify: true,
// НУЖНО: Сделать конфигурируемым через provider schema + env var
```
**Инструкция:**
1. Добавить атрибут `insecure` в schema провайдера (Optional, bool, default false)
2. Добавить чтение env var `NUBES_INSECURE`
3. InsecureSkipVerify = config_value || env_value || false
4. Код добавлять В КОНЕЦ секции Configure(), не рефакторить существующий
**Файл 3: `universal_rebuild/internal/provider/provider.go`**
- Проверить аналогичную проблему с InsecureSkipVerify
- Применить тот же паттерн
**Ограничения:**
- НЕ менять сигнатуры существующих функций
- Новый код — APPEND ONLY
- InsecureSkipVerify=false по умолчанию (breaking change для текущих юзеров — СПРОСИТЬ оператора)
---
### A3. Health endpoints для registry-server
**Цель:** Добавить `/healthz`, `/readyz`, `/metrics` endpoints.
**Файл:** `registry-server-build/main.go`
**Инструкция:**
1. Прочитать текущий `main.go` полностью
2. Добавить в КОНЕЦ файла (новые handler-функции):
```go
func healthzHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
func readyzHandler(w http.ResponseWriter, r *http.Request) {
// Проверить доступность S3
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
```
3. Зарегистрировать handlers в main() — СПРОСИТЬ оператора перед добавлением в mux
**Ограничения:**
- НЕ менять существующие handlers
- НЕ запускать docker build
- Новые функции — APPEND ONLY
---
### A4. Operaционная документация
**Цель:** Создать набор операционных документов для DevOps Nubes.
**Создать файлы:**
**`docs/ops/RUNBOOK.md`:**
```markdown
# Runbook: Terraform Provider Registry
## Предпосылки
- Kubernetes cluster ≥ 1.27
- Helm ≥ 3.12
- Доступ к S3 (s3.msk-1.ngcloud.ru)
- Harbor registry (для образов)
## Установка
helm install terraform-registry ./charts/terraform-registry \
-f charts/terraform-registry/values-prod.yaml \
-n terraform-registry --create-namespace
## Обновление версии
1. Собрать новый образ (CI pipeline)
2. Обновить tag в values
3. helm upgrade terraform-registry ./charts/terraform-registry -f values-prod.yaml
## Проверка здоровья
kubectl -n terraform-registry get pods
curl https://<REGISTRY_HOST>/healthz
curl https://<REGISTRY_HOST>/.well-known/terraform.json
## Компоненты
- Registry Server — HTTP-сервер протокола Terraform Registry
- Operator — K8s controller для сборки provider binaries
- S3 — хранилище артефактов (бинарники + документация)
```
**`docs/ops/TROUBLESHOOTING.md`:**
```markdown
# Troubleshooting
## Registry Server не отвечает
1. kubectl -n terraform-registry get pods -l app=registry-server
2. kubectl -n terraform-registry logs -l app=registry-server --tail=100
3. Проверить ingress: kubectl get ingress -n terraform-registry
4. Проверить S3: curl -s https://s3.msk-1.ngcloud.ru (bucket access)
## Provider binary не скачивается
1. Проверить наличие в S3: s3cmd ls s3://terraform-registry/terraform-providers/...
2. Проверить SHA256SUMS сигнатуру
3. Проверить GPG ключ
## Operator не создаёт build job
1. kubectl -n terraform-registry get terraformproviderrelease
2. kubectl -n terraform-registry describe terraformproviderrelease <name>
3. kubectl -n terraform-registry get jobs
4. Проверить RBAC: operator ServiceAccount должен иметь права на jobs и secrets
## TLS / Certificate проблемы
- Проверить cert-manager: kubectl get certificates -n terraform-registry
- ⚠️ НЕ пересоздавать certificates с LetsEncrypt issuer (rate limits!)
- Для отладки использовать self-signed issuer
```
**`docs/ops/MONITORING.md`:**
```markdown
# Мониторинг
## Ключевые метрики
- registry_http_requests_total — кол-во запросов к registry
- registry_http_request_duration_seconds — latency
- registry_s3_operations_total — операции с S3
- registry_s3_errors_total — ошибки S3
## Алерты (Prometheus)
- RegistryDown: up == 0 (>2 min)
- RegistryHighLatency: p99 > 5s (>5 min)
- RegistryS3Errors: rate > 0.1/s (>5 min)
- RegistryPodRestart: увеличение restart count
## Grafana Dashboard
- Import dashboard ID: (создать при установке мониторинга)
```
**`docs/ops/UPGRADE.md`:**
```markdown
# Процедура обновления
## Provider version update (без downtime)
1. CI собирает новый provider binary
2. Создать TerraformProviderRelease CR с новой версией
3. Operator создаёт build job → артефакты в S3
4. Старые версии остаются доступны (immutable artifacts)
## Registry Server update (rolling)
1. Обновить image tag в Helm values
2. helm upgrade --set registry.image.tag=<new> terraform-registry ./charts/...
3. Проверить: kubectl rollout status deployment/registry-server -n terraform-registry
4. Rollback: helm rollback terraform-registry 1
## Operator update
1. Обновить operator image tag
2. helm upgrade ...
3. Проверить CRD compatibility: kubectl get crd terraformproviderreleases.terra.core.nubes.ru
```
**`docs/ops/ROLLBACK.md`:**
```markdown
# Процедура отката
## Helm rollback
helm rollback terraform-registry <revision>
helm history terraform-registry -n terraform-registry
## Emergency: Direct image rollback
kubectl -n terraform-registry set image deployment/registry-server \
registry-server=<HARBOR>/terraform/registry-server:<PREV_TAG>
## S3 artifacts (immutable — откат не нужен)
Все версии provider binary хранятся бессрочно.
Удаление только вручную через s3cmd.
```
---
## Группа B: Инфраструктурная подготовка
### B1. Dockerfile оптимизация
**Цель:** Убедиться что Dockerfile для registry-server и operator готовы к Harbor.
**Инструкция:**
1. Прочитать `registry-server-build/` и `operator/build/`
2. Проверить что Dockerfile использует multi-stage build
3. Проверить что нет hardcoded путей
4. Убедиться что base image — official (golang:1.24 + alpine/scratch)
5. НЕ запускать docker build — только проверить файлы
**Создать (если отсутствует):** `registry-server-build/.dockerignore`, `operator/.dockerignore`
---
### B2. CI Pipeline definition
**Цель:** Создать файл CI pipeline (GitLab CI / Tekton) для автосборки.
**Создать:** `devops/ci/pipeline.yaml`
```yaml
# GitLab CI - пример (адаптировать под конкретный CI Nubes)
stages:
- test
- build
- sign
- publish
variables:
HARBOR_HOST: "pearlharbor.registryk8s.services.ngcloud.ru"
S3_BUCKET: "terraform-registry"
PROVIDER_NAME: "nubes"
PROVIDER_NAMESPACE: "nubes"
test:
stage: test
image: golang:1.24
script:
- cd universal_rebuild
- go test ./...
- go vet ./...
build-provider:
stage: build
image: golang:1.24
script:
- cd universal_rebuild
- GOOS=linux GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_linux_amd64
- GOOS=darwin GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_darwin_amd64
- GOOS=windows GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_windows_amd64.exe
artifacts:
paths: [universal_rebuild/bin/]
build-images:
stage: build
script:
- docker build -t ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG} registry-server-build/
- docker build -t ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG} operator/
- docker push ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG}
- docker push ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG}
sign:
stage: sign
script:
- cd universal_rebuild/bin
- sha256sum terraform-provider-* > SHA256SUMS
- gpg --import $GPG_PRIVATE_KEY
- gpg --detach-sign SHA256SUMS
publish-to-s3:
stage: publish
script:
- s3cmd put bin/* s3://${S3_BUCKET}/terraform-providers/...
```
---
### B3. NetworkPolicy template
**Цель:** Подготовить NetworkPolicy для изоляции namespace.
**Включить в Helm chart:** `charts/terraform-registry/templates/networkpolicy.yaml`
```yaml
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "terraform-registry.fullname" . }}-netpol
namespace: {{ .Values.global.namespace }}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: {{ .Values.registry.service.targetPort }}
protocol: TCP
egress:
- to: []
ports:
- port: 443 # S3, Vault
protocol: TCP
- port: 53 # DNS
protocol: UDP
- port: 53
protocol: TCP
{{- end }}
```
---
## Группа C: Domain Migration Plan
### C1. Domain migration (порядок действий)
**Это НЕ код — это инструкция для DevOps. Записать в `docs/ops/DOMAIN_MIGRATION.md`:**
```markdown
# Миграция домена registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> → registry.nubes.ru
## Фаза 1: Dual-domain (параллельная работа)
1. Настроить Ingress с двумя hosts: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> + registry.nubes.ru
2. Оба домена указывают на один Registry Server
3. Обновить provider main.go: Address → registry.nubes.ru
4. Старый адрес registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> продолжает работать
## Фаза 2: Миграция клиентов
1. Документировать новый registry address для пользователей
2. .terraformrc mirror config для переходного периода:
provider_installation {
direct {
exclude = ["registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/*/*"]
}
network_mirror {
url = "https://registry.nubes.ru/v1/providers/"
}
}
## Фаза 3: Редирект
1. registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> Ingress → 301 redirect на registry.nubes.ru
2. Мониторинг: отслеживать запросы на старый домен
## Фаза 4: Деком (через 6+ месяцев)
1. Убрать registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> из Ingress
2. DNS → удалить A/CNAME запись
```
---
## Группа D: Code Quality (из CODEBASE_ANALYSIS_AND_ROADMAP.md)
### D1. Deprecated-маркеры для legacy CreateGenericInstance
**Файл:** `internal/core/client.go`
**Инструкция:** Добавить КОММЕНТАРИИ (не код) перед каждым методом V1-V5:
```go
// Deprecated: Use CreateGenericInstanceUniversalV5 instead.
// This method is kept for backward compatibility and will be removed in v3.0.
func (c *UniversalClient) CreateGenericInstance(...) ...
```
**Методы для пометки:**
- `CreateGenericInstance` (V1)
- `CreateGenericInstanceUniversal` (V2)
- `CreateGenericInstanceUniversalV2` (V3)
- `CreateGenericInstanceUniversalV3` (V4)
- `CreateGenericInstanceUniversalV4` (V5)
**Ограничения:** ТОЛЬКО комментарии. НЕ менять код методов. НЕ удалять.
---
### D2. .gitignore для secrets
**Файл:** `.gitignore` (корень репозитория)
**Добавить в КОНЕЦ файла:**
```gitignore
# Secrets (should be in Vault, not in git)
secrets/*.asc
secrets/*.token
secrets/*.key
!secrets/.gitkeep
```
**Создать:** `secrets/.gitkeep` (пустой файл, чтобы директория осталась в git)
---
### D3. Unit tests для core layer
**Цель:** Создать минимальный набор тестов.
**Создать файлы:**
- `universal_rebuild/internal/core/client_test.go`
- `universal_rebuild/internal/resources_core/crud_test.go`
**Минимальные тесты для `client_test.go`:**
- `TestNormalizeValue_EmptyString`
- `TestNormalizeValue_NullString`
- `TestNormalizeValue_MapType`
- `TestNormalizeValue_ArrayType`
- `TestNormalizeValue_TrimSpace`
**Минимальные тесты для `crud_test.go`:**
- `TestIsStatusSuspended`
- `TestIsStatusNonAdoptable`
- `TestDeleteBehaviorDefault`
**Ограничения:**
- Тесты — НОВЫЕ файлы (не менять существующие)
- Использовать стандартный `testing` пакет Go
- НЕ запускать тесты (`go test`) без разрешения оператора
---
## Порядок выполнения
```
ФАЗА 0 (быстрые wins):
D1 → Deprecated комментарии [5 мин]
D2 → .gitignore для secrets [2 мин]
A2 → debugLogPath() function [10 мин]
ФАЗА 1 (Helm chart):
A1 → Полный Helm chart [30-60 мин]
ФАЗА 2 (Ops docs):
A4 → RUNBOOK, TROUBLESHOOTING и др. [20 мин]
C1 → DOMAIN_MIGRATION.md [10 мин]
ФАЗА 3 (Code quality):
D3 → Unit tests [30 мин]
A3 → Health endpoints [15 мин]
ФАЗА 4 (CI/CD):
B2 → CI pipeline definition [15 мин]
B1 → Dockerfile audit [10 мин]
```
---
## Правила для агента (напоминание)
1. **IMMUTABILITY POLICY** — НЕ менять существующий рабочий код
2. **APPEND ONLY** — новый код только в конец файла
3. **Комментарии — ЭТО НЕ ПРАВКА КОДА**, их можно и нужно добавлять
4. **НЕ запускать** docker build, kubectl apply, helm install
5. **НЕ трогать** ресурсы с LetsEncrypt issuerRef
6. **Спрашивать разрешения** перед изменением сигнатур функций
7. **Secrets в коде** — КАТЕГОРИЧЕСКИ нет
8. **Перед любой работой** — прочитать `REPO_CONTENTS.md`
9. **Файлы в `internal/resources_gen/`** — НЕ МЕНЯТЬ ВРУЧНУЮ (только через генератор)
10. **Минимальные ресурсы** — при создании K8s ресурсов ставить минимальный CPU/memory
+268
View File
@@ -0,0 +1,268 @@
<!-- ⛔⛔⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ! Все примеры ниже — ИСТОРИЧЕСКИЕ. -->
<!-- Актуальный API: https://lk-api-gateway.ngcloud.ru/api/v1/svc -->
# How It Was Done — Developer Guide (закрытая страница)
**Filename & Versioning:** howitwasdone.md / 2026‑02‑04 / Draft v1
Этот документ — единый технический мануал. Он доступен только по прямой ссылке и не включён в публичную навигацию.
## Оглавление
1. Архитектура и методы (CRUD)
2. База знаний ошибок
3. Глоссарий (со ссылками на места употребления)
4. Билд и публикация провайдера и документации
---
## 1. Архитектура и методы (CRUD)
### 1.1 Архитектура универсального rebuild
- Ядро: universal_rebuild/internal/core (универсальный клиент API и общий flow операций).
- Провайдер: universal_rebuild/internal/provider (schema, конфигурация, подключение ресурсов).
- YAML-спеки: universal_rebuild/resources_yaml (источник истины).
- Генератор: universal_rebuild/tools/gen (генерация Go-ресурсов и registry).
Ключевое правило: новая логика — только новые функции/файлы. Существующий Go‑код не менять без согласования.
<a id="discovery"></a>
### 1.2 Источник параметров (discovery)
Параметры извлекаются через proxy endpoint API:
- /index.cfm?endpoint=/services/{svcId}
- /index.cfm?endpoint=/serviceOperation/{svcOperationId}
- (опц.) /index.cfm?endpoint=/param-value-list/{svcOperationCfsParamId}
Схема: сервис → операции → CFS параметры → YAML → генерация Go.
<a id="create-universal"></a>
### 1.3 Create (универсальный 7‑шаговый паттерн)
1) POST /instances → instanceUid
2) POST /instanceOperations (create) → instanceOperationUid
3) GET /instanceOperations/{uid}?fields=cfsParams
4) POST /instanceOperationCfsParams для каждого параметра
5) GET /instanceOperations/{uid}/validate-cfs
6) POST /instanceOperations/{uid}/run с payload {}
7) Polling до завершения операции
Критично: параметры отправляются все, включая дефолты.
<a id="read-adopt"></a>
### 1.4 Read / Adopt
- При isDeleted или explainedStatus=deleted — state очищается.
- При совпадении display_name и resume_if_exists=true — adopt/resume.
- Предупреждения показываются только на create и не мешают managed ресурсам.
<a id="update-modify"></a>
### 1.5 Update / Modify
- IDs параметров на modify отличаются от create.
- Нельзя хардкодить ID: нужно получать manifest операции и строить маппинг.
- Если modify отсутствует в availableOperations — выдаётся ясная ошибка.
<a id="polling-rules"></a>
### 1.6 Polling (железные правила)
- Завершение операции определяется только по dtFinish.
- После dtFinish результат определяется isSuccessful.
- Для VM применяется двойной контроль: статус операции + статус инстанса (ERROR/STOPPED).
### 1.7 Нормализация типов
- map/json → "{}"
- list/array → "[]"
- Пустые строки в JSON‑параметрах запрещены (валидаторы на plan).
<a id="soft-delete"></a>
### 1.8 Soft Delete
- Для тяжёлых ресурсов delete заменяется на suspend (карантин/retention).
- Повторный apply при suspend может выполнять resume.
---
## 2. База знаний ошибок
### A. Аутентификация и токены
**Токены лежат в `secrets/{dev,test,prod}.token`. Срок действия — до декабря 2026.**
Проверить дату JWT:
```bash
python3 -c "import json,base64; t=open('secrets/test.token').read().split('.'); d=json.loads(base64.urlsafe_b64decode(t[1]+'==')); from datetime import datetime,timezone; print(datetime.fromtimestamp(d['exp'],tz=timezone.utc))"
```
**Симптом:** 403 Forbidden (НЕ 401!)
**Причина (старый API index.cfm):** DDoS-Guard блокирует — нет Referer или нет User-Agent.
**Решение для curl (старый API):**
```bash
curl -s --max-time 10 \
-H "Authorization: Bearer $TOKEN" \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
-H "Referer: https://deck-test.ngcloud.ru/" \
"https://deck-api-test.ngcloud.ru/api/v1/index.cfm?endpoint=/services/90"
```
Referer должен совпадать со стендом (test/dev/prod). Новый Gateway (lk-api-gateway) требует только Authorization.
**Симптом:** 401 Unauthorized / Authorization header expected
**Причина:** токен истёк или не передан в окружение
**Решение:** обновить access_token и сохранить в файле HH‑MM‑SS.token; убедиться, что Terraform читает токен
### B. Поллинг зависает
**Симптом:** apply «висит», операция не завершается
**Причина:** ожидание по статусу инстанса/операции без dtFinish
**Решение:** критерий завершения — dtFinish; далее isSuccessful
### C. Modify: Invalid CFS parameter (400)
**Причина:** отправлены create IDs для modify
**Решение:** получать manifest операции и динамически маппить IDs
### D. Modify недоступен
**Симптом:** action modify not available
**Причина:** modify нет в availableOperations
**Решение:** проверять доступные операции и выдавать явную ошибку
### E. Map/JSON/List: Invalid format
**Симптом:** 400 Invalid format map/array/json
**Причина:** пустые строки вместо {} или []
**Решение:** нормализация по типу, trim, отправка {} или []
### F. VM: зависание на FW / 500 String[]→GUID
**Симптом:** зависание на стадии FW, 500 checkParam
**Причина:** пустые/невалидные JSON‑массивы, ошибки backend
**Решение:** валидация JSON массивов, дефолт ["0.0.0.0/0"], фильтрация пустых значений; при повторении — эскалация на поддержку
### G. VM modify: checkParam instanceOperationCfsParamUid
**Симптом:** 500 Invalid call of function checkParam
**Причина:** дублирование параметров при POST/PUT
**Решение:** гибридный PUT/POST по наличию instanceOperationCfsParamUid; проверить формат эндпойнта
### H. Postgres: split() on null
**Симптом:** Cannot invoke method split() on null object
**Причина:** передан UUID конкретного S3‑бакета
**Решение:** использовать UUID сервиса S3 (svcId 12), а не бакета
### I. GPG/Registry
**Симптом:** authentication signature from unknown issuer
**Причина:** ключ подписи не совпадает с ключом в registry
**Решение:** обновить signing_keys на сервере или пересобрать/подменить бинарник
**Симптом:** openpgp invalid data
**Причина:** ASCII‑armor для .sig
**Решение:** бинарная detached подпись без --armor
### J. Registry/S3
**Симптом:** presigned URL не работает через Ingress
**Причина:** Host заголовок ломает подпись
**Решение:** proxy‑режим download на сервере registry
### K. Документация: 404
**Причина:** неверный S3‑ключ (hostname в префиксе)
**Решение:** фиксированный префикс docs/<ns>/<name>/<version>/
### L. Версии Terraform Plugin Framework
**Симптом:** ошибки сборки при апгрейде
**Причина:** несовместимость framework и plugin‑go
**Решение:** использовать совместимые версии
### M. TLS handshake timeout
**Причина:** сетевые условия/VPN
**Решение:** повторить apply
---
## 3. Глоссарий (с ссылками на места употребления)
Каждый термин содержит ссылки на разделы этого же документа, где он используется.
- **instance** — экземпляр сервиса в Nubes Cloud.
- Ссылки: [Create](#create-universal)
- **instanceOperation** — асинхронная операция над instance (create/modify/delete/suspend/resume).
- Ссылки: [Create](#create-universal), [Polling](#polling-rules)
- **cfsParams** — список параметров операции, получаемый через manifest операции.
- Ссылки: [Create](#create-universal), [Discovery](#discovery)
- **svcOperationCfsParamId** — ID параметра операции, различается для create и modify.
- Ссылки: [Update / Modify](#update-modify)
- **dtFinish** — единственный надёжный индикатор завершения операции.
- Ссылки: [Polling](#polling-rules)
- **isSuccessful** — результат операции после dtFinish.
- Ссылки: [Polling](#polling-rules)
- **resourceRealm** — окружение/realm; иногда обязателен и задаётся пользователем.
- Ссылки: [Read / Adopt](#read-adopt)
- **display_name** — человекочитаемое имя; используется для adopt/resume.
- Ссылки: [Read / Adopt](#read-adopt)
- **resume_if_exists** — включение adopt/resume по display_name.
- Ссылки: [Read / Adopt](#read-adopt)
- **delete_mode** — режим удаления (delete/suspend/state_only).
- Ссылки: [Soft Delete](#soft-delete)
- **suspend** — мягкое удаление (карантин/retention).
- Ссылки: [Soft Delete](#soft-delete)
- **validate-cfs** — проверка параметров операции до run.
- Ссылки: [Create](#create-universal)
- **state.out** — выходные данные API; при отсутствии задаются null.
- Ссылки: [Read / Adopt](#read-adopt)
- **computed** — вычисляемые поля, должны быть известны после apply.
- Ссылки: [Read / Adopt](#read-adopt)
- **registry protocol** — API Terraform Registry (discovery + versions + download).
- Ссылки: [Публикация](#publish-registry)
- **signing_keys** — публичные GPG ключи, выдаваемые registry.
- Ссылки: [Публикация](#publish-registry)
- **NLI** — Natural Language Infrastructure (AI‑парсинг инструкций).
- Ссылки: [AI‑интеграция](#ai-nli)
---
## 4. Билд и публикация провайдера и документации
### 4.1 Сборка провайдера (universal_rebuild)
Общий цикл:
1) Генерация YAML параметров сервисов.
2) Генерация Go‑ресурсов.
3) Сборка бинарника go build.
Ключевые каталоги:
- universal_rebuild/resources_yaml
- universal_rebuild/internal/resources_gen
- universal_rebuild/tools/gen
<a id="publish-registry"></a>
### 4.2 Публикация провайдера в Registry
- Артефакты: zip, SHA256SUMS, SHA256SUMS.sig
- Подпись: для .sig использовать бинарную detached подпись
- Хранилище: S3 bucket terraform‑registry
- Префикс — по правилам registry‑сервера
### 4.3 Документация (MkDocs)
- Сборка: Docker образ squidfunk/mkdocs-material
- Результат: директория site/
- Публикация: scripts/publish-docs.sh
- Путь: docs/<namespace>/<name>/<version>/
### 4.4 Важные нюансы
- При смене домена обновлять registry и ключи подписи.
- Presigned URL через Ingress может ломаться — использовать proxy mode.
- Технические папки исключаются из публикации.
### 4.5 Рекомендованный порядок работ
1) Discovery параметров сервиса
2) Генерация YAML
3) Генерация Go‑ресурсов
4) Build
5) Тесты create/modify/suspend/resume
6) Документация и публикация
<a id="ai-nli"></a>
### 4.6 AI‑интеграция (NLI)
Кратко: реализована для ресурса Tubulus через ModifyPlan + askGemini, с защитой от двойного вызова AI.
### 4.7 Прямая ссылка
https://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 -->/docs/nubes/nubes/2.0.0/howitwasdone/