# Паттерн создания ресурсов "Tubulus" (Platinum Standard) В платформе Nubes Cloud большинство ресурсов (VDC, S3, DBaaS, Tubulus-болванки) подчиняются строгому 7-шаговому циклу создания. Простого `POST /instances` недостаточно — ресурс появится в UI, но останется в статусе "not created", пока не будет выполнен финальный `POST /run`. ## Пошаговый алгоритм ### Шаг 1: Инициализация (Create Instance) Отправляем `POST /instances`. - **Payload**: `contractId`, `serviceId`, `specificationItemId`, `displayName`. - **Результат**: Получаем `instanceUid`. ### Шаг 2: Создание операции (Create Operation) Отправляем `POST /instanceOperations` для привязки операции создания к инстансу. - **Payload**: `instanceUid`, `operation: "create"`. - **Результат**: Получаем `instanceOperationUid`. ### Шаг 3: Получение параметров (Fetch Parameters) Отправляем `GET /instanceOperations/{uid}?fields=cfsParams`. - **Цель**: Получить список параметров (`svcOperationCfsParamId`), которые платформа ожидает для этого ресурса. ### Шаг 4: Отправка параметров (Submit Parameters) — **Критично!** Для **каждого** параметра из списка (даже если он пустой или имеет значение по умолчанию) нужно отправить `POST /instanceOperationCfsParams`. - **Payload**: `instanceOperationUid`, `svcOperationCfsParamId`, `paramValue`. - **Важно**: Браузер всегда переотправляет все параметры. Пропуск этого шага может привести к ошибкам валидации. ### Шаг 5: Валидация (Validate) Отправляем `GET /instanceOperations/{uid}/validate-cfs`. - Проверяем, что ответ `200 OK` и нет ошибок в теле ответа. ### Шаг 6: Запуск (Run) — **Самый важный шаг!** Отправляем `POST /instanceOperations/{uid}/run`. - **Payload**: Обязательно `{}` (пустой JSON-объект). **НЕ `nil`**, а именно `{}`. - **Результат**: После этого шага бэкенд начинает реальную работу (например, вызывает Jenkins или Ansible). ### Шаг 7: Ожидание готовности (Polling) Периодически опрашиваем `GET /instances/{uid}`. - Ждем, когда `explainedStatus` станет `running` (для S3) или `Active`. --- ## Специфика S3 бакетов (`nubes_s3_bucket`) 1. **Модификация (Update)**: В текущем UI и API **отсутствует** операция `modify`. Ресурс S3 является иммутабельным в плане параметров бакета через этот API. Для изменения (например, размера) требуется пересоздание или использование других API (если они будут найдены). 2. **Удаление (Delete)**: Выполняется через стандартный `DELETE /instances/{id}`. 3. **Уникальность**: `displayName` и `bucket_name` должны быть уникальны в рамках контракта/платформы. Рекомендуется использовать суффиксы с временной меткой в тестах. 4. **Placement**: Допустимые значения: `HOT` (по умолчанию) или `COLD`. --- ## Рекомендации для разработчика провайдера - Объединяйте шаги 1-6 в одну функцию клиента (например, `CreateInstance` в `client_impl.go`). - Всегда передавайте пустую карту `map[string]interface{}{}` в метод `Run`, чтобы она сериализовалась в `{}`. - Тщательно логируйте `instanceOperationUid`, так как по нему можно отследить ошибки в логах платформы. ### Troubleshooting - **Висит в "not created"**: Вы забыли вызвать Шаг 6 (Run) или отправили неверный payload. - **Ошибка 400 на параметрах**: Проверьте, что все ID параметров соответствуют результату из Шага 3. ## Operation Lifecycle & Success Criteria (Official Developer Info) Операции в Nubes Cloud выполняются асинхронно. - **201 Created**: Операция запущена успешно. - **Polling (GET status)**: Каждый запрос статуса возвращает `200 OK`. Это означает, что платформа успешно обрабатывает запрос состояния, но не гарантирует успех самой операции. - **Completion Timestamp**: Операция считается завершенной только тогда, когда в ответе появляется временная метка завершения. - **isSuccessful**: Ключевой флаг, который определяет успех операции и отрисовывает "зелёную галочку" в интерфейсе. Если операция завершена, но `isSuccessful: false`, ресурс может находиться в неконсистентном состоянии. --- ## Suspend/Resume и совпадение параметров (Discovery Notes) Контекст: в Nubes Cloud многие сервисы **не удаляются напрямую**. `destroy` приводит к `suspend` и двухнедельному карантину. Это означает, что следующий `apply` может столкнуться с существующим инстансом в `suspend`. ### Принцип восстановления Если найден `suspended` инстанс с тем же `resource_name`, корректное поведение: 1. **Сравнить параметры** в Terraform (create‑params) с `instance.state.params`. 2. **Resume** выполнять только при полном совпадении параметров. 3. При несовпадении — вернуть стандартную ошибку "resource already exists". ### Потенциальные мины - **Несовпадение ключей**: `state.params` может использовать ключи, отличные от `code` в YAML — сравнение даст false. - **Типы/форматы**: bool/int/JSON/масивы могут быть сериализованы по‑разному (строка vs JSON). - **Defaults**: если параметр не задан в TF и приходит default от API, возможны ложные mismatches. - **RefSvcId**: в create могут отправляться UUID, а в `state.params` может быть имя — mismatch. - **Ручные изменения**: любые правки вне TF делают resume невозможным. - **Пустые params**: если API не возвращает `state.params`, сравнение всегда провалится. - **YAML/Go рассинхрон**: неверный маппинг ID→code приводит к ложным mismatch. - **Сеть/таймауты**: дополнительный GET `/instances/{id}` может фейлиться и блокировать resume. ### Экономический аспект `suspend` сохраняет данные и продолжает потреблять дисковое пространство — пользователь **продолжает платить**. Регулярное создание новых инстансов вместо восстановления `suspended` приведёт к росту расходов.