doc: README для DOCS/HISTORY, polygon-docs/, +.venv в gitignore
This commit is contained in:
@@ -8,6 +8,7 @@ secrets/*
|
|||||||
|
|
||||||
# Submodules / nested repos
|
# Submodules / nested repos
|
||||||
app-autotest/
|
app-autotest/
|
||||||
|
polygon/
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -19,6 +20,7 @@ Thumbs.db
|
|||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.venv/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# Go (embed.go generates from yaml)
|
# Go (embed.go generates from yaml)
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
# DOCS — Архив документации
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `ARCHITECTURE.md` | Актуальная архитектура: эндпоинты, схема БД, безопасность (§8 с фиксами аудита) |
|
||||||
|
| `ARCHITECTURE-FULL.md` | Развёрнутая архитектура: полный список файлов, все роуты, зависимости |
|
||||||
|
| `ARCHITECTURE.legacy.md` | Архитектура v1.0.x (до рефакторинга) |
|
||||||
|
| `architecture-final.md` | Финальная архитектура после всех рефакторингов и аудитов |
|
||||||
|
| `architecture-next.md` | Черновик архитектуры vNext — идеи на будущее |
|
||||||
|
| `architecture-questions-for-sonnet.md` | Вопросы к Sonnet по архитектуре (перед ревью) |
|
||||||
|
| `architecture-review-sonnet.md` | Ревью архитектуры от Sonnet |
|
||||||
|
| `architecture-round2.md` | Второй раунд архитектурных решений |
|
||||||
|
|
||||||
|
## Как делать (How-to)
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `howto-flask-nubes.md` | **ВАЖНО.** Как писать Flask-приложение под Nubes: структура `site/`, порт 5000, `app.run()`, запреты |
|
||||||
|
|
||||||
|
## API Nubes
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `api-access.md` | Как получить доступ к Nubes API: токены, clientId, стенды |
|
||||||
|
| `api-create-flow.md` | Полный flow создания инстанса: instances → instanceOperations → params → run → poll |
|
||||||
|
| `api-operation-stages.md` | Стадии выполнения операций (plan, apply, etc.) |
|
||||||
|
| `terraform-operations-full-logic.md` | Логика Terraform-операций: validate-cfs, refSvc, нормализация параметров |
|
||||||
|
|
||||||
|
## Планы
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `polygon-plan.md` | **ВАЖНО.** План мок-полигона: 3 фазы, 12 шагов, YAML-спецификация |
|
||||||
|
| `opus-plan-2026-07-31.md` | План Опуса: унификация executor, гибкие ссылки, 4 фазы 13 шагов |
|
||||||
|
| `test-results-history-plan.md` | План истории результатов тестов |
|
||||||
|
| `gpt56-sol-plan.md` | План Sol по GPT-5.6 |
|
||||||
|
|
||||||
|
## Промпты для агентов
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `agent-analysis-request.md` | Запрос на анализ проекта (архитектура, риски, рекомендации) |
|
||||||
|
| `opus-architecture-prompt.md` | Промпт Опусу: полное описание проекта + вопросы по архитектуре |
|
||||||
|
| `opus-frontend-prompt.md` | Промпт Опусу: вопросы по фронтенду (instance dropdown) |
|
||||||
|
| `gpt56-sol-prompt.md` | Промпт GPT-5.6: описание проекта для Sol |
|
||||||
|
| `sonnet-review-prompt.md` | Промпт Sonnet для code review |
|
||||||
|
|
||||||
|
## Ревью и аудиты — Sonnet
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `sonnet-full-code-review.md` | Полный code review всех файлов |
|
||||||
|
| `sonnet-full-response.md` | Полный ответ Sonnet на review |
|
||||||
|
| `sonnet-full-review.md` | Сводка review |
|
||||||
|
| `sonnet-architecture-review-v1.0.89.md` | Архитектурное ревью v1.0.89 |
|
||||||
|
| `sonnet-architecture-review-v1.0.89-r2.md` | Второй раунд архитектурного ревью |
|
||||||
|
| `sonnet-review-v1.1.11.md` | Ревью v1.1.11 |
|
||||||
|
| `sonnet-review-answers.md` | Ответы на замечания Sonnet |
|
||||||
|
| `sonnet-review-round2.md` | Второй раунд ревью |
|
||||||
|
| `sonnet-review-round3.md` | Третий раунд ревью |
|
||||||
|
| `sonnet-final-audit.md` | Финальный аудит |
|
||||||
|
| `sonnet-final-review.md` | Финальное ревью |
|
||||||
|
| `sonnet-missed-bugs.md` | Пропущенные баги (что Sonnet не нашёл) |
|
||||||
|
| `sonnet-params-and-audit.md` | Параметры + аудит |
|
||||||
|
| `sonnet-response-params-audit.md` | Ответ: параметры и аудит |
|
||||||
|
| `sonnet-response-final.md` | Финальный ответ |
|
||||||
|
| `sonnet-response-round2.md` | Ответ второго раунда |
|
||||||
|
| `sonnet-response-v1.1.11.md` | Ответ на ревью v1.1.11 |
|
||||||
|
| `sonnet-response-serviceInstanceUid.md` | Ответ про serviceInstanceUid |
|
||||||
|
| `sonnet-response-state-out.md` | Ответ про state.out |
|
||||||
|
| `sonnet-state-out-valuelist.md` | stateOut + valueList |
|
||||||
|
| `sonnet-terraform-diff.md` | Разбор Terraform diff |
|
||||||
|
| `sonnet-new-chat.md` | Новый чат с Sonnet |
|
||||||
|
| `sonnet-question-tracker.md` | Трекер вопросов к Sonnet |
|
||||||
|
|
||||||
|
## Ревью — Опус
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `opus-questions-2026-07-31.md` | 19 уточняющих вопросов Опуса по архитектуре |
|
||||||
|
| `opus-instance-dropdown-questions.md` | Вопросы Опуса про instance dropdown |
|
||||||
|
| `opus-review-v1.2.0.md` | Ревью v1.2.0 от Опуса |
|
||||||
|
|
||||||
|
## Вопросы-ответы
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `questions-to-sol.md` | Вопросы к Sol |
|
||||||
|
| `sol-answers.md` | Ответы Sol |
|
||||||
|
| `sol-scenario-editor.md` | Sol про редактор сценариев |
|
||||||
|
|
||||||
|
## Прочее
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `HISTORY.md` | Краткая хронология версий (v1.0.x → v1.2.x) |
|
||||||
|
| `dialogues.md` | Диалоги/дискуссии в процессе разработки |
|
||||||
|
| `service-categories.md` | Категории сервисов Nubes (37 сервисов, их типы) |
|
||||||
|
| `review-comparison-sonnet-opus.md` | Сравнение ревью Sonnet vs Opus |
|
||||||
|
| `step-by-step-audit-v1.0.50.md` | Пошаговый аудит v1.0.50 |
|
||||||
|
| `vm-213.md` | Заметки о VM-213 (тестовый стенд) |
|
||||||
@@ -504,3 +504,50 @@ convert_all(stands_dir) → dict[int, config]
|
|||||||
|
|
||||||
Конвертер помечает такие операции: `{svcOperationId, operation, subresource, action}`.
|
Конвертер помечает такие операции: `{svcOperationId, operation, subresource, action}`.
|
||||||
Один универсальный `apply_effect`, без сервис-специфичного кода.
|
Один универсальный `apply_effect`, без сервис-специфичного кода.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полигон — отдельная репа (2026-07-31)
|
||||||
|
|
||||||
|
### Решение: отдельный managed-сервис
|
||||||
|
|
||||||
|
Полигон выносится из `app-autotest/polygon/` в отдельный репозиторий
|
||||||
|
`https://gitea.services.ngcloud.ru/forcloud/polygon.git`.
|
||||||
|
|
||||||
|
Это НЕ часть app-autotest, а самостоятельный сервис:
|
||||||
|
`polygon.pythonk8s.dev.nubes.ru/` — эмулятор Nubes API.
|
||||||
|
|
||||||
|
### Структура (Nubes-совместимая, по howto-flask-nubes.md)
|
||||||
|
|
||||||
|
```
|
||||||
|
polygon/
|
||||||
|
├── requirements.txt
|
||||||
|
├── README.md
|
||||||
|
├── .gitignore
|
||||||
|
└── site/
|
||||||
|
├── app.py # точка входа, порт 5000
|
||||||
|
├── state.py # MockState
|
||||||
|
├── config_loader.py # загрузка YAML + defaults
|
||||||
|
├── defaults.py # default_for(dataType)
|
||||||
|
├── from_stands.py # конвертер STANDS YAML → polygon/services/*.yaml
|
||||||
|
└── services/ # генерится конвертером
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поправки к плану Опуса
|
||||||
|
|
||||||
|
| Было | Стало |
|
||||||
|
|------|-------|
|
||||||
|
| `polygon/server.py` на 5001 | `site/app.py` на 5000 |
|
||||||
|
| Папка внутри app-autotest | Отдельная репа |
|
||||||
|
| `NUBES_API_ENDPOINT=http://localhost:5001` | `http://localhost:5000` (или URL сервиса) |
|
||||||
|
| Только локально/CI | Можно задеплоить на Nubes |
|
||||||
|
|
||||||
|
### Ключевые правила
|
||||||
|
|
||||||
|
- `polygon/` репа = **только код**, никакой документации
|
||||||
|
- Документация полигона → `HISTORY/2026-07-31-session.md` (этот файл)
|
||||||
|
- `polygon/` добавлен в `.gitignore` основного репо
|
||||||
|
- Версионирование своё: polygon v0.1.0
|
||||||
|
- YAML не руками — через `from_stands.py` из STANDS (37 сервисов)
|
||||||
|
- `site/__init__.py` ⛔ нельзя
|
||||||
|
- Импорты без префикса `site.`
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# HISTORY — Хронология сессий
|
||||||
|
|
||||||
|
| Файл | Дата | Содержание |
|
||||||
|
|------|------|------------|
|
||||||
|
| `2026-07-23-session-1.md` | 23.07.2026 | Сессия 1 |
|
||||||
|
| `2026-07-23-session-2.md` | 23.07.2026 | Сессия 2 |
|
||||||
|
| `2026-07-23-session-3.md` | 23.07.2026 | Сессия 3 |
|
||||||
|
| `2026-07-23-session-4.md` | 23.07.2026 | Сессия 4 |
|
||||||
|
| `2026-07-24-all-failures.md` | 24.07.2026 | Все ошибки/падения |
|
||||||
|
| `2026-07-24-har-analysis.md` | 24.07.2026 | Анализ HAR-файлов |
|
||||||
|
| `2026-07-24-session-1.md` | 24.07.2026 | Сессия 1 |
|
||||||
|
| `2026-07-24-session-2.md` | 24.07.2026 | Сессия 2 |
|
||||||
|
| `2026-07-24-session-3.md` | 24.07.2026 | Сессия 3 |
|
||||||
|
| `2026-07-24-session-4.md` | 24.07.2026 | Сессия 4 |
|
||||||
|
| `2026-07-24-session-5.md` | 24.07.2026 | Сессия 5 |
|
||||||
|
| `2026-07-24-session-6.md` | 24.07.2026 | Сессия 6 |
|
||||||
|
| `2026-07-29-session.md` | 29.07.2026 | Сессия: CMDB delete, переписка с Георгием |
|
||||||
|
| `2026-07-30-session.md` | 30.07.2026 | Сессия |
|
||||||
|
| `2026-07-31-session.md` | 31.07.2026 | **Текущая.** Аудит Codex (3 раунда), полигон (план + отдельная репа), v1.2.16→v1.2.23 |
|
||||||
|
|
||||||
|
## Формат
|
||||||
|
|
||||||
|
Каждая сессия содержит:
|
||||||
|
- Контекст (что обсуждалось)
|
||||||
|
- Принятые решения
|
||||||
|
- Реализованные изменения (версии)
|
||||||
|
- Ошибки и как исправлены
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
# Задача: спроектировать мок-полигон Nubes API
|
||||||
|
|
||||||
|
> Адресат: Claude Sonnet 4.6
|
||||||
|
> Дата: 2026-07-31
|
||||||
|
> ⛔ ОТВЕТ — ТОЛЬКО В ЧАТ. Не редактировать файлы. Не создавать файлы. Только текст в чат.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Что такое polygon
|
||||||
|
|
||||||
|
**Polygon** — отдельный managed-сервис на Nubes (`polygon.pythonk8s.dev.nubes.ru`),
|
||||||
|
эмулирующий REST API облачной платформы Nubes. Нужен для интеграционных тестов
|
||||||
|
приложения **app-autotest** — чтобы тесты гонялись не на реальном облаке, а на
|
||||||
|
локальном/CI эмуляторе.
|
||||||
|
|
||||||
|
**Ключевое:** polygon — это НЕ часть app-autotest. Это самостоятельный сервис
|
||||||
|
со своим репозиторием (`https://gitea.services.ngcloud.ru/forcloud/polygon.git`),
|
||||||
|
своим деплоем, своей версией (v0.1.0).
|
||||||
|
|
||||||
|
### Текущее состояние
|
||||||
|
|
||||||
|
Сервис запущен на Nubes, код минимальный:
|
||||||
|
|
||||||
|
```
|
||||||
|
polygon/
|
||||||
|
├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0
|
||||||
|
├── README.md
|
||||||
|
├── .gitignore
|
||||||
|
└── site/
|
||||||
|
└── app.py # 4 эндпоинта: /health, /, /api/v1/svc/instances,
|
||||||
|
# /api/v1/svc/_mock/reset
|
||||||
|
```
|
||||||
|
|
||||||
|
`site/app.py` — стандартный Flask по инструкции Nubes:
|
||||||
|
- `app = Flask(__name__, template_folder="templates", static_folder="static")`
|
||||||
|
- `/health` → `"OK"` (healthcheck)
|
||||||
|
- `/` → HTML-страница с версией
|
||||||
|
- `app.run(debug=True, host="0.0.0.0", port=5000)` в `if __name__ == "__main__"`
|
||||||
|
|
||||||
|
Gunicorn на проде: `gunicorn app:app --workers 2 --bind 0.0.0.0:8000`.
|
||||||
|
|
||||||
|
## 2. Исходные данные: STANDS YAML
|
||||||
|
|
||||||
|
В `STANDS/test/resources_yaml/` лежат **37 YAML-файлов** — описания ВСЕХ сервисов
|
||||||
|
Nubes в формате Terraform-провайдера. Это КАНОНИЧЕСКИЙ источник данных о сервисах:
|
||||||
|
их параметры, операции, типы данных, дефолты, valueList'ы.
|
||||||
|
|
||||||
|
### Структура STANDS YAML (на примере dummy и postgres)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 1_dummy.yaml (простой сервис, 339 строк)
|
||||||
|
name: dummy
|
||||||
|
service_id: 1
|
||||||
|
service_display_name: Болванка
|
||||||
|
service_short_name: dummy
|
||||||
|
lifecycle:
|
||||||
|
suspend_on_destroy_default: true
|
||||||
|
adopt_existing_on_create_default: false
|
||||||
|
outputs:
|
||||||
|
params:
|
||||||
|
- code: state_params # map
|
||||||
|
- code: state_out # map
|
||||||
|
- code: vault_secrets # map, sensitive
|
||||||
|
# ... ещё 5 outputs
|
||||||
|
operations:
|
||||||
|
- name: create
|
||||||
|
id: 18
|
||||||
|
kind: instance
|
||||||
|
action: create
|
||||||
|
params:
|
||||||
|
- id: 242
|
||||||
|
code: resourceRealm
|
||||||
|
data_type: string
|
||||||
|
required: true
|
||||||
|
default: dummy
|
||||||
|
value_list: [dummy]
|
||||||
|
- id: 198
|
||||||
|
code: durationMs
|
||||||
|
data_type: "integer >= 0"
|
||||||
|
required: true
|
||||||
|
default: "0"
|
||||||
|
- id: 199
|
||||||
|
code: failAtStart
|
||||||
|
data_type: boolean
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
|
value_list: ["false", "true"]
|
||||||
|
- id: 321
|
||||||
|
code: someMapParam
|
||||||
|
data_type: map
|
||||||
|
sub_params:
|
||||||
|
- id: 322
|
||||||
|
code: subparam1
|
||||||
|
data_type: string
|
||||||
|
- id: 323
|
||||||
|
code: secret
|
||||||
|
data_type: string
|
||||||
|
- name: modify
|
||||||
|
id: 92
|
||||||
|
kind: instance
|
||||||
|
action: modify
|
||||||
|
params: [...] # те же параметры, is_modifiable: true
|
||||||
|
- name: delete
|
||||||
|
id: 71
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 90_postgres.yaml (самый сложный, 934 строки)
|
||||||
|
operations:
|
||||||
|
- name: create # id: 19, kind: instance
|
||||||
|
- name: delete # id: 20
|
||||||
|
- name: modify # id: 40
|
||||||
|
- name: suspend # id: 41
|
||||||
|
- name: resume # id: 42
|
||||||
|
- name: restart # id: 43 ← лишняя?
|
||||||
|
- name: recovery # id: 24 ← лишняя?
|
||||||
|
- name: create_user # id: 44 ← subresource!
|
||||||
|
- name: delete_user # id: 45 ← subresource!
|
||||||
|
- name: create_database # id: 46 ← subresource!
|
||||||
|
- name: delete_database # id: 47 ← subresource!
|
||||||
|
|
||||||
|
# У create-операции — map-fixed с sub_params:
|
||||||
|
- name: create
|
||||||
|
params:
|
||||||
|
- id: 788
|
||||||
|
code: clusterConfiguration
|
||||||
|
data_type: map-fixed
|
||||||
|
sub_params:
|
||||||
|
- id: 106
|
||||||
|
code: replicas
|
||||||
|
data_type: "integer > 0"
|
||||||
|
default: "1"
|
||||||
|
value_list: ["1", "3", "5", "7"]
|
||||||
|
- id: 107
|
||||||
|
code: disk
|
||||||
|
data_type: "integer > 0"
|
||||||
|
default: "10"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Спектр сложности (37 сервисов)
|
||||||
|
|
||||||
|
| Тип | Примеры | Особенности |
|
||||||
|
|-----|---------|-------------|
|
||||||
|
| Простые | dummy, s3, http, gitea, nodejs, flask | Плоские параметры, 6 операций |
|
||||||
|
| Средние | redis, mongodb, rabbitmq, clickhouse, kafka | map-fixed (cluster/access/startup) |
|
||||||
|
| Сложные | postgres, mariadb | + subresources (create_user, create_database) |
|
||||||
|
| Инфраструктурные | vc_org, vc_vdc, vc_nsxt, vapp, vc_vm_v2/v3 | Другие kind'ы, refSvcId на другие сервисы |
|
||||||
|
| Особые | s3bucket (refSvcId на s3), template, k8s_velero, llm_ai | Нестандартные параметры |
|
||||||
|
|
||||||
|
Все 37 файлов лежат здесь: `STANDS/test/resources_yaml/`
|
||||||
|
Полный список: `1_dummy.yaml`, `2_template.yaml`, `12_s3.yaml`, `13_s3bucket.yaml`,
|
||||||
|
`19_vc_org.yaml`, `21_vc_vdc.yaml`, `22_vc_nsxt.yaml`, `25_vcexternalip.yaml`,
|
||||||
|
`26_vapp.yaml`, `27_vc_vm_v2.yaml`, `28_vc_vm_v3.yaml`, `29_vc_vdc_group.yaml`,
|
||||||
|
`50_nextcloud.yaml`, `81_superset.yaml`, `82_harbor.yaml`, `86_k8s_velero.yaml`,
|
||||||
|
`89_flask.yaml`, `90_postgres.yaml`, `91_redis.yaml`, `92_mongodb.yaml`,
|
||||||
|
`93_rabbitmq.yaml`, `94_lucee.yaml`, `95_nodejs.yaml`, `96_pgadmin.yaml`,
|
||||||
|
`98_http.yaml`, `99_gitea.yaml`, `109_zones_v2.yaml`, `111_dnsrecord.yaml`,
|
||||||
|
`115_mariadb.yaml`, `116_kafka.yaml`, `119_akhq.yaml`, `120_clickhouse.yaml`,
|
||||||
|
`148_vc_mgmt_sthutrval_cluster.yaml`, `149_valo_tenant.yaml`,
|
||||||
|
`150_k8s_sthutrval_cluster.yaml`, `151_k8s_openbao.yaml`, `163_llm_ai.yaml`.
|
||||||
|
|
||||||
|
## 3. Архитектурные решения (уже приняты)
|
||||||
|
|
||||||
|
Предыдущий архитектор (Опус) принял 10 решений. Их нужно УЧЕСТЬ, но можно
|
||||||
|
ОСПОРИТЬ если найдёшь лучший подход.
|
||||||
|
|
||||||
|
| # | Решение | Обоснование |
|
||||||
|
|---|---------|-------------|
|
||||||
|
| 1 | Отдельный процесс :5000 | `http_client` делает реальные HTTP-запросы — blueprint не проверит |
|
||||||
|
| 2 | Data-driven: сервисы из YAML | Не хардкодить 37 сервисов в Python |
|
||||||
|
| 3 | Мин. поля + `default_for(dataType)` | 20+ параметров вручную — ад |
|
||||||
|
| 4 | Ленивый dtFinish | Без потоков, `MOCK_OP_DELAY` (по умолчанию 0.1с) |
|
||||||
|
| 5 | Единая стейт-машина | create→running→suspended→deleted |
|
||||||
|
| 6 | Реальный мерж params при modify | Иначе тест modify→проверить state.params бессмысленен |
|
||||||
|
| 7 | Статический stateOut из YAML | Для MVP, генерация из параметров — потом |
|
||||||
|
| 8 | `/_mock/reset` для тестов | Без сброса тесты влияют друг на друга |
|
||||||
|
| 9 | Тесты через `app_client` | Проверяет реальную связку app-autotest ↔ эмулятор |
|
||||||
|
| 10 | refSvcId игнорируем в MVP | validate-cfs всегда OK, ссылки не проверяются |
|
||||||
|
|
||||||
|
## 4. Критические точки интеграции (из кода app-autotest)
|
||||||
|
|
||||||
|
Это НЕ предположения — это факты из реального кода, который будет ходить в polygon:
|
||||||
|
|
||||||
|
### 4.1 Location-заголовки обязательны
|
||||||
|
|
||||||
|
`HttpClient.post()` достаёт UUID из заголовка `Location` (последний сегмент, `len >= 32`).
|
||||||
|
Polygon ОБЯЗАН отдавать:
|
||||||
|
- `POST /api/v1/svc/instances` → `201` + `Location: ./{instanceUid}`
|
||||||
|
- `POST /api/v1/svc/instanceOperations` → `201` + `Location: ./{instanceOperationUid}`
|
||||||
|
|
||||||
|
Без Location executor не получит UUID → ошибка.
|
||||||
|
|
||||||
|
### 4.2 Поллинг
|
||||||
|
|
||||||
|
`poll_until_done()` делает `GET /instanceOperations/{uid}?fields=...`,
|
||||||
|
ждёт `dtFinish`, спит 5 секунд между попытками. При `MOCK_OP_DELAY=0.1`
|
||||||
|
dtFinish появится на первом же GET — вторая итерация со сном не случится.
|
||||||
|
|
||||||
|
### 4.3 Short-circuit localhost в auth.py
|
||||||
|
|
||||||
|
`detect_endpoint()` в app-autotest хардкодит dev/test стенды и игнорирует
|
||||||
|
`NUBES_API_ENDPOINT`. Нужно добавить проверку: если endpoint начинается с
|
||||||
|
`http://localhost` или `http://127.0.0.1` → пропустить detect, сразу использовать.
|
||||||
|
Это будет сделано в app-autotest, не в polygon.
|
||||||
|
|
||||||
|
### 4.4 validate-cfs = пустое тело
|
||||||
|
|
||||||
|
`send_params_terraform()` считает успехом пустой/не-JSON ответ.
|
||||||
|
Polygon отдаёт `200` с пустым телом.
|
||||||
|
|
||||||
|
### 4.5 state.params по коду, cfsParams по числовому id
|
||||||
|
|
||||||
|
`get_params_with_current_values()` мержит `state.params[код]` с шаблоном из
|
||||||
|
`GET /instanceOperations/default/{opId}`.
|
||||||
|
YAML должен связывать числовой `svcOperationCfsParamId` ↔ код параметра.
|
||||||
|
|
||||||
|
### 4.6 Nubes-совместимость
|
||||||
|
|
||||||
|
Polygon деплоится как managed-сервис Nubes (pythonk8s). Следовательно:
|
||||||
|
- `site/app.py` — точка входа (платформа ждёт `python site/app.py`)
|
||||||
|
- `app.run(host="0.0.0.0", port=5000)` — обязательно
|
||||||
|
- ⛔ `site/__init__.py` — НЕЛЬЗЯ (конфликтует со stdlib `site.py`)
|
||||||
|
- ⛔ `from site.xxx import ...` — НЕЛЬЗЯ (импорт без префикса)
|
||||||
|
- `/health` → `"OK"` — healthcheck для Nubes
|
||||||
|
|
||||||
|
## 5. Что нужно от тебя
|
||||||
|
|
||||||
|
### 5.1 Архитектура проекта
|
||||||
|
|
||||||
|
Полная архитектура polygon с обоснованием каждого решения. Включая:
|
||||||
|
|
||||||
|
- **Структура файлов** в `site/` — какие модули, за что отвечают
|
||||||
|
- **Схема данных в памяти** — MockState: instances, operations, связи
|
||||||
|
- **Стейт-машина инстанса** — все статусы, переходы
|
||||||
|
- **Поток запроса** — что происходит от POST /instances до GET /instanceOperations/{uid}
|
||||||
|
- **Схема API** — полный список эндпоинтов с форматами запросов/ответов
|
||||||
|
|
||||||
|
### 5.2 Универсальный конвертер STANDS → polygon
|
||||||
|
|
||||||
|
Главный вопрос: **можно ли написать ОДИН конвертер для всех 37 сервисов без сервис-специфичного кода?**
|
||||||
|
|
||||||
|
Если да — спроектировать `from_stands.py`:
|
||||||
|
- Алгоритм конвертации одного YAML
|
||||||
|
- Маппинг ВСЕХ полей STANDS → polygon (таблица)
|
||||||
|
- Обработка краевых случаев (map-fixed, sub_params, subresources)
|
||||||
|
- Генерация stateParams из create-операции (defaults)
|
||||||
|
- Генерация stateOut (из операций create_user/create_database и т.п.)
|
||||||
|
- Выходной формат: отдельные файлы в `services/` или один dict?
|
||||||
|
|
||||||
|
Если нет — объяснить ГДЕ проходит граница и что придётся делать вручную.
|
||||||
|
|
||||||
|
### 5.3 Подробный план реализации
|
||||||
|
|
||||||
|
Пошаговый план с разбивкой на этапы. Для каждого этапа:
|
||||||
|
- Какие файлы создать/изменить
|
||||||
|
- Что именно реализовать (функции, классы, эндпоинты)
|
||||||
|
- Критерий готовности этапа
|
||||||
|
- Ожидаемые сложности
|
||||||
|
|
||||||
|
## 6. Конкретные вопросы
|
||||||
|
|
||||||
|
Ответь на каждый:
|
||||||
|
|
||||||
|
### Q1. from_stands.py
|
||||||
|
Как обрабатывать `map-fixed` параметры (clusterConfiguration, startupConfiguration)?
|
||||||
|
В STANDS: `params[].sub_params[]`. В реальном API: `dataDescriptor`.
|
||||||
|
Должен ли конвертер генерировать `dataDescriptor` из `sub_params`? Или моку
|
||||||
|
достаточно знать структуру чтобы принимать такие параметры при create?
|
||||||
|
|
||||||
|
### Q2. subresources
|
||||||
|
У postgres/mariadb есть `create_user`, `delete_user`, `create_database`, `delete_database`.
|
||||||
|
Можно ли вывести **общее правило**: «если операция имеет kind ≠ instance — это subresource»?
|
||||||
|
Или «если действие create/delete а операция не create/delete инстанса — subresource»?
|
||||||
|
|
||||||
|
Как универсально эмулировать subresource-операции? Сейчас идея: `apply_effect` смотрит на
|
||||||
|
`action` и `kind` — если `kind != "instance"`, значит меняем `state.out`, не `state.params`.
|
||||||
|
|
||||||
|
### Q3. stateOut
|
||||||
|
В реальном API после create PostgreSQL: `state.out = {users: {pgadmin: {}}, databases: {mydb: {}}}`.
|
||||||
|
Может ли конвертер автоматически вывести структуру stateOut из наличия subresource-операций?
|
||||||
|
Например: есть `create_user` → добавить `users: {}` в stateOut.
|
||||||
|
|
||||||
|
### Q4. Лишние операции
|
||||||
|
У postgres есть restart, recovery. У некоторых сервисов есть специфичные операции.
|
||||||
|
Включать их в мок? Если да — как эмулировать? Если нет — что отвечать на запрос
|
||||||
|
`GET /instanceOperations/default/{id}` для несуществующей операции?
|
||||||
|
|
||||||
|
### Q5. valueList
|
||||||
|
В STANDS YAML `value_list` есть у многих параметров. Нужно ли моку хранить их
|
||||||
|
и отдавать в `GET /instanceOperations/default/{id}` (cfsParams)? Или мок может
|
||||||
|
отдавать пустой `valueList` для всех?
|
||||||
|
|
||||||
|
### Q6. Config loader vs генерация
|
||||||
|
Два подхода:
|
||||||
|
- **A)** `from_stands.py` генерит YAML в `services/`, `config_loader.py` их читает при старте
|
||||||
|
- **B)** `from_stands.py` сразу возвращает dict, без промежуточных YAML
|
||||||
|
|
||||||
|
Какой лучше? Плюсы A: можно редактировать сгенерированные YAML руками для сложных сервисов.
|
||||||
|
Плюсы B: проще, меньше файлов.
|
||||||
|
|
||||||
|
### Q7. mock-специфичные эндпоинты
|
||||||
|
`/_mock/reset` — сброс состояния. Нужны ли ещё?
|
||||||
|
- `/_mock/state` — посмотреть текущее состояние (для отладки)?
|
||||||
|
- `/_mock/delay/{seconds}` — изменить MOCK_OP_DELAY на лету?
|
||||||
|
- `/_mock/services` — список загруженных сервисов?
|
||||||
|
|
||||||
|
### Q8. Идентификаторы
|
||||||
|
UUID для instanceUid и instanceOperationUid — генерить через `uuid.uuid4()`?
|
||||||
|
Или детерминированно (например, `uuid5(namespace, service_id + counter)`)?
|
||||||
|
|
||||||
|
### Q9. Пагинация
|
||||||
|
`GET /instances?pageSize=N&page=P` — как правильно эмулировать?
|
||||||
|
Стоп по `len(batch) < pageSize`? Что если pageSize > 200?
|
||||||
|
|
||||||
|
### Q10. Тестирование
|
||||||
|
5 сценариев из плана Опуса — достаточны? Какие ещё сценарии добавить?
|
||||||
|
Как организовать тесты чтобы они не зависели друг от друга?
|
||||||
|
|
||||||
|
## 7. Что можно спрашивать у меня
|
||||||
|
|
||||||
|
Ты можешь задавать мне уточняющие вопросы в текстовом виде. Я отвечу.
|
||||||
|
|
||||||
|
Что можно спрашивать:
|
||||||
|
- Уточнения по STANDS YAML (структура конкретных сервисов, поля которые не видел)
|
||||||
|
- Детали работы app-autotest (какие запросы шлёт, какие форматы ждёт)
|
||||||
|
- Приоритеты (что важнее сделать сначала)
|
||||||
|
- Ограничения Nubes-платформы
|
||||||
|
|
||||||
|
Что НЕ надо спрашивать:
|
||||||
|
- «Какие технологии использовать?» — только Flask, Python, PyYAML
|
||||||
|
- «Может использовать БД?» — нет, всё в памяти
|
||||||
|
- «Может Kubernetes API?» — нет, это чистый HTTP-эмулятор
|
||||||
|
|
||||||
|
Формат вопросов:
|
||||||
|
```
|
||||||
|
### Вопрос 1: <краткий заголовок>
|
||||||
|
<развёрнутый вопрос с контекстом>
|
||||||
|
```
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# Ответ Claude Sonnet 4.6 — архитектура и план polygon
|
||||||
|
|
||||||
|
Дата: 2026-07-31
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ответы на Q1–Q10
|
||||||
|
|
||||||
|
**Q1: map-fixed → dataDescriptor**
|
||||||
|
Конвертер ГЕНЕРИРУЕТ `dataDescriptor` из `sub_params`. Формат каждой записи:
|
||||||
|
```
|
||||||
|
{sub_param_code: {dataType, defaultValue, valueList, isRequired}}
|
||||||
|
```
|
||||||
|
Это обязательно: `normalize_value` в app-autotest использует `dataDescriptor` для генерации JSON из defaults при пустом значении map-параметра.
|
||||||
|
|
||||||
|
**Q2: subresources**
|
||||||
|
Правило простое и точное: `kind == "subresource"` (это явное поле в STANDS YAML).
|
||||||
|
`apply_effect` смотрит на `kind`:
|
||||||
|
- `kind == "instance"` → меняем статус + `state.params`
|
||||||
|
- `kind == "subresource"` → меняем `state.out[plural]`, статус НЕ меняем
|
||||||
|
|
||||||
|
**Q3: stateOut auto-generation**
|
||||||
|
Да, конвертер выводит автоматически. Сканирует операции с `kind=subresource, action=create`, строит `state_out_template`. Пример для postgres: `{users: {}, databases: {}, backups: {}}`. При create инстанса мок копирует этот шаблон в `instance.state.out`.
|
||||||
|
|
||||||
|
**Q4: "лишние" операции**
|
||||||
|
Включить все (restart, recovery, etc.) — иначе `GET /instanceOperations/default/{id}` вернёт 404 и app-autotest упадёт. Реализация: no-op transition (статус остаётся `running`, dtFinish ставится, isSuccessful=True). Если operation_id вообще не найден → 404 с `{"error": "operation not found"}`.
|
||||||
|
|
||||||
|
**Q5: valueList**
|
||||||
|
Хранить и отдавать обязательно. `get_params_with_current_values` в app-autotest заполняет из него дропдауны для `delete_user`/`delete_database`.
|
||||||
|
|
||||||
|
**Q6: Подход A (generate YAML → services/)**
|
||||||
|
Выбрать A. `from_stands.py` генерирует `site/services/N_name.yaml`, `config_loader.py` читает при старте Flask. Плюс: ручная правка для сложных случаев, прозрачность. `from_stands.py` запускается один раз при setup.
|
||||||
|
|
||||||
|
**Q7: Mock-эндпоинты**
|
||||||
|
Все 4:
|
||||||
|
- `POST /_mock/reset` — обязателен для тестовой изоляции
|
||||||
|
- `GET /_mock/state` — текущее состояние (отладка)
|
||||||
|
- `GET /_mock/services` — список загруженных сервисов
|
||||||
|
- `POST /_mock/delay/<float>` — изменить MOCK_OP_DELAY на лету
|
||||||
|
|
||||||
|
**Q8: UUID** → `uuid.uuid4()`. Детерминированные не нужны.
|
||||||
|
|
||||||
|
**Q9: Пагинация** → stop when `len(batch) < pageSize`, cap at 200, page=1 by default. Response: `{"results": [...], "pageSize": N, "page": P, "total": len(all)}`.
|
||||||
|
|
||||||
|
**Q10: Тесты**
|
||||||
|
К 5 сценариям Опуса добавить: (a) тест пагинации (3 инстанса, pageSize=2), (b) тест subresource create→verify state.out→delete→verify removed, (c) тест modify→verify params merged, (d) тест /_mock/state, (e) тест 404 на невалидный op_id. Каждый тест начинается с `POST /_mock/reset` (через `autouse` fixture).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
polygon/
|
||||||
|
├── requirements.txt
|
||||||
|
└── site/
|
||||||
|
├── app.py # Flask app + все 15 маршрутов (один файл)
|
||||||
|
├── mock_state.py # MockState: instances, operations, op_params
|
||||||
|
├── state_machine.py # apply_effect()
|
||||||
|
├── config_loader.py # load_services() → services_dict + ops_index
|
||||||
|
├── from_stands.py # CLI-конвертер STANDS YAML → services/*.yaml
|
||||||
|
├── services/ # 37 YAML-файлов (generated)
|
||||||
|
└── templates/
|
||||||
|
└── index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ключевое ограничение Nubes:**
|
||||||
|
- `site/__init__.py` — ЗАПРЕЩЁН
|
||||||
|
- `from site.xxx import` — ЗАПРЕЩЁН
|
||||||
|
- В app.py: только `import mock_state`, `import config_loader`, `import state_machine` (работает т.к. Python добавляет директорию скрипта в `sys.path[0]`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Схема данных в памяти
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mock_state.py — синглтон state = MockState()
|
||||||
|
class MockState:
|
||||||
|
instances = {} # instanceUid → instance_dict
|
||||||
|
operations = {} # opUid → operation_dict
|
||||||
|
op_params = {} # opUid → {paramId(int): paramValue(str)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**instance_dict:** `{instanceUid, serviceId, displayName, descr, status, explainedStatus, svc, dtCreate, state: {params: {code: val}, out: {users: {}, databases: {}}}}`
|
||||||
|
|
||||||
|
**operation_dict:** `{instanceOperationUid, instanceUid, svcOperationId, operation, kind, action, subresource|None, dtStart, dtFinish|None, isSuccessful|None, errorLog, svc, stages}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Конвертер from_stands.py
|
||||||
|
|
||||||
|
**HTML entities:** STANDS YAML содержит `integer >= 0`, `"` и т.д. `from_stands.py` **обязан** применять `html.unescape()` ко всем строковым полям (`data_type`, `value_list` элементы, `default`).
|
||||||
|
|
||||||
|
**Маппинг STANDS → cfsParam формат:**
|
||||||
|
|
||||||
|
| STANDS | polygon/cfsParam |
|
||||||
|
|---|---|
|
||||||
|
| `id` | `svcOperationCfsParamId` |
|
||||||
|
| `code` | `svcOperationCfsParam` |
|
||||||
|
| `data_type` (unescape) | `dataType` |
|
||||||
|
| `required` | `isRequired` |
|
||||||
|
| `default` | `defaultValue` |
|
||||||
|
| `value_list` | `valueList` |
|
||||||
|
| `sub_params` (map-fixed) | → `dataDescriptor: {code: {dataType,defaultValue,valueList,isRequired}}` |
|
||||||
|
| `sub_params` (map) | → `sub_params` (хранить как есть для normalize_value) |
|
||||||
|
|
||||||
|
**stateOut auto-generation:**
|
||||||
|
```python
|
||||||
|
for op in operations:
|
||||||
|
if op.kind == "subresource" and op.action == "create":
|
||||||
|
plural = op.subresource + "s" # user→users, database→databases
|
||||||
|
state_out_template[plural] = {}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полный список API эндпоинтов (15 шт.)
|
||||||
|
|
||||||
|
| # | Метод | Путь | Назначение |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | GET | `/health` | `"OK"` |
|
||||||
|
| 2 | GET | `/` | HTML-страница |
|
||||||
|
| 3 | GET | `/api/v1/svc/instances` | Список инстансов (paged) |
|
||||||
|
| 4 | GET | `/api/v1/svc/instances/<uid>` | Детали инстанса |
|
||||||
|
| 5 | POST | `/api/v1/svc/instances` | Создать shell → 201 + Location |
|
||||||
|
| 6 | GET | `/api/v1/svc/instanceOperations/default/<int:op_id>` | Шаблон операции |
|
||||||
|
| 7 | POST | `/api/v1/svc/instanceOperations` | Создать операцию → 201 + Location |
|
||||||
|
| 8 | GET | `/api/v1/svc/instanceOperations/<uid>` | Детали операции + cfsParams |
|
||||||
|
| 9 | POST | `/api/v1/svc/instanceOperationCfsParams` | Задать значение параметра |
|
||||||
|
| 10 | GET | `/api/v1/svc/instanceOperations/<uid>/validate-cfs` | Валидация → 200 **пустое тело** |
|
||||||
|
| 11 | POST | `/api/v1/svc/instanceOperations/<uid>/run` | Выполнить операцию |
|
||||||
|
| 12 | POST | `/api/v1/svc/_mock/reset` | Сброс состояния |
|
||||||
|
| 13 | GET | `/api/v1/svc/_mock/state` | Debug: текущее состояние |
|
||||||
|
| 14 | GET | `/api/v1/svc/_mock/services` | Debug: загруженные сервисы |
|
||||||
|
| 15 | POST | `/api/v1/svc/_mock/delay/<float:s>` | Изменить MOCK_OP_DELAY |
|
||||||
|
|
||||||
|
**Критические детали:**
|
||||||
|
|
||||||
|
- **Эндпоинт 5** (`POST /instances`): возвращает `201` + заголовок `Location: ./{uid}`. `HttpClient.post()` берёт uid из последнего сегмента Location, проверяет `len >= 32`. Body: `{"instanceUid": uid}` (двойная защита).
|
||||||
|
|
||||||
|
- **Эндпоинт 7** (`POST /instanceOperations`): при `operation=="create"` тело НЕ содержит `svcOperationId` — polygon находит его сам из `svc_def.operations["create"]["id"]`.
|
||||||
|
|
||||||
|
- **Эндпоинт 10** (validate-cfs): `return "", 200` (НЕ `jsonify`). app-autotest ловит `JSONDecodeError` и считает это успехом.
|
||||||
|
|
||||||
|
- **Эндпоинт 11** (run): синхронный — `time.sleep(MOCK_OP_DELAY)`, затем `apply_effect`, затем `dtFinish = datetime.utcnow().isoformat() + "Z"`. Первый poll после run увидит dtFinish.
|
||||||
|
|
||||||
|
- **Маршруты Flask:** `/instanceOperations/default/<int:op_id>` должен стоять **выше** `/instanceOperations/<uid>` в app.py.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Поток запроса (полный цикл create)
|
||||||
|
|
||||||
|
```
|
||||||
|
executor.py polygon
|
||||||
|
───────── ───────
|
||||||
|
POST /instances {serviceId,displayName}
|
||||||
|
→ создать instance shell (status="creating")
|
||||||
|
→ response 201 + Location: ./uid1
|
||||||
|
← instanceUid = uid1
|
||||||
|
|
||||||
|
POST /instanceOperations {instanceUid, operation:"create"}
|
||||||
|
→ найти create op_id из service_def
|
||||||
|
→ создать operation_dict (dtFinish=None)
|
||||||
|
→ response 201 + Location: ./uid2
|
||||||
|
← opUid = uid2
|
||||||
|
|
||||||
|
GET /instanceOperations/uid2?fields=cfsParams
|
||||||
|
→ response {"instanceOperation": {"cfsParams": [...]}}
|
||||||
|
cfsParams из service_def + пустые paramValue
|
||||||
|
|
||||||
|
POST /instanceOperationCfsParams × N → state.op_params[uid2][paramId] = val
|
||||||
|
GET /instanceOperations/uid2/validate-cfs → response "" 200
|
||||||
|
POST /instanceOperations/uid2/run
|
||||||
|
→ sleep(0.1s)
|
||||||
|
→ apply_effect: state.params←codes, status="running"
|
||||||
|
→ dtFinish = now
|
||||||
|
→ response {"ok": true}
|
||||||
|
|
||||||
|
GET /instanceOperations/uid2?fields=dtFinish,...
|
||||||
|
→ response {"instanceOperation": {"dtFinish": "2026-...", "isSuccessful": true, ...}}
|
||||||
|
← poll done: status="OK"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фазы реализации
|
||||||
|
|
||||||
|
**Этап 1 — Конвертер** (`from_stands.py`): html.unescape, convert_param рекурсивный, build_state_out_template, pluralize, CLI. Запустить: `cd polygon/site && python from_stands.py`. Критерий: 37 YAML в `site/services/`, у postgres `state_out_template: {users:{}, databases:{}, backups:{}}`.
|
||||||
|
|
||||||
|
**Этап 2 — MockState + config_loader + state_machine**: три отдельных модуля. Критерий: `python -c "import config_loader; s,i=config_loader.load_services('services'); print(len(s))"` → 37.
|
||||||
|
|
||||||
|
**Этап 3 — Flask API** в app.py: реализовать все 15 эндпоинтов в порядке от простых к сложным (см. таблицу). Критерий: `python -m py_compile app.py` без ошибок, `curl localhost:5000/health` → OK.
|
||||||
|
|
||||||
|
**Этап 4 — Тесты**:
|
||||||
|
- В polygon: `tests/test_converter.py`, `tests/test_state_machine.py` (юнит)
|
||||||
|
- В app-autotest: `tests/conftest.py` (+fixture `polygon_server` subprocess), `tests/test_polygon_integration.py` (интеграционные)
|
||||||
|
|
||||||
|
|
||||||
|
## Уточнения (второй раунд)
|
||||||
|
|
||||||
|
### Уточнение 1: `map` с `sub_params` → тоже `dataDescriptor`
|
||||||
|
|
||||||
|
Правило: **если у параметра есть `sub_params` — генерировать `dataDescriptor`**, независимо от того `map` это или `map-fixed`.
|
||||||
|
|
||||||
|
| `data_type` | `sub_params` | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| `map-fixed` | есть | генерировать `dataDescriptor` |
|
||||||
|
| `map` | есть | генерировать `dataDescriptor` |
|
||||||
|
| `array-map-fixed` | есть | генерировать `dataDescriptor` |
|
||||||
|
| любой | нет | `dataDescriptor: null` |
|
||||||
|
|
||||||
|
### Уточнение 2: `state.out` после create — пустые `{}`
|
||||||
|
|
||||||
|
Достаточно пустых `{}` из `state_out_template`. STANDS YAML не описывает runtime-дефолты (`pgadmin`, `mydb`) — это эффект реального Terraform, не мок.
|
||||||
|
|
||||||
|
### Уточнение 3: расположение тестов
|
||||||
|
|
||||||
|
```
|
||||||
|
app-autotest/
|
||||||
|
tests/
|
||||||
|
conftest.py # + fixture polygon_server (subprocess)
|
||||||
|
test_polygon_integration.py # интеграционные тесты
|
||||||
|
|
||||||
|
polygon/
|
||||||
|
tests/
|
||||||
|
test_converter.py # юнит: from_stands.py
|
||||||
|
test_state_machine.py # юнит: apply_effect
|
||||||
|
```
|
||||||
|
|
||||||
|
`polygon_server` fixture: subprocess → poll `/health` (timeout 5s) → после тестов `terminate()`.
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Ссылки: генератор YAML (tf_provider)
|
||||||
|
|
||||||
|
Репозиторий Terraform-провайдера Nubes: `~/tf_provider`
|
||||||
|
|
||||||
|
## Как генерируются STANDS YAML
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `~/tf_provider/README.md` | Общий пайплайн: 4 шага генерации провайдера |
|
||||||
|
| `~/tf_provider/docs/README.md` | **Главный индекс документации.** Формат токенов (не протухают), DDoS-Guard заголовки, Gateway URL |
|
||||||
|
| `~/tf_provider/docs/HOWTO_ADD_NEW_SERVICE.md` | Как добавить сервис: `services_list.txt` → `01_generate_yamls.sh` → API → YAML |
|
||||||
|
| `~/tf_provider/docs/ARCHITECTURE_NEW.md` | Архитектура: слои (core/provider/resources_gen/yaml), метод Виталия без instanceUid |
|
||||||
|
| `~/tf_provider/docs/70_api/api-discovery-algorithms.md` | **Ключевой документ.** Алгоритм получения параметров: `/instanceOperations/default/{id}` → `cfsParams` с `dataDescriptor`, `valueList`, `isModifiable` |
|
||||||
|
|
||||||
|
## Критические API-эндпоинты (метод Виталия)
|
||||||
|
|
||||||
|
Цепочка из 3 запросов — получение ВСЕХ параметров сервиса без создания инстанса:
|
||||||
|
|
||||||
|
1. `GET /api/v1/svc/services` → найти `svcId` по имени
|
||||||
|
2. `GET /api/v1/svc/services/{svcId}` → найти `svcOperationId` операции (create: `{svc.operations[?operation=="create"].svcOperationId}`)
|
||||||
|
3. `GET /api/v1/svc/instanceOperations/default/{svcOperationId}` → **все cfsParams**: `dataDescriptor` (подполя map-fixed), `valueList`, `isModifiable`, `isSensitive`, `regex`, ...
|
||||||
|
|
||||||
|
### Почему `/instanceOperations/default/{id}` а не `/serviceOperation/{id}`:
|
||||||
|
|
||||||
|
| Данные | `/serviceOperation/{id}` | `/instanceOperations/default/{id}` |
|
||||||
|
|--------|--------------------------|-------------------------------------|
|
||||||
|
| ID, code, тип, isRequired | ✅ | ✅ |
|
||||||
|
| `valueList` (допустимые значения) | ❌ | ✅ |
|
||||||
|
| `dataDescriptor` (подполя map-fixed) | ❌ | ✅ |
|
||||||
|
| `isModifiable` | ❌ | ✅ |
|
||||||
|
| `regex`, `maxLength`, `minValue`... | ❌ | ✅ |
|
||||||
|
| `isSensitive` | ❌ | ✅ |
|
||||||
|
|
||||||
|
### Формат valueList:
|
||||||
|
|
||||||
|
- Для **верхнеуровневых** параметров: JSON-массив `["false", "true"]`
|
||||||
|
- Для **подполей** dataDescriptor: comma-separated строка `"1,3,5,7"`
|
||||||
|
|
||||||
|
## Где взять реальные ответы API (для сверки мока)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Токены (НЕ протухают до декабря 2026):
|
||||||
|
~/tf_provider/secrets/test.token
|
||||||
|
~/tf_provider/secrets/dev.token
|
||||||
|
~/tf_provider/secrets/prod.token
|
||||||
|
|
||||||
|
# Проверить дату токена:
|
||||||
|
python3 -c "
|
||||||
|
import json,base64
|
||||||
|
t=open('$HOME/tf_provider/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))
|
||||||
|
"
|
||||||
|
|
||||||
|
# Пример curl (test стенд):
|
||||||
|
curl -s --max-time 10 \
|
||||||
|
-H "Authorization: Bearer $(cat ~/tf_provider/secrets/test.token)" \
|
||||||
|
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
|
||||||
|
-H "Referer: https://deck-test.ngcloud.ru/" \
|
||||||
|
"https://lk-api-gateway-test.ngcloud.ru/api/v1/svc/instanceOperations/default/<opId>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⛔ DDoS-Guard: ВСЕГДА нужны 3 заголовка
|
||||||
|
|
||||||
|
**Без них — 403 Forbidden даже с валидным токеном.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
|
||||||
|
-H "Referer: https://deck-{stand}.ngcloud.ru/"
|
||||||
|
```
|
||||||
|
|
||||||
|
### URL стендов (Gateway)
|
||||||
|
|
||||||
|
| Стенд | Gateway URL | Referer |
|
||||||
|
|-------|-------------|---------|
|
||||||
|
| test | `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc` | `https://deck-test.ngcloud.ru/` |
|
||||||
|
| dev | `https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc` | `https://deck-dev.ngcloud.ru/` |
|
||||||
|
| prod | `https://lk-api-gateway.ngcloud.ru/api/v1/svc` | `https://deck.ngcloud.ru/` |
|
||||||
|
|
||||||
|
## Сгенерированные YAML (все стенды)
|
||||||
|
|
||||||
|
| Стенд | Сервисов | Путь |
|
||||||
|
|-------|----------|------|
|
||||||
|
| dev | 50 | `~/tf_provider/generated/dev/resources_yaml/` |
|
||||||
|
| test | 48 | `~/tf_provider/generated/test/resources_yaml/` |
|
||||||
|
| prod | 46 | `~/tf_provider/generated/prod/resources_yaml/` |
|
||||||
|
|
||||||
|
44 сервиса идентичны на всех трёх стендах.
|
||||||
|
|
||||||
|
## Поток данных: API → polygon
|
||||||
|
|
||||||
|
```
|
||||||
|
API Nubes (реальный)
|
||||||
|
→ 01_generate_yamls.sh
|
||||||
|
→ ~/tf_provider/generated/test/resources_yaml/*.yaml
|
||||||
|
→ копируются в STANDS/test/resources_yaml/ (autotest)
|
||||||
|
→ from_stands.py конвертирует
|
||||||
|
→ polygon/site/services/*.yaml
|
||||||
|
→ config_loader.py загружает при старте
|
||||||
|
→ polygon эмулирует /instanceOperations/default/{id}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user