feat(provider): ресурс аллокации принимает имя организации (резолв в UUID через ResolveRefSvcParamValue, как в nubes_vc_vdc); конфиги и доки без org_uid

This commit is contained in:
Repinoid
2026-09-24 15:28:13 +03:00
parent bd5de0cead
commit 5bd197f031
5 changed files with 71 additions and 33 deletions
+2 -2
View File
@@ -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
}
-6
View File
@@ -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" {
+3 -3
View File
@@ -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 <org_uid>
terraform import nubes_vc_org_ip_allocation.this organ
terraform import nubes_vc_nsxt_snat.this <nsxt_uid>
```
+3 -5
View File
@@ -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"
```
@@ -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("клиент не инициализирован")