fix(modifiers): ImportState заполняет Required-атрибуты
Было: ImportState ставил только id + organization/nsxt_uid, оставляя Required-атрибуты (vip_configure / ip_space_name) в null — Terraform не мог свести импорт с конфигом. - nsxt_snat: importIpSpaceName(live) — live-значение, иначе канон no-needed. - org_ip_allocation: importVipConfigure(live) — канонический JSON из live, иначе []. - Чистые хелперы + тесты TestImportVipConfigure/TestImportIpSpaceName. Проверено: go build ./... OK; go test ./internal/resources_core/... -short -> PASS. Terraform import проверяется владельцем.
This commit is contained in:
@@ -225,6 +225,24 @@ func (r *NsxtSnatResource) ImportState(ctx context.Context, req resource.ImportS
|
|||||||
uid := strings.TrimSpace(req.ID)
|
uid := strings.TrimSpace(req.ID)
|
||||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), uid)...)
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), uid)...)
|
||||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("nsxt_uid"), uid)...)
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("nsxt_uid"), uid)...)
|
||||||
|
|
||||||
|
// Required-атрибут ip_space_name нельзя оставить null после импорта: пользователь
|
||||||
|
// обязан указать его в конфиге. Заполняем live-значением; если SNAT не выставлен —
|
||||||
|
// каноном «выключено» (no-needed).
|
||||||
|
var live map[string]string
|
||||||
|
if r.client != nil {
|
||||||
|
live, _ = r.client.GetInstanceStateParams(ctx, uid)
|
||||||
|
}
|
||||||
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("ip_space_name"), importIpSpaceName(live))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// importIpSpaceName выбирает значение ip_space_name для состояния после импорта:
|
||||||
|
// live-значение, если есть; иначе канон «SNAT выключен» (no-needed).
|
||||||
|
func importIpSpaceName(live map[string]string) string {
|
||||||
|
if v := strings.TrimSpace(live["ipSpaceName"]); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return noNeededIpSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
// setSnat отправляет modify только с ipSpaceName. Остальные параметры операции
|
// setSnat отправляет modify только с ipSpaceName. Остальные параметры операции
|
||||||
|
|||||||
@@ -299,6 +299,25 @@ func (r *OrgIpAllocationResource) ImportState(ctx context.Context, req resource.
|
|||||||
uid := strings.TrimSpace(req.ID)
|
uid := strings.TrimSpace(req.ID)
|
||||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), uid)...)
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), uid)...)
|
||||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization"), uid)...)
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization"), uid)...)
|
||||||
|
|
||||||
|
// Required-атрибут vip_configure нельзя оставить null после импорта.
|
||||||
|
// Заполняем live-значением; если аллокации нет — пустым массивом.
|
||||||
|
var live map[string]string
|
||||||
|
if r.client != nil {
|
||||||
|
live, _ = r.client.GetInstanceStateParams(ctx, uid)
|
||||||
|
}
|
||||||
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vip_configure"), importVipConfigure(live))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// importVipConfigure выбирает значение vip_configure для состояния после импорта:
|
||||||
|
// канонический JSON из live (если есть непустые элементы), иначе пустой массив.
|
||||||
|
func importVipConfigure(live map[string]string) string {
|
||||||
|
if raw, ok := live["vIPConfigure"]; ok {
|
||||||
|
if items, err := parseVipConfigure(raw); err == nil && len(items) > 0 {
|
||||||
|
return formatVipConfigure(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "[]"
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveOrganizationUID принимает имя организации из ЛК или её UUID и возвращает UUID.
|
// resolveOrganizationUID принимает имя организации из ЛК или её UUID и возвращает UUID.
|
||||||
|
|||||||
@@ -51,6 +51,48 @@ func TestFormatVipConfigure_Canonical(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importVipConfigure/importIpSpaceName — выбор значения Required-атрибутов при импорте
|
||||||
|
// (B7: Required не должен оставаться null после import).
|
||||||
|
func TestImportVipConfigure(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
live map[string]string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"live-аллокация", map[string]string{"vIPConfigure": `[{"name":"internet-ipv4-v1","count":"3"}]`}, `[{"name":"internet-ipv4-v1","count":"3"}]`},
|
||||||
|
{"нет ключа", map[string]string{}, "[]"},
|
||||||
|
{"nil", nil, "[]"},
|
||||||
|
{"свежая орга [{}]", map[string]string{"vIPConfigure": `[{}]`}, "[]"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := importVipConfigure(tc.live); got != tc.want {
|
||||||
|
t.Fatalf("получено %q, ожидалось %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportIpSpaceName(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
live map[string]string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"live-значение", map[string]string{"ipSpaceName": "internet-ipv4-v1"}, "internet-ipv4-v1"},
|
||||||
|
{"нет ключа", map[string]string{}, "no-needed"},
|
||||||
|
{"nil", nil, "no-needed"},
|
||||||
|
{"только пробелы", map[string]string{"ipSpaceName": " "}, "no-needed"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := importIpSpaceName(tc.live); got != tc.want {
|
||||||
|
t.Fatalf("получено %q, ожидалось %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Сравнение смысловое: `jsonencode` сортирует ключи по алфавиту (count раньше name),
|
// Сравнение смысловое: `jsonencode` сортирует ключи по алфавиту (count раньше name),
|
||||||
// но для нас это то же самое значение — переписывать state нельзя (иначе ложный дрейф).
|
// но для нас это то же самое значение — переписывать state нельзя (иначе ложный дрейф).
|
||||||
func TestVipAllocationsEqual_OrderInsensitive(t *testing.T) {
|
func TestVipAllocationsEqual_OrderInsensitive(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user