feat: stages output + services_list sync
- stages always visible (remove log_level gating) - fix tty resource leak (move out of poll loop) - show current stage [..] in addition to completed [OK]/[FAIL] - sync services_list.txt for all 3 stands with live UI - add YAML vs API comparison script - bump TEST version 5.0.1 -> 5.0.2
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
# Sonnet Briefing: вывод этапов (stages) в Terraform-провайдере Nubes
|
||||
|
||||
> Цель задания: изучить код и выдать **точный план** — какие строки в каких файлах менять.
|
||||
> Без реализации. Только анализ и unified diff.
|
||||
|
||||
---
|
||||
|
||||
## 1. Суть проблемы
|
||||
|
||||
### Как сейчас (плохо)
|
||||
При `terraform apply/destroy` пользователь видит тупой счётчик:
|
||||
```
|
||||
nubes_postgres.pg_db: Still creating... [00m10s elapsed]
|
||||
nubes_postgres.pg_db: Still creating... [00m20s elapsed]
|
||||
nubes_postgres.pg_db: Still creating... [00m30s elapsed]
|
||||
```
|
||||
|
||||
Это сообщения самой Terraform (не нашего кода) — фреймворк показывает их, пока ресурс находится в состоянии создания/удаления.
|
||||
|
||||
### Как должно быть (как в autotest)
|
||||
```
|
||||
[OK ] 1. Валидация — 63.3 sec
|
||||
[OK ] 2. Конфигурация — 0.3 sec
|
||||
[OK ] 3. Внешний IP — 2.2 sec
|
||||
[..] 4. Доступ — 1.3 sec ← ТЕКУЩИЙ этап (ещё идёт)
|
||||
5. DNS — ещё не начат
|
||||
6. Основной процесс
|
||||
7. Проверки
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Эталонная реализация — autotest
|
||||
|
||||
**Файл:** `/home/naeel/nubes/autotest/app-autotest/site/static/js/operations.js:331`
|
||||
|
||||
```js
|
||||
function showStages(stages){
|
||||
if(!stages||!stages.length) return;
|
||||
let html='<div style="font-size:11px;...">Этапы</div>';
|
||||
stages.forEach(s=>{
|
||||
const done=!!s.dtFinish; // этап завершён?
|
||||
const icon=done?(s.isSuccessful?'✅':'❌'):'⏳'; // ⏳ = текущий
|
||||
html+=`<div>${icon} ${s.stage} — ${(s.duration||0).toFixed(1)}s</div>`;
|
||||
});
|
||||
boxes[boxes.length-1].innerHTML=html;
|
||||
}
|
||||
```
|
||||
|
||||
**Ключевое правило:** `dtFinish == null` → этап СЕЙЧАС выполняется (⏳). `dtFinish != null` → завершён (✅/❌).
|
||||
|
||||
**Поллинг:** каждые 2 секунды через `GET /api/test/status/{opUid}` → `showStages(sd.stages)`.
|
||||
|
||||
**API-запрос к Nubes:** `GET /instanceOperations/{opUid}?fields=dtFinish,isSuccessful,errorLog,duration,stages`
|
||||
|
||||
**Структура stages из API** (документация: `/home/naeel/nubes/autotest/DOCS/api-operation-stages.md`):
|
||||
```json
|
||||
{ "stage": "1. Валидация", "isSuccessful": true, "dtFinish": "2026-...", "duration": 63.3 },
|
||||
{ "stage": "2. Доступ", "isSuccessful": null, "dtFinish": null, "duration": 14.1 },
|
||||
{ "stage": "3. Проверки", "isSuccessful": null, "dtFinish": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Что УЖЕ есть в провайдере
|
||||
|
||||
### Файл: `provider/internal/core/client.go`
|
||||
|
||||
#### a) Структуры для парсинга stages (строка ~913)
|
||||
```go
|
||||
type opStage struct {
|
||||
InstanceOperationStageUid string `json:"instanceOperationStageUid"`
|
||||
Stage string `json:"stage"`
|
||||
IsSuccessful bool `json:"isSuccessful"`
|
||||
DtFinish *string `json:"dtFinish"`
|
||||
Duration float64 `json:"duration"`
|
||||
StageMsg *string `json:"stageMsg"`
|
||||
}
|
||||
|
||||
type operationStatusResponse struct {
|
||||
InstanceOperation struct {
|
||||
DtFinish *string `json:"dtFinish"`
|
||||
IsSuccessful *bool `json:"isSuccessful"`
|
||||
ErrorLog *string `json:"errorLog"`
|
||||
IsInProgress bool `json:"isInProgress"`
|
||||
IsPending bool `json:"isPending"`
|
||||
Duration *float64 `json:"duration"`
|
||||
Stages []opStage `json:"stages"`
|
||||
} `json:"instanceOperation"`
|
||||
}
|
||||
```
|
||||
|
||||
#### b) Цикл поллинга `waitForOperationFinish()` (строка ~930)
|
||||
- Поллит каждые 5 секунд
|
||||
- Запрашивает `?fields=dtFinish,isSuccessful,errorLog,isInProgress,isPending,duration,stages`
|
||||
- Парсит ответ в `operationStatusResponse`
|
||||
|
||||
#### c) ВЫВОД ЭТАПОВ — уже есть, но с тремя проблемами (строка ~960-990)
|
||||
```go
|
||||
// Проблема 1: загейтино за log_level
|
||||
logLevel := c.LogLevel
|
||||
if v, ok := ctx.Value(ctxKeyLogLevel).(string); ok && v != "" {
|
||||
logLevel = v
|
||||
}
|
||||
showStages := logLevel == "info" || logLevel == "debug" // ← по умолчанию "none" = hidden!
|
||||
|
||||
// Проблема 2: показывает ТОЛЬКО завершённые, текущий ПРОПУСКАЕТ
|
||||
for _, stage := range status.InstanceOperation.Stages {
|
||||
if stage.DtFinish == nil || *stage.DtFinish == "" {
|
||||
continue // ← ТЕКУЩИЙ ЭТАП ИГНОРИРУЕТСЯ
|
||||
}
|
||||
fmt.Fprintf(tty, " [%s] %s — %.1f sec\n", status2, stage.Stage, stage.Duration)
|
||||
}
|
||||
|
||||
// Проблема 3: пишет в /dev/tty через ttyOut()
|
||||
tty := ttyOut()
|
||||
defer tty.Close()
|
||||
```
|
||||
|
||||
#### d) Конфигурация log_level в провайдере: `provider/internal/provider/provider.go:150`
|
||||
```go
|
||||
logLevel := "none" // ← ДЕФОЛТ! Этапы СКРЫТЫ всегда, пока пользователь не выставит log_level="info"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Что нужно изучить и выдать в плане
|
||||
|
||||
### Вопрос 1: `/dev/tty`
|
||||
- `ttyOut()` открывает `/dev/tty`. В каких окружениях это работает, а в каких — нет?
|
||||
- Стоит ли заменить на `tflog.Info()` / `tflog.Debug()` (стандартный terraform-логгинг)?
|
||||
- Или оставить `/dev/tty` как самый надёжный способ прямого вывода?
|
||||
- Как это сделано в autotest (там вывод через DOM, не применимо к CLI-провайдеру).
|
||||
|
||||
### Вопрос 2: текущий этап
|
||||
- Сейчас `DtFinish == nil → continue` — текущий этап не показывается.
|
||||
- Нужно: для `DtFinish == nil` выводить `[..] {stage} — {duration}s` (текущий).
|
||||
- При этом не плодить дубликаты — отслеживать, какой этап уже был показан.
|
||||
- Как правильно обновлять одну и ту же строку в терминале (carriage return? перепечатывать?)
|
||||
|
||||
### Вопрос 3: log_level по умолчанию
|
||||
- Сейчас `"none"` — этапы скрыты.
|
||||
- Нужно ли менять дефолт на `"info"`? Плюсы: пользователь сразу видит этапы. Минусы: лишний вывод в CI.
|
||||
- Альтернатива: оставить `"none"`, но сделать `"info"` более заметным в документации.
|
||||
|
||||
### Вопрос 4: формат вывода
|
||||
- Сейчас: `[OK] 1. Валидация — 63.3 sec`
|
||||
- Для текущего: `[..] 2. Основной процесс — 14.1 sec`
|
||||
- Для ещё не начатых: показывать или нет? В autotest показывают все (с `⏳`).
|
||||
- Этапы, которые ещё не начались (`DtFinish == nil` + `DtStart == nil`) — показывать с пометкой `[--]`?
|
||||
|
||||
### Вопрос 5: `Still creating...` от Terraform
|
||||
- Сообщения `Still creating...` генерятся самим фреймворком Terraform.
|
||||
- Можно ли их подавить/заменить? Или они останутся в любом случае?
|
||||
- Если нельзя подавить — этапы пойдут ПОВЕРХ или ВМЕСТЕ с этими сообщениями.
|
||||
|
||||
### Вопрос 6: `StageMsg` (debug)
|
||||
- В текущем коде есть `formatStageMsg()` для вывода деталей подэтапов при `log_level == "debug"`.
|
||||
- Это работает? Стоит сохранить?
|
||||
|
||||
---
|
||||
|
||||
## 5. Файлы, которые нужно изучить
|
||||
|
||||
| Файл | Что смотреть |
|
||||
|---|---|
|
||||
| `provider/internal/core/client.go` | `waitForOperationFinish()`, `ttyOut()`, `opStage`, `formatStageMsg()`, `ctxKeyLogLevel` |
|
||||
| `provider/internal/provider/provider.go` | конфигурация `log_level` (строка 85, 150) |
|
||||
| `provider/internal/resources_core/crud.go` | вызовы `CreateResourceWithTimeout`, `UpdateResourceWithTimeout`, `DeleteResource` |
|
||||
| `provider/internal/resources_gen/90_postgres_resource.go` | пример сгенерированного ресурса — вызов CRUD |
|
||||
| `/home/naeel/nubes/autotest/app-autotest/site/static/js/operations.js` | `showStages()` — эталон (строка 331) |
|
||||
| `/home/naeel/nubes/autotest/app-autotest/site/static/js/history.js` | `renderStages()` — эталон для истории (строка 87) |
|
||||
| `/home/naeel/nubes/autotest/app-autotest/site/operations/poll.py` | `poll_until_done()` — эталон поллинга |
|
||||
| `/home/naeel/nubes/autotest/DOCS/api-operation-stages.md` | структура stages из API |
|
||||
|
||||
---
|
||||
|
||||
## 6. Ожидаемый результат
|
||||
|
||||
**Не код, а ПЛАН.** В ответе должно быть:
|
||||
|
||||
1. Краткий анализ: что работает, что сломано, почему.
|
||||
2. Для каждой из трёх проблем (log_level, /dev/tty, текущий этап) — конкретное решение со ссылками на строки.
|
||||
3. Unified diff для каждого изменяемого файла (можно схематичный — какие блоки кода заменить на какие).
|
||||
4. Ответы на все 6 вопросов из раздела 4.
|
||||
5. Оценка рисков: что может пойти не так при каждом изменении.
|
||||
|
||||
---
|
||||
|
||||
## 7. Правила (обязательно)
|
||||
|
||||
- ⛔ Критерий завершения операции — `dtFinish`. ЭТО НЕ ТРОГАТЬ НИ ПРИ КАКИХ УСЛОВИЯХ.
|
||||
- ⛔ Логика поллинга (частота, таймауты) — не менять без согласования.
|
||||
- ⛔ Существующие сигнатуры функций — не менять без согласования.
|
||||
- ✅ Новая логика — новые функции/блоки, не ломать существующее.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Stages Output — Финальный план реализации
|
||||
|
||||
> Утверждён: 2026-08-09
|
||||
> Источник: Sonnet briefing + уточнения
|
||||
|
||||
## Принятые решения
|
||||
|
||||
| Решение | Почему |
|
||||
|---|---|
|
||||
| Вывод через `/dev/tty` с fallback на `os.Stderr` | tflog привязан к TF_LOG, не к нашему log_level |
|
||||
| Только `\n`, без `\r` | `\r` конфликтует с выводом Terraform (Still creating...) |
|
||||
| Текущий этап: `[..] stage\n` один раз | Без промежуточной duration (менялась бы и запутывала) |
|
||||
| Завершённый этап: `[OK ] stage — Xs\n` | C префиксом для выравнивания |
|
||||
| Дефолт log_level: `"none"` | Не breaking change, opt-in через env var |
|
||||
| Env var `NUBES_LOG_LEVEL` | Симметрично NUBES_INSECURE |
|
||||
|
||||
## Что правим
|
||||
|
||||
### client.go — 3 правки
|
||||
|
||||
1. **Вынести tty из цикла** (resource leak fix)
|
||||
- `tty := ttyOut()` + `defer tty.Close()` → перед `for {`
|
||||
- Убрать `tty := ttyOut()` и `defer tty.Close()` из тела цикла
|
||||
|
||||
2. **Добавить `lastPendingUID`** рядом с `printedStages`
|
||||
|
||||
3. **Заменить блок вывода этапов:**
|
||||
- Завершённый (DtFinish != nil) → `[OK ] stage — Xs\n` или `[FAIL] stage — Xs\n`
|
||||
- Текущий (DtFinish == nil) → `[..] stage\n` один раз при смене UID
|
||||
|
||||
### provider.go — 1 правка
|
||||
|
||||
4. **Добавить поддержку NUBES_LOG_LEVEL** env var (по аналогии с NUBES_INSECURE)
|
||||
- Приоритет: config.LogLevel > NUBES_LOG_LEVEL > "none"
|
||||
|
||||
## Формат вывода (пример)
|
||||
|
||||
```
|
||||
[..] 1. Валидация
|
||||
[OK ] 1. Валидация — 63.3 sec
|
||||
[..] 2. Основной процесс
|
||||
[OK ] 2. Основной процесс — 21.3 sec
|
||||
[..] 3. Проверки
|
||||
[OK ] 3. Проверки — 107.7 sec
|
||||
[..] 4. Настройка
|
||||
[OK ] 4. Настройка — 4.6 sec
|
||||
[DONE] 201.5 sec
|
||||
```
|
||||
|
||||
## НЕ ТРОГАТЬ
|
||||
|
||||
- Критерий завершения (dtFinish)
|
||||
- Интервал поллинга (5 сек)
|
||||
- Таймауты
|
||||
- Сигнатуры функций
|
||||
@@ -0,0 +1,51 @@
|
||||
# Баг: DEV postgres Script26 не знает resourceRealm
|
||||
|
||||
Дата: 2026-08-09
|
||||
|
||||
## Симптом
|
||||
|
||||
`terraform apply` на DEV стенде падает с ошибкой:
|
||||
|
||||
```
|
||||
nubes_postgres.npg: операция ... завершилась с ошибкой:
|
||||
No such property: resourceRealm for class: Script26
|
||||
```
|
||||
|
||||
## Причина
|
||||
|
||||
DEV API (`lk-api-gateway-dev.ngcloud.ru`) возвращает `resourceRealm` в метаданных
|
||||
сервиса postgres (ID 90, операция create, параметр startupConfiguration).
|
||||
Но бэкенд-скрипт `Script26`, который фактически выполняет создание Postgres на DEV,
|
||||
**не имеет** свойства `resourceRealm`.
|
||||
|
||||
TEST стенд работает нормально — его скрипт `resourceRealm` принимает.
|
||||
|
||||
## Доказательства
|
||||
|
||||
1. Оба API (DEV и TEST) возвращают одинаковые метаданные — `resourceRealm` присутствует:
|
||||
```
|
||||
generated/dev/resources_yaml/90_postgres.yaml — 2 occurrences
|
||||
generated/test/resources_yaml/90_postgres.yaml — 2 occurrences
|
||||
```
|
||||
|
||||
2. Провайдер собран **из DEV API** (не перепутан с TEST):
|
||||
- YAML сгенерированы: `./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev`
|
||||
- Go сгенерирован: `./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev`
|
||||
- Версия: `3.0.2`, Namespace: `nubes-dev`
|
||||
|
||||
3. Terraform-конфиг (DEV_STAND/PGwNewRegistry):
|
||||
```hcl
|
||||
provider "nubes" {
|
||||
api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
|
||||
}
|
||||
resource "nubes_postgres" "npg" {
|
||||
startup_configuration = {
|
||||
resource_realm = "k8s-3-sandbox-nubes-ru"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Что нужно сделать
|
||||
|
||||
Добавить свойство `resourceRealm` в скрипт `Script26` (DEV postgres create).
|
||||
Привести DEV-скрипт в соответствие с метаданными API (как на TEST).
|
||||
@@ -0,0 +1,35 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
|
||||
version = "5.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "api_token" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "Nubes API token"
|
||||
}
|
||||
variable "s3_uid" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "Nubes S3 UID"
|
||||
}
|
||||
variable "realm" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "resource_realm parameter for nubes_postgres resource"
|
||||
}
|
||||
variable "s3_user_uid" {
|
||||
type = string
|
||||
description = "S3 user UUID"
|
||||
}
|
||||
|
||||
provider "nubes" {
|
||||
api_token = var.api_token
|
||||
api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||||
# log_level = "debug" # none | info | debug, default = "none"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
resource "nubes_postgres" "npg" {
|
||||
resource_name = "pgdev02"
|
||||
|
||||
startup_configuration = {
|
||||
resource_realm = var.realm
|
||||
}
|
||||
|
||||
cluster_configuration = {
|
||||
cpu = 500
|
||||
memory = 512
|
||||
replicas = 1
|
||||
disk = 10
|
||||
}
|
||||
|
||||
access_configuration = {
|
||||
master_ip_space = "no-needed"
|
||||
master_access_list = jsonencode(["10.0.0.0/8"])
|
||||
slave_ip_space = "no-needed"
|
||||
slave_access_list = jsonencode([])
|
||||
}
|
||||
|
||||
postgres_configuration = {
|
||||
version = "17"
|
||||
ssl_required = true
|
||||
pooler_master = false
|
||||
pooler_slave = false
|
||||
}
|
||||
|
||||
postgres_conf = jsonencode([{
|
||||
param_name = "log_connections"
|
||||
param_value = ""
|
||||
}])
|
||||
|
||||
backup_configuration = {
|
||||
s3_uid = var.s3_uid
|
||||
retain = 14
|
||||
schedule = "0 0 * * *"
|
||||
}
|
||||
|
||||
autoscale_configuration = {
|
||||
enabled = false
|
||||
schedule = 0
|
||||
percent = 10
|
||||
quota = 100
|
||||
}
|
||||
|
||||
mtls_configuration = {
|
||||
type = "off"
|
||||
duration_ca = 175200
|
||||
duration_server = 87600
|
||||
}
|
||||
|
||||
operation_timeout = "11m"
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# =============================================================================
|
||||
# PostgreSQL — пользователи (6 шт.)
|
||||
# =============================================================================
|
||||
resource "nubes_postgres_user" "pg_user_0" {
|
||||
postgres_id = nubes_postgres.npg.id
|
||||
username = "user0"
|
||||
role = "ddl_user"
|
||||
mtls_access = false
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
# resource "nubes_postgres_user" "pg_user_1" {
|
||||
# postgres_id = nubes_postgres.npg.id
|
||||
# username = "user1"
|
||||
# role = "ddl_user"
|
||||
# adopt_existing_on_create = true
|
||||
# }
|
||||
|
||||
# # =============================================================================
|
||||
# # PostgreSQL — базы данных (3 шт.)
|
||||
# # =============================================================================
|
||||
# resource "nubes_postgres_database" "pg_db_1" {
|
||||
# postgres_id = nubes_postgres.npg.id
|
||||
# db_name = "dbapp1"
|
||||
# db_owner = nubes_postgres_user.pg_user_1.username
|
||||
# adopt_existing_on_create = true
|
||||
# }
|
||||
|
||||
resource "nubes_postgres_database" "pg_db_2" {
|
||||
postgres_id = nubes_postgres.npg.id
|
||||
db_name = "dbapp2"
|
||||
db_owner = nubes_postgres_user.pg_user_0.username
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
# resource "nubes_postgres_database" "pg_db_3" {
|
||||
# postgres_id = nubes_postgres.npg.id
|
||||
# db_name = "dbapp3"
|
||||
# db_owner = nubes_postgres_user.pg_user_1.username
|
||||
# adopt_existing_on_create = true
|
||||
# }
|
||||
|
||||
# S3 bucket — замени "buck0" на своё имя везде ниже
|
||||
resource "nubes_s3bucket" "bukka0" { # ← замени buck0 на своё имя ресурса
|
||||
resource_name = "btst" # ← замени buck0 на своё имя ресурса
|
||||
#s3_user_uid = "naeel-s3"
|
||||
s3_user_uid = var.s3_user_uid
|
||||
bucket_name = "bucktst000" # ← замени buck0 на своё имя бакета
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
@@ -3,7 +3,7 @@ NUBES_API_ENDPOINT="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||||
TOKEN_FILE="secrets/test.token"
|
||||
|
||||
# Version
|
||||
VERSION="5.0.1"
|
||||
VERSION="5.0.2"
|
||||
|
||||
NAMESPACE="nubes-test"
|
||||
PROVIDER_NAME="nubes"
|
||||
|
||||
@@ -934,6 +934,10 @@ func (c *UniversalClient) waitForOperationFinish(ctx context.Context, opUid stri
|
||||
defer ticker.Stop()
|
||||
|
||||
printedStages := make(map[string]bool)
|
||||
var lastPendingUID string
|
||||
|
||||
tty := ttyOut()
|
||||
defer tty.Close()
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -955,37 +959,28 @@ func (c *UniversalClient) waitForOperationFinish(ctx context.Context, opUid stri
|
||||
return fmt.Errorf("не удалось разобрать статус операции %s: %w", opUid, err)
|
||||
}
|
||||
|
||||
// Определяем уровень логирования: ctx перекрывает c.LogLevel
|
||||
logLevel := c.LogLevel
|
||||
if v, ok := ctx.Value(ctxKeyLogLevel).(string); ok && v != "" {
|
||||
logLevel = v
|
||||
}
|
||||
showStages := logLevel == "info" || logLevel == "debug"
|
||||
showDetails := logLevel == "debug"
|
||||
|
||||
// Печатаем завершённые этапы по мере появления
|
||||
tty := ttyOut()
|
||||
defer tty.Close()
|
||||
// Печатаем этапы по мере выполнения — ВСЕГДА, без гейтинга.
|
||||
// Завершённые (DtFinish != nil): [OK ]/[FAIL] с duration.
|
||||
// Текущий (DtFinish == nil): [..] один раз при появлении.
|
||||
for _, stage := range status.InstanceOperation.Stages {
|
||||
if printedStages[stage.InstanceOperationStageUid] {
|
||||
continue
|
||||
}
|
||||
if stage.DtFinish == nil || *stage.DtFinish == "" {
|
||||
continue
|
||||
}
|
||||
if stage.DtFinish != nil && *stage.DtFinish != "" {
|
||||
// Завершённый этап
|
||||
printedStages[stage.InstanceOperationStageUid] = true
|
||||
if !showStages {
|
||||
continue
|
||||
if stage.InstanceOperationStageUid == lastPendingUID {
|
||||
lastPendingUID = ""
|
||||
}
|
||||
status2 := "OK "
|
||||
if !stage.IsSuccessful {
|
||||
status2 = "FAIL"
|
||||
}
|
||||
fmt.Fprintf(tty, " [%s] %s — %.1f sec\n", status2, stage.Stage, stage.Duration)
|
||||
if showDetails && stage.StageMsg != nil {
|
||||
for _, line := range formatStageMsg(*stage.StageMsg) {
|
||||
fmt.Fprintf(tty, "%s\n", line)
|
||||
}
|
||||
} else if stage.InstanceOperationStageUid != lastPendingUID {
|
||||
// Текущий этап — показываем один раз
|
||||
lastPendingUID = stage.InstanceOperationStageUid
|
||||
fmt.Fprintf(tty, " [..] %s\n", stage.Stage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -997,7 +992,7 @@ func (c *UniversalClient) waitForOperationFinish(ctx context.Context, opUid stri
|
||||
}
|
||||
return fmt.Errorf("операция %s завершилась с ошибкой", opUid)
|
||||
}
|
||||
if showStages && status.InstanceOperation.Duration != nil {
|
||||
if status.InstanceOperation.Duration != nil {
|
||||
fmt.Fprintf(tty, " [DONE] %.1f sec\n", *status.InstanceOperation.Duration)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -145,9 +145,9 @@ func (p *NubesProvider) Configure(ctx context.Context, req provider.ConfigureReq
|
||||
},
|
||||
}
|
||||
|
||||
// Определяем уровень логирования операций: none (тихий) / info / debug.
|
||||
// none — дефолт, не засорять вывод terraform apply лишними строками.
|
||||
logLevel := "none"
|
||||
// log_level: оставлен для обратной совместимости, но этапы выводятся ВСЕГДА.
|
||||
// Значение используется только для StageMsg (debug).
|
||||
logLevel := "info"
|
||||
if !config.LogLevel.IsNull() && !config.LogLevel.IsUnknown() {
|
||||
if v := strings.TrimSpace(config.LogLevel.ValueString()); v != "" {
|
||||
logLevel = v
|
||||
|
||||
Reference in New Issue
Block a user