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:
“Naeel”
2026-08-09 18:18:35 +04:00
parent b6b25f7548
commit fda819cd81
9 changed files with 470 additions and 31 deletions
+22 -27
View File
@@ -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
}
printedStages[stage.InstanceOperationStageUid] = true
if !showStages {
continue
}
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)
if stage.DtFinish != nil && *stage.DtFinish != "" {
// Завершённый этап
printedStages[stage.InstanceOperationStageUid] = true
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)
} 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
+3 -3
View File
@@ -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