From 8d405ba6952801ca299e32f0b9ce1fb62d379d6e Mon Sep 17 00:00:00 2001 From: Repinoid Date: Mon, 21 Sep 2026 20:26:41 +0300 Subject: [PATCH] =?UTF-8?q?fix(core):=20=D0=BF=D0=BE=D0=B4=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D0=B7=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=84=D0=BB=D0=B8=D0=BA=D1=82=D0=B5=20=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=20=D1=83=D1=87=D0=B8=D1=82=D1=8B=D0=B2=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82=D0=B2=D0=B8?= =?UTF-8?q?=D0=B5=20suspend/resume=20(no=20adopt)=20+=20supportsSuspend=20?= =?UTF-8?q?=D0=B2=20=D1=81=D0=B8=D0=B3=D0=BD=D0=B0=D1=82=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resource_diagnostics_required.go | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/provider/internal/resources_core/resource_diagnostics_required.go b/provider/internal/resources_core/resource_diagnostics_required.go index d88e0a1..8a2bb79 100644 --- a/provider/internal/resources_core/resource_diagnostics_required.go +++ b/provider/internal/resources_core/resource_diagnostics_required.go @@ -17,15 +17,25 @@ import ( ) // PlanExistingResourceDiagnosticsWithParams returns diagnostics when resource exists and compares required params. +// Устаревшая обёртка: считает, что сервис поддерживает suspend/resume (старое поведение). func PlanExistingResourceDiagnosticsWithParams(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredParams map[int]string) diag.Diagnostics { - return PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredParams, "", nil) + return PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredParams, "", nil, true) } +// PlanExistingResourceDiagnosticsWithParamsAndDomain — см. PlanExistingResourceDiagnosticsWithParams. func PlanExistingResourceDiagnosticsWithParamsAndDomain(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredParams map[int]string, desiredDomain string) diag.Diagnostics { - return PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredParams, desiredDomain, nil) + return PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredParams, desiredDomain, nil, true) } -func PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredParams map[int]string, desiredDomain string, allowedDomainServiceIDs []int) diag.Diagnostics { +// PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices — create-time проверка +// «инстанс с таким именем уже существует». +// +// supportsSuspend — поддерживает ли сервис операции suspend/resume (из YAML-спеки). +// Если НЕТ (например vc_nsxt/Edge), то: +// - советы про resume/adopt для состояния Suspend бессмысленны — такое состояние +// для такого сервиса вообще недостижимо, а усыновление требует resume; +// - корректные действия пользователя: удалить инстанс в ЛК или взять другое имя. +func PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredParams map[int]string, desiredDomain string, allowedDomainServiceIDs []int, supportsSuspend bool) diag.Diagnostics { f, _ := os.OpenFile("/tmp/nubes_find_debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if f != nil { fmt.Fprintf(f, "[FIND-DEBUG] PlanExistingResourceDiagnostics entered: serviceId=%d name=%q adopt=%v client=%v\n", serviceID, resourceName, adoptExistingOnCreate, client != nil) @@ -71,7 +81,7 @@ func PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx context.C } diags.AddError( "РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)", - "Инстанс с таким resource_name уже работает.\nАвто-усыновление отключено (adopt_existing_on_create=false).\nВыберите другое имя, если нужен новый ресурс.\nИли выполните импорт существующего ресурса через усыновление: установите adopt_existing_on_create=true и повторите apply."+formatInstanceDetails(existing, serviceID, resourceName), + "Инстанс с таким resource_name уже работает.\nАвто-усыновление отключено (adopt_existing_on_create=false).\nВыберите другое имя, если нужен новый ресурс."+runningConflictHint(supportsSuspend)+formatInstanceDetails(existing, serviceID, resourceName), ) return diags } @@ -86,7 +96,7 @@ func PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx context.C if !adoptExistingOnCreate { diags.AddError( "РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)", - "Инстанс найден в состоянии Suspend, но adopt_existing_on_create=false.\nДля восстановления/импорта этим ресурсом включите adopt_existing_on_create=true и повторите apply."+formatInstanceDetails(existing, serviceID, resourceName), + "Инстанс найден в состоянии Suspend, но adopt_existing_on_create=false."+suspendConflictHint(supportsSuspend)+formatInstanceDetails(existing, serviceID, resourceName), ) return diags } @@ -192,11 +202,15 @@ func appendRemotePlanInputDiagnostics(ctx context.Context, client *core.Universa } } +// CreateExistingResourceDiagnosticsWithDomain — устаревшая обёртка (suspend считается +// поддерживаемым). См. CreateExistingResourceDiagnosticsWithDomainAndServices. func CreateExistingResourceDiagnosticsWithDomain(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredDomain string) diag.Diagnostics { - return CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredDomain, nil) + return CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, client, serviceID, resourceName, adoptExistingOnCreate, desiredDomain, nil, true) } -func CreateExistingResourceDiagnosticsWithDomainAndServices(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredDomain string, allowedDomainServiceIDs []int) diag.Diagnostics { +// CreateExistingResourceDiagnosticsWithDomainAndServices — см. +// PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices (supportsSuspend). +func CreateExistingResourceDiagnosticsWithDomainAndServices(ctx context.Context, client *core.UniversalClient, serviceID int, resourceName string, adoptExistingOnCreate bool, desiredDomain string, allowedDomainServiceIDs []int, supportsSuspend bool) diag.Diagnostics { var diags diag.Diagnostics if client == nil { return diags @@ -230,7 +244,7 @@ func CreateExistingResourceDiagnosticsWithDomainAndServices(ctx context.Context, } diags.AddError( "РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)", - "Инстанс с таким resource_name уже работает.\nАвто-усыновление отключено (adopt_existing_on_create=false).\nВыберите другое имя, если нужен новый ресурс.\nИли выполните импорт существующего ресурса через усыновление: установите adopt_existing_on_create=true и повторите apply."+formatInstanceDetails(existing, serviceID, resourceName), + "Инстанс с таким resource_name уже работает.\nАвто-усыновление отключено (adopt_existing_on_create=false).\nВыберите другое имя, если нужен новый ресурс."+runningConflictHint(supportsSuspend)+formatInstanceDetails(existing, serviceID, resourceName), ) return diags } @@ -240,7 +254,7 @@ func CreateExistingResourceDiagnosticsWithDomainAndServices(ctx context.Context, } diags.AddError( "РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)", - "Инстанс найден в состоянии Suspend, но adopt_existing_on_create=false.\nДля восстановления/импорта этим ресурсом включите adopt_existing_on_create=true и повторите apply."+formatInstanceDetails(existing, serviceID, resourceName), + "Инстанс найден в состоянии Suspend, но adopt_existing_on_create=false."+suspendConflictHint(supportsSuspend)+formatInstanceDetails(existing, serviceID, resourceName), ) return diags } @@ -252,6 +266,30 @@ func CreateExistingResourceDiagnosticsWithDomainAndServices(ctx context.Context, return diags } +// runningConflictHint — что предлагать, если инстанс с таким именем уже работает. +// +// Для сервисов БЕЗ suspend/resume усыновление работающего инстанса технически возможно +// (приостановка не требуется — adopt просто берёт существующий uid), но пользователю +// часто нужен именно destroy, поэтому называем оба пути явно. +func runningConflictHint(supportsSuspend bool) string { + if !supportsSuspend { + return "\nЛибо усыновите существующий инстанс: adopt_existing_on_create=true (он уже работает, suspend не требуется).\nЛибо удалите инстанс в Личном кабинете Nubes и повторите apply." + } + return "\nИли выполните импорт существующего ресурса через усыновление: установите adopt_existing_on_create=true и повторите apply." +} + +// suspendConflictHint — что предлагать, если инстанс найден в состоянии Suspend. +// +// Для сервисов БЕЗ suspend/resume состояние Suspend недостижимо, а adopt требует +// resume — значит совет «включите adopt_existing_on_create=true» заведомо невыполним. +// Корректный выход один: удалить инстанс и/или взять другое имя. +func suspendConflictHint(supportsSuspend bool) string { + if !supportsSuspend { + return "\nСервис НЕ поддерживает suspend/resume, поэтому усыновление (adopt) для него невозможно.\nУдалите инстанс в Личном кабинете Nubes и повторите apply, либо выберите другое resource_name." + } + return "\nДля восстановления/импорта этим ресурсом включите adopt_existing_on_create=true и повторите apply." +} + func appendLocalPlanInputDiagnostics(serviceID int, desiredParams map[int]string, desiredDomain string, diags *diag.Diagnostics) { if diags == nil { return