diff --git a/provider/internal/resources_core/nsxt_snat_resource.go b/provider/internal/resources_core/nsxt_snat_resource.go index 1ffd52b..d545348 100644 --- a/provider/internal/resources_core/nsxt_snat_resource.go +++ b/provider/internal/resources_core/nsxt_snat_resource.go @@ -146,11 +146,10 @@ func (r *NsxtSnatResource) Read(ctx context.Context, req resource.ReadRequest, r return } - // Ключа ipSpaceName нет, пока SNAT ни разу не включали (HAR fresh-create), - // поэтому отсутствие ключа = null. Значение "no-needed" (SNAT выключен) — реальное. - if raw, ok := live["ipSpaceName"]; !ok || strings.TrimSpace(raw) == "" { - state.IpSpaceName = types.StringNull() - } else { + // ВАЖНО: в Required-атрибут нельзя писать null — после apply state обязан совпасть с планом, + // иначе Terraform вернёт "Provider produced inconsistent result after apply". Если ключа ещё нет + // (SNAT ни разу не включали, HAR fresh-create) — оставляем текущее значение state. + if raw, ok := live["ipSpaceName"]; ok && strings.TrimSpace(raw) != "" { state.IpSpaceName = types.StringValue(strings.TrimSpace(raw)) } @@ -180,10 +179,9 @@ func (r *NsxtSnatResource) Delete(ctx context.Context, req resource.DeleteReques remove, err := ShouldRemoveFromState(ctx, r.client, nsxtUID) if err != nil { - resp.Diagnostics.AddWarning( - "SNAT не выключался", - fmt.Sprintf("не удалось проверить существование шлюза %s: %s", nsxtUID, err), - ) + // Реальная ошибка API (не «шлюза нет») — нельзя молча терять SNAT: ресурс уйдёт из state, + // а SNAT останется включённым. + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return } if remove { @@ -242,9 +240,11 @@ func (r *NsxtSnatResource) setSnat(ctx context.Context, nsxtUID types.String, ip return fmt.Errorf("клиент не инициализирован") } + // Пустую строку молча подменять нельзя (скрытое поведение + риск вечного diff). + // Выключение SNAT — явное каноническое значение "no-needed". value := strings.TrimSpace(ipSpaceName.ValueString()) if value == "" { - value = noNeededIpSpace + return fmt.Errorf("ip_space_name не может быть пустым: укажите имя ipSpace или %q для выключения SNAT", noNeededIpSpace) } unlock := r.client.LockInstance(uid)