From 5bd197f031444ba9a597d695996b03c9ae2557de Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 24 Sep 2026 15:28:13 +0300 Subject: [PATCH] =?UTF-8?q?feat(provider):=20=D1=80=D0=B5=D1=81=D1=83?= =?UTF-8?q?=D1=80=D1=81=20=D0=B0=D0=BB=D0=BB=D0=BE=D0=BA=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B8=D0=BC=D1=8F=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20(=D1=80=D0=B5=D0=B7=D0=BE?= =?UTF-8?q?=D0=BB=D0=B2=20=D0=B2=20UUID=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20ResolveRefSvcParamValue,=20=D0=BA=D0=B0=D0=BA=20=D0=B2=20nub?= =?UTF-8?q?es=5Fvc=5Fvdc);=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BA=D0=B8=20=D0=B1=D0=B5=D0=B7=20org=5F?= =?UTF-8?q?uid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEV_STAND/FullPipe/modifiers.tf | 4 +- DEV_STAND/FullPipe/variables.tf | 6 -- docs/curated/modifiers/org_ip_and_snat.md | 6 +- docs/curated/pipeline/vdc_edge_ip_snat.md | 8 +- .../org_ip_allocation_resource.go | 80 +++++++++++++++---- 5 files changed, 71 insertions(+), 33 deletions(-) diff --git a/DEV_STAND/FullPipe/modifiers.tf b/DEV_STAND/FullPipe/modifiers.tf index a12b137..1a319c2 100644 --- a/DEV_STAND/FullPipe/modifiers.tf +++ b/DEV_STAND/FullPipe/modifiers.tf @@ -16,7 +16,7 @@ # 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком) resource "nubes_vc_org_ip_allocation" "org_ip" { - org_uid = var.org_uid + organization = var.organization vip_configure = jsonencode([ { @@ -45,7 +45,7 @@ resource "nubes_vc_nsxt_snat" "snat" { output "allocated_org_ip" { description = "Выделено внешних IP на организации" value = { - org_uid = var.org_uid + organization = var.organization ip_space_name = var.ip_space_name ip_count = var.ip_count } diff --git a/DEV_STAND/FullPipe/variables.tf b/DEV_STAND/FullPipe/variables.tf index 4f0ee97..7e934d3 100644 --- a/DEV_STAND/FullPipe/variables.tf +++ b/DEV_STAND/FullPipe/variables.tf @@ -16,12 +16,6 @@ variable "organization" { description = "Имя или UUID организации (vc_org)" } -# UUID той же организации — нужен ресурсам-модификаторам (они адресуются строго по uid) -variable "org_uid" { - type = string - description = "UUID организации (vc_org) для nubes_vc_org_ip_allocation" -} - # --- Модификаторы (IP на орге + SNAT на эдже) --- variable "ip_space_name" { diff --git a/docs/curated/modifiers/org_ip_and_snat.md b/docs/curated/modifiers/org_ip_and_snat.md index 7be9d82..a0c6318 100644 --- a/docs/curated/modifiers/org_ip_and_snat.md +++ b/docs/curated/modifiers/org_ip_and_snat.md @@ -12,7 +12,7 @@ | Атрибут | Тип | Описание | |---|---|---| -| `org_uid` | string, обязательный | UUID услуги «Организация в Cloud Director» | +| `organization` | string, обязательный | Организация: имя из ЛК или её UUID | | `vip_configure` | string (JSON), обязательный | Массив аллокаций: `[{"name":"internet-ipv4-v1","count":"3"}]`. `count` — строка | | `keep_on_destroy` | bool, по умолчанию `false` | Не снимать квоту при `destroy` | @@ -24,7 +24,7 @@ ```hcl resource "nubes_vc_org_ip_allocation" "this" { - org_uid = var.org_uid + organization = var.organization vip_configure = jsonencode([ { name = "internet-ipv4-v1", count = "3" } @@ -70,6 +70,6 @@ resource "nubes_vc_nsxt_snat" "this" { Оба ресурса импортируются по UUID родительской услуги: ```bash -terraform import nubes_vc_org_ip_allocation.this +terraform import nubes_vc_org_ip_allocation.this organ terraform import nubes_vc_nsxt_snat.this ``` diff --git a/docs/curated/pipeline/vdc_edge_ip_snat.md b/docs/curated/pipeline/vdc_edge_ip_snat.md index 8cb4650..024bb3b 100644 --- a/docs/curated/pipeline/vdc_edge_ip_snat.md +++ b/docs/curated/pipeline/vdc_edge_ip_snat.md @@ -17,8 +17,7 @@ cd tf_examples/fullpipe_chain | Что | Где взять | Пример | |---|---|---| | Токен API | ЛК → Профиль → Токены → «Технический» | `eyJhbGciOi...` | -| UUID организации | ЛК → услуга «Организация в Cloud Director» → UUID в карточке услуги | `57eeacd1-dc7f-4a52-b903-7e5f7d3c1164` | -| Имя организации | там же — название услуги | `organ` | +| Имя организации | ЛК → услуга «Организация в Cloud Director» → название услуги | `organ` | | Сетевой провайдер | ЛК → vDC → «Сетевой провайдер» | `snb1` | | Provider VDC | ЛК → vDC → «Provider VDC» | `Intel Broadwell 2.4` | | Дисковая политика | ЛК → vDC → доступные дисковые политики | `SATA` | @@ -31,9 +30,8 @@ cp terraform.tfvars.example terraform.tfvars ``` ```hcl -api_token = "eyJhbGciOi..." # токен из шага 2 -organization = "organ" # имя организации -org_uid = "57eeacd1-dc7f-4a52-b903-7e5f7d3c1164" # UUID организации +api_token = "eyJhbGciOi..." # токен из шага 2 +organization = "organ" # имя организации ip_space_name = "internet-ipv4-v1" ip_count = "3" ``` diff --git a/provider/internal/resources_core/org_ip_allocation_resource.go b/provider/internal/resources_core/org_ip_allocation_resource.go index f74a819..da5a11a 100644 --- a/provider/internal/resources_core/org_ip_allocation_resource.go +++ b/provider/internal/resources_core/org_ip_allocation_resource.go @@ -36,7 +36,7 @@ type OrgIpAllocationResource struct { type OrgIpAllocationModel struct { ID types.String `tfsdk:"id"` - OrgUID types.String `tfsdk:"org_uid"` + Organization types.String `tfsdk:"organization"` VIPConfigure types.String `tfsdk:"vip_configure"` KeepOnDestroy types.Bool `tfsdk:"keep_on_destroy"` } @@ -59,7 +59,7 @@ func (r *OrgIpAllocationResource) Metadata(ctx context.Context, req resource.Met func (r *OrgIpAllocationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "Аллокация внешних IP (vIPConfigure) на существующей организации Cloud Director. " + - "Организация создаётся вручную в ЛК, ресурс адресует её по `org_uid`. " + + "Организация создаётся вручную в ЛК, в конфиге указывается её имя или UUID. " + "Операция имеет replace-семантику: массив перезаписывается целиком.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ @@ -68,9 +68,10 @@ func (r *OrgIpAllocationResource) Schema(ctx context.Context, req resource.Schem stringplanmodifier.UseStateForUnknown(), }, }, - "org_uid": schema.StringAttribute{ - Required: true, - MarkdownDescription: "UUID существующей услуги «Организация в Cloud Director».", + "organization": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Организация, на которой выделяются внешние IP: имя из ЛК (например `organ`) " + + "или её UUID.", PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, @@ -100,12 +101,18 @@ func (r *OrgIpAllocationResource) Create(ctx context.Context, req resource.Creat return } - if err := r.applyAllocation(ctx, plan.OrgUID, plan.VIPConfigure); err != nil { + orgUID, err := r.resolveOrganizationUID(ctx, plan.Organization) + if err != nil { resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return } - plan.ID = types.StringValue(strings.TrimSpace(plan.OrgUID.ValueString())) + if err := r.applyAllocation(ctx, orgUID, plan.VIPConfigure); err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) + return + } + + plan.ID = types.StringValue(orgUID) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -116,12 +123,18 @@ func (r *OrgIpAllocationResource) Update(ctx context.Context, req resource.Updat return } - if err := r.applyAllocation(ctx, plan.OrgUID, plan.VIPConfigure); err != nil { + orgUID, err := r.resolveOrganizationUID(ctx, plan.Organization) + if err != nil { resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return } - plan.ID = types.StringValue(strings.TrimSpace(plan.OrgUID.ValueString())) + if err := r.applyAllocation(ctx, orgUID, plan.VIPConfigure); err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) + return + } + + plan.ID = types.StringValue(orgUID) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -132,8 +145,13 @@ func (r *OrgIpAllocationResource) Read(ctx context.Context, req resource.ReadReq return } - orgUID := strings.TrimSpace(state.OrgUID.ValueString()) - if orgUID == "" || r.client == nil { + if strings.TrimSpace(state.Organization.ValueString()) == "" || r.client == nil { + return + } + + orgUID, err := r.resolveOrganizationUID(ctx, state.Organization) + if err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return } @@ -181,8 +199,13 @@ func (r *OrgIpAllocationResource) Delete(ctx context.Context, req resource.Delet return } - orgUID := strings.TrimSpace(state.OrgUID.ValueString()) - if orgUID == "" || r.client == nil { + if strings.TrimSpace(state.Organization.ValueString()) == "" || r.client == nil { + return + } + + orgUID, err := r.resolveOrganizationUID(ctx, state.Organization) + if err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return } @@ -267,14 +290,37 @@ func (r *OrgIpAllocationResource) Configure(_ context.Context, req resource.Conf func (r *OrgIpAllocationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { 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("org_uid"), uid)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization"), uid)...) +} + +// resolveOrganizationUID принимает имя организации из ЛК или её UUID и возвращает UUID. +// Резолв делает клиент — тем же путём, что сгенерированный nubes_vc_vdc +// (core.ResolveRefSvcParamValue, сравн. 21_vc_vdc_resource.go). +func (r *OrgIpAllocationResource) resolveOrganizationUID(ctx context.Context, organization types.String) (string, error) { + if r.client == nil { + return "", fmt.Errorf("клиент не инициализирован") + } + raw := strings.TrimSpace(organization.ValueString()) + if raw == "" { + return "", fmt.Errorf("organization обязателен") + } + + resolved, err := r.client.ResolveRefSvcParamValue(ctx, 19, raw) + if err != nil { + return "", fmt.Errorf("не удалось определить организацию %q: %w", raw, err) + } + resolved = strings.TrimSpace(resolved) + if resolved == "" { + return "", fmt.Errorf("организация %q не найдена", raw) + } + return resolved, nil } // applyAllocation отправляет modify с массивом vIPConfigure целиком. -func (r *OrgIpAllocationResource) applyAllocation(ctx context.Context, orgUID types.String, vipConfigure types.String) error { - uid := strings.TrimSpace(orgUID.ValueString()) +func (r *OrgIpAllocationResource) applyAllocation(ctx context.Context, orgUID string, vipConfigure types.String) error { + uid := strings.TrimSpace(orgUID) if uid == "" { - return fmt.Errorf("org_uid обязателен") + return fmt.Errorf("organization обязателен") } if r.client == nil { return fmt.Errorf("клиент не инициализирован")