feat(provider): ресурс аллокации принимает имя организации (резолв в UUID через ResolveRefSvcParamValue, как в nubes_vc_vdc); конфиги и доки без org_uid
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
# 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком)
|
# 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком)
|
||||||
resource "nubes_vc_org_ip_allocation" "org_ip" {
|
resource "nubes_vc_org_ip_allocation" "org_ip" {
|
||||||
org_uid = var.org_uid
|
organization = var.organization
|
||||||
|
|
||||||
vip_configure = jsonencode([
|
vip_configure = jsonencode([
|
||||||
{
|
{
|
||||||
@@ -45,7 +45,7 @@ resource "nubes_vc_nsxt_snat" "snat" {
|
|||||||
output "allocated_org_ip" {
|
output "allocated_org_ip" {
|
||||||
description = "Выделено внешних IP на организации"
|
description = "Выделено внешних IP на организации"
|
||||||
value = {
|
value = {
|
||||||
org_uid = var.org_uid
|
organization = var.organization
|
||||||
ip_space_name = var.ip_space_name
|
ip_space_name = var.ip_space_name
|
||||||
ip_count = var.ip_count
|
ip_count = var.ip_count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ variable "organization" {
|
|||||||
description = "Имя или UUID организации (vc_org)"
|
description = "Имя или UUID организации (vc_org)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# UUID той же организации — нужен ресурсам-модификаторам (они адресуются строго по uid)
|
|
||||||
variable "org_uid" {
|
|
||||||
type = string
|
|
||||||
description = "UUID организации (vc_org) для nubes_vc_org_ip_allocation"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Модификаторы (IP на орге + SNAT на эдже) ---
|
# --- Модификаторы (IP на орге + SNAT на эдже) ---
|
||||||
|
|
||||||
variable "ip_space_name" {
|
variable "ip_space_name" {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
| Атрибут | Тип | Описание |
|
| Атрибут | Тип | Описание |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `org_uid` | string, обязательный | UUID услуги «Организация в Cloud Director» |
|
| `organization` | string, обязательный | Организация: имя из ЛК или её UUID |
|
||||||
| `vip_configure` | string (JSON), обязательный | Массив аллокаций: `[{"name":"internet-ipv4-v1","count":"3"}]`. `count` — строка |
|
| `vip_configure` | string (JSON), обязательный | Массив аллокаций: `[{"name":"internet-ipv4-v1","count":"3"}]`. `count` — строка |
|
||||||
| `keep_on_destroy` | bool, по умолчанию `false` | Не снимать квоту при `destroy` |
|
| `keep_on_destroy` | bool, по умолчанию `false` | Не снимать квоту при `destroy` |
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
resource "nubes_vc_org_ip_allocation" "this" {
|
resource "nubes_vc_org_ip_allocation" "this" {
|
||||||
org_uid = var.org_uid
|
organization = var.organization
|
||||||
|
|
||||||
vip_configure = jsonencode([
|
vip_configure = jsonencode([
|
||||||
{ name = "internet-ipv4-v1", count = "3" }
|
{ name = "internet-ipv4-v1", count = "3" }
|
||||||
@@ -70,6 +70,6 @@ resource "nubes_vc_nsxt_snat" "this" {
|
|||||||
Оба ресурса импортируются по UUID родительской услуги:
|
Оба ресурса импортируются по UUID родительской услуги:
|
||||||
|
|
||||||
```bash
|
```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>
|
terraform import nubes_vc_nsxt_snat.this <nsxt_uid>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ cd tf_examples/fullpipe_chain
|
|||||||
| Что | Где взять | Пример |
|
| Что | Где взять | Пример |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Токен API | ЛК → Профиль → Токены → «Технический» | `eyJhbGciOi...` |
|
| Токен API | ЛК → Профиль → Токены → «Технический» | `eyJhbGciOi...` |
|
||||||
| UUID организации | ЛК → услуга «Организация в Cloud Director» → UUID в карточке услуги | `57eeacd1-dc7f-4a52-b903-7e5f7d3c1164` |
|
| Имя организации | ЛК → услуга «Организация в Cloud Director» → название услуги | `organ` |
|
||||||
| Имя организации | там же — название услуги | `organ` |
|
|
||||||
| Сетевой провайдер | ЛК → vDC → «Сетевой провайдер» | `snb1` |
|
| Сетевой провайдер | ЛК → vDC → «Сетевой провайдер» | `snb1` |
|
||||||
| Provider VDC | ЛК → vDC → «Provider VDC» | `Intel Broadwell 2.4` |
|
| Provider VDC | ЛК → vDC → «Provider VDC» | `Intel Broadwell 2.4` |
|
||||||
| Дисковая политика | ЛК → vDC → доступные дисковые политики | `SATA` |
|
| Дисковая политика | ЛК → vDC → доступные дисковые политики | `SATA` |
|
||||||
@@ -31,9 +30,8 @@ cp terraform.tfvars.example terraform.tfvars
|
|||||||
```
|
```
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
api_token = "eyJhbGciOi..." # токен из шага 2
|
api_token = "eyJhbGciOi..." # токен из шага 2
|
||||||
organization = "organ" # имя организации
|
organization = "organ" # имя организации
|
||||||
org_uid = "57eeacd1-dc7f-4a52-b903-7e5f7d3c1164" # UUID организации
|
|
||||||
ip_space_name = "internet-ipv4-v1"
|
ip_space_name = "internet-ipv4-v1"
|
||||||
ip_count = "3"
|
ip_count = "3"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type OrgIpAllocationResource struct {
|
|||||||
|
|
||||||
type OrgIpAllocationModel struct {
|
type OrgIpAllocationModel struct {
|
||||||
ID types.String `tfsdk:"id"`
|
ID types.String `tfsdk:"id"`
|
||||||
OrgUID types.String `tfsdk:"org_uid"`
|
Organization types.String `tfsdk:"organization"`
|
||||||
VIPConfigure types.String `tfsdk:"vip_configure"`
|
VIPConfigure types.String `tfsdk:"vip_configure"`
|
||||||
KeepOnDestroy types.Bool `tfsdk:"keep_on_destroy"`
|
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) {
|
func (r *OrgIpAllocationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
MarkdownDescription: "Аллокация внешних IP (vIPConfigure) на существующей организации Cloud Director. " +
|
MarkdownDescription: "Аллокация внешних IP (vIPConfigure) на существующей организации Cloud Director. " +
|
||||||
"Организация создаётся вручную в ЛК, ресурс адресует её по `org_uid`. " +
|
"Организация создаётся вручную в ЛК, в конфиге указывается её имя или UUID. " +
|
||||||
"Операция имеет replace-семантику: массив перезаписывается целиком.",
|
"Операция имеет replace-семантику: массив перезаписывается целиком.",
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"id": schema.StringAttribute{
|
"id": schema.StringAttribute{
|
||||||
@@ -68,9 +68,10 @@ func (r *OrgIpAllocationResource) Schema(ctx context.Context, req resource.Schem
|
|||||||
stringplanmodifier.UseStateForUnknown(),
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"org_uid": schema.StringAttribute{
|
"organization": schema.StringAttribute{
|
||||||
Required: true,
|
Required: true,
|
||||||
MarkdownDescription: "UUID существующей услуги «Организация в Cloud Director».",
|
MarkdownDescription: "Организация, на которой выделяются внешние IP: имя из ЛК (например `organ`) " +
|
||||||
|
"или её UUID.",
|
||||||
PlanModifiers: []planmodifier.String{
|
PlanModifiers: []planmodifier.String{
|
||||||
stringplanmodifier.RequiresReplace(),
|
stringplanmodifier.RequiresReplace(),
|
||||||
},
|
},
|
||||||
@@ -100,12 +101,18 @@ func (r *OrgIpAllocationResource) Create(ctx context.Context, req resource.Creat
|
|||||||
return
|
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())
|
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||||
return
|
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)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +123,18 @@ func (r *OrgIpAllocationResource) Update(ctx context.Context, req resource.Updat
|
|||||||
return
|
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())
|
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||||
return
|
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)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +145,13 @@ func (r *OrgIpAllocationResource) Read(ctx context.Context, req resource.ReadReq
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orgUID := strings.TrimSpace(state.OrgUID.ValueString())
|
if strings.TrimSpace(state.Organization.ValueString()) == "" || r.client == nil {
|
||||||
if orgUID == "" || r.client == nil {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgUID, err := r.resolveOrganizationUID(ctx, state.Organization)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +199,13 @@ func (r *OrgIpAllocationResource) Delete(ctx context.Context, req resource.Delet
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orgUID := strings.TrimSpace(state.OrgUID.ValueString())
|
if strings.TrimSpace(state.Organization.ValueString()) == "" || r.client == nil {
|
||||||
if orgUID == "" || r.client == nil {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgUID, err := r.resolveOrganizationUID(ctx, state.Organization)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||||
return
|
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) {
|
func (r *OrgIpAllocationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
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("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 целиком.
|
// applyAllocation отправляет modify с массивом vIPConfigure целиком.
|
||||||
func (r *OrgIpAllocationResource) applyAllocation(ctx context.Context, orgUID types.String, vipConfigure types.String) error {
|
func (r *OrgIpAllocationResource) applyAllocation(ctx context.Context, orgUID string, vipConfigure types.String) error {
|
||||||
uid := strings.TrimSpace(orgUID.ValueString())
|
uid := strings.TrimSpace(orgUID)
|
||||||
if uid == "" {
|
if uid == "" {
|
||||||
return fmt.Errorf("org_uid обязателен")
|
return fmt.Errorf("organization обязателен")
|
||||||
}
|
}
|
||||||
if r.client == nil {
|
if r.client == nil {
|
||||||
return fmt.Errorf("клиент не инициализирован")
|
return fmt.Errorf("клиент не инициализирован")
|
||||||
|
|||||||
Reference in New Issue
Block a user