4.12.2
This commit is contained in:
dayterr
2026-08-14 16:22:01 +03:00
parent 8b4a60e448
commit 9cd93e4277
25 changed files with 879 additions and 228 deletions

View File

@@ -1,8 +1,53 @@
## Version 4.12.1 ## Version 4.12.2
### Добавлено
#### SDN logicalports
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1363 | Вычисляемые поля `id`, `logical_port_id` и `assigned_at` в блоке `logical_port_addresses` в resource `decort_sdn_logical_port` в sdn/logicalports |
#### extnet
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1375 | Вычисляемые поля `sec_vnfdev_id` и `pre_reservations` в resource `decort_cb_extnet` в cloudbroker/extnet |
#### kvmvm
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1375 | Вычисляемое поле `user_data` в resource `decort_cb_kvmvm` в cloudbroker/kvmvm |
### Изменено
#### SDN logicalports
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1363 | Тип поля `logical_port_addresses` с `list` на `set` в resource `decort_sdn_logical_port` в sdn/logicalports |
| BATF-1363 | Тип поля `unique_identifier` с опционального на опционально-вычисляемое в resource `decort_sdn_logical_port` в sdn/logicalports |
| BATF-1363 | Тип поля `is_primary` в блоке `logical_port_addresses` с обязательного на опциональное в resource `decort_sdn_logical_port` в sdn/logicalports |
#### k8ci
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1376 | Опциональное поле `enabled` получило значение по умолчанию `true` в resource `decort_cb_k8ci` в cloudbroker/k8ci |
#### storage policy
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1373 | Тип поля `limit_iops` с опционального на опционально-вычисляемое в resource `decort_cb_storage_policy` в cloudbroker/stpolicy |
### Исправлено ### Исправлено
#### sdn logicalports #### SDN logicalports
| Идентификатор<br>задачи | Описание | | Идентификатор<br>задачи | Описание |
| --- | --- | | --- | --- |
| BATF-1357 | Ошибка поля `adapter_mac` при обновлении resource `decort_sdn_logical_port` в sdn/logicalports | | BATF-1363 | Баг с полем `labels` при обновлении resource `decort_sdn_logical_port` в sdn/logicalports |
| BATF-1363 | Каскадное изменение адресов `logical_port_addresses` при редактировании списка блоков адресов в resource `decort_sdn_logical_port` в sdn/logicalports |
#### extnet
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BATF-1375 | Баги с полями `shared_with`, `vnfdev_ip`, `default_qos`, `default_qos`, `highly_available`, `mtu` и `sec_vnfdev_id` при создании resource `decort_cb_extnet` в cloudbroker/extnet |

View File

@@ -7,7 +7,7 @@ ZIPDIR = ./zip
BINARY=${NAME} BINARY=${NAME}
WORKPATH= ./examples/terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAMESPACE}/${VERSION}/${OS_ARCH} WORKPATH= ./examples/terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAMESPACE}/${VERSION}/${OS_ARCH}
MAINPATH = ./cmd/decort/ MAINPATH = ./cmd/decort/
VERSION=4.12.1 VERSION=4.12.2
OS_ARCH=$(shell go env GOHOSTOS)_$(shell go env GOHOSTARCH) OS_ARCH=$(shell go env GOHOSTOS)_$(shell go env GOHOSTARCH)
FILES = ${BINARY}_${VERSION}_darwin_amd64\ FILES = ${BINARY}_${VERSION}_darwin_amd64\

View File

@@ -64,10 +64,12 @@ description: |-
- `milestones` (Number) - `milestones` (Number)
- `network` (String) - `network` (String)
- `network_ids` (List of Object) (see [below for nested schema](#nestedatt--network_ids)) - `network_ids` (List of Object) (see [below for nested schema](#nestedatt--network_ids))
- `pre_reservations` (List of Object) (see [below for nested schema](#nestedatt--pre_reservations))
- `prefix` (Number) - `prefix` (Number)
- `pri_vnfdev_id` (Number) - `pri_vnfdev_id` (Number)
- `reservations` (List of Object) (see [below for nested schema](#nestedatt--reservations)) - `reservations` (List of Object) (see [below for nested schema](#nestedatt--reservations))
- `routes` (List of Object) (see [below for nested schema](#nestedatt--routes)) - `routes` (List of Object) (see [below for nested schema](#nestedatt--routes))
- `sec_vnfdev_id` (Number)
- `status` (String) - `status` (String)
- `vnfs` (List of Object) (see [below for nested schema](#nestedatt--vnfs)) - `vnfs` (List of Object) (see [below for nested schema](#nestedatt--vnfs))
@@ -125,6 +127,7 @@ Optional:
Read-Only: Read-Only:
- `client_type` (String) - `client_type` (String)
- `desc` (String)
- `domain_name` (String) - `domain_name` (String)
- `hostname` (String) - `hostname` (String)
- `ip` (String) - `ip` (String)
@@ -142,6 +145,22 @@ Read-Only:
- `secondary` (Number) - `secondary` (Number)
<a id="nestedatt--pre_reservations"></a>
### Nested Schema for `pre_reservations`
Read-Only:
- `account_id` (Number)
- `client_type` (String)
- `desc` (String)
- `domain_name` (String)
- `hostname` (String)
- `ip` (String)
- `mac` (String)
- `type` (String)
- `vm_id` (Number)
<a id="nestedatt--reservations"></a> <a id="nestedatt--reservations"></a>
### Nested Schema for `reservations` ### Nested Schema for `reservations`

View File

@@ -135,6 +135,7 @@ description: |-
- `tech_status` (String) - `tech_status` (String)
- `updated_by` (String) - `updated_by` (String)
- `updated_time` (Number) - `updated_time` (Number)
- `user_data` (String)
- `user_managed` (Boolean) - `user_managed` (Boolean)
- `vgpus` (List of Object) List of virtual GPUs (see [below for nested schema](#nestedatt--vgpus)) - `vgpus` (List of Object) List of virtual GPUs (see [below for nested schema](#nestedatt--vgpus))
- `virtual_image_name` (String) - `virtual_image_name` (String)

View File

@@ -17,21 +17,21 @@ description: |-
### Required ### Required
- `access_group_id` (String) Access Group ID - `access_group_id` (String) UUID of the access group
- `description` (String) Description - `description` (String) Description
- `display_name` (String) Display Name - `display_name` (String) Display Name
- `enabled` (Boolean) Whether the logical port should be enabled - `enabled` (Boolean) Whether the logical port should be enabled
- `hypervisor` (String) Hypervisor - `hypervisor` (String) Associated hypervisor
- `port_security` (Boolean) Whether the port security is enabled - `port_security` (Boolean) Whether the port security is enabled
- `segment_id` (String) Segment ID - `segment_id` (String) UUID of the network segment
### Optional ### Optional
- `adapter_mac` (String) Adapter MAC address - `adapter_mac` (String) Adapter MAC address
- `force` (Boolean) - `force` (Boolean)
- `labels` (Block List, Max: 1) Labels (see [below for nested schema](#nestedblock--labels)) - `labels` (Block List, Max: 1) Labels (see [below for nested schema](#nestedblock--labels))
- `logical_port_addresses` (Block List) (see [below for nested schema](#nestedblock--logical_port_addresses)) - `logical_port_addresses` (Block Set) (see [below for nested schema](#nestedblock--logical_port_addresses))
- `migrate` (Boolean) If true, triggers live migration to the hypervisor specified in the 'hypervisor' field. If false, hypervisor changes are applied via the regular update endpoint. - `migrate` (Boolean) If true, triggers live migration to the hypervisor specified in the 'hypervisor' field. If false, hypervisor changes are applied via the regular update endpoint
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
- `unique_identifier` (String) Unique identifier of the logical port - `unique_identifier` (String) Unique identifier of the logical port
@@ -41,11 +41,13 @@ description: |-
- `address_detection` (Boolean) - `address_detection` (Boolean)
- `bindings` (List of Object) (see [below for nested schema](#nestedatt--bindings)) - `bindings` (List of Object) (see [below for nested schema](#nestedatt--bindings))
- `created_at` (String) - `created_at` (String)
- `exclude_firewall` (List of Object) (see [below for nested schema](#nestedatt--exclude_firewall))
- `external_network_id` (String) - `external_network_id` (String)
- `hypervisor_display_name` (String) - `hypervisor_display_name` (String)
- `id` (String) ID of the logical port - `id` (String) ID of the logical port
- `live_migration_target_hv` (String) - `live_migration_target_hv` (String)
- `status` (List of Object) (see [below for nested schema](#nestedatt--status)) - `status` (List of Object) (see [below for nested schema](#nestedatt--status))
- `updated_at` (String) Last update time of the logical port
- `version_id` (Number) Version ID of the logical port - `version_id` (Number) Version ID of the logical port
<a id="nestedblock--labels"></a> <a id="nestedblock--labels"></a>
@@ -63,13 +65,19 @@ Optional:
Required: Required:
- `ip` (String) IP address of the logical port - `ip` (String) IP address of the logical port
- `ip_type` (String) - `ip_type` (String) IP type
- `is_primary` (Boolean)
Optional: Optional:
- `is_discovered` (Boolean) - `is_discovered` (Boolean)
- `mac` (String) - `is_primary` (Boolean)
- `mac` (String) MAC address; optional on initial port creation & required when adding or updating addresses in an existing logical port
Read-Only:
- `assigned_at` (String) Timestamp when the address was assigned
- `id` (String) ID of the address, assigned by the platform
- `logical_port_id` (String) ID of the parent logical port
<a id="nestedblock--timeouts"></a> <a id="nestedblock--timeouts"></a>
@@ -115,6 +123,16 @@ Read-Only:
<a id="nestedatt--exclude_firewall"></a>
### Nested Schema for `exclude_firewall`
Read-Only:
- `exclusion_reason` (String)
- `logical_port_addresses_excluded` (Boolean)
- `logical_port_excluded` (Boolean)
<a id="nestedatt--status"></a> <a id="nestedatt--status"></a>
### Nested Schema for `status` ### Nested Schema for `status`

2
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
golang.org/x/net v0.44.0 golang.org/x/net v0.44.0
repository.basistech.ru/BASIS/decort-golang-sdk v1.14.12 repository.basistech.ru/BASIS/decort-golang-sdk v1.14.13
) )
require ( require (

4
go.sum
View File

@@ -318,5 +318,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repository.basistech.ru/BASIS/decort-golang-sdk v1.14.12 h1:R3qmiYmeF+cun3VyBm4FR3pMvXByp7HY19Jsyoj66gA= repository.basistech.ru/BASIS/decort-golang-sdk v1.14.13 h1:kYopwej9i4QORHzCyo8AXdHQplyCBD10771PJFBB+4A=
repository.basistech.ru/BASIS/decort-golang-sdk v1.14.12/go.mod h1:S/f7GxwWcE88eFpORV+I9xqEf8zDW5srQHpG2XQCLZM= repository.basistech.ru/BASIS/decort-golang-sdk v1.14.13/go.mod h1:S/f7GxwWcE88eFpORV+I9xqEf8zDW5srQHpG2XQCLZM=

View File

@@ -89,7 +89,7 @@ func flattenRecordExtnet(d *schema.ResourceData, recNet *extnet.RecordExtNet) {
d.Set("ipcidr", recNet.IPCIDR) d.Set("ipcidr", recNet.IPCIDR)
d.Set("milestones", recNet.Milestones) d.Set("milestones", recNet.Milestones)
d.Set("name", recNet.Name) d.Set("name", recNet.Name)
d.Set("network_ids", recNet.NetworkIDs) d.Set("network_ids", flattenNetworkIDs(recNet.NetworkIDs))
d.Set("ntp", recNet.NTP) d.Set("ntp", recNet.NTP)
d.Set("ovs_bridge", recNet.OVSBridge) d.Set("ovs_bridge", recNet.OVSBridge)
d.Set("pre_reservations_num", recNet.PreReservationsNum) d.Set("pre_reservations_num", recNet.PreReservationsNum)
@@ -142,9 +142,13 @@ func flattenRecordExtnetResource(d *schema.ResourceData, recNet *extnet.RecordEx
d.Set("default_qos", flattenExtnetDefaultQos(recNet.DefaultQOS)) d.Set("default_qos", flattenExtnetDefaultQos(recNet.DefaultQOS))
d.Set("vnfs", flattenExtnetVNFS(recNet.VNFs)) d.Set("vnfs", flattenExtnetVNFS(recNet.VNFs))
d.Set("reservations", flattenExtnetReservations(recNet.Reservations)) d.Set("reservations", flattenExtnetReservations(recNet.Reservations))
d.Set("pre_reservations", flattenExtnetReservations(recNet.PreReservations))
d.Set("routes", flattenStaticRouteList(staticRouteList)) d.Set("routes", flattenStaticRouteList(staticRouteList))
d.Set("zone_id", recNet.ZoneID) d.Set("zone_id", recNet.ZoneID)
d.Set("enable_secgroups", recNet.EnableSecGroups) d.Set("enable_secgroups", recNet.EnableSecGroups)
d.Set("sec_vnfdev_id", recNet.SecVNFDevID)
d.Set("highly_available", recNet.Redundant)
d.Set("mtu", recNet.MTU)
} }
func flattenExtnetExcluded(ers extnet.ListReservations) []map[string]interface{} { func flattenExtnetExcluded(ers extnet.ListReservations) []map[string]interface{} {

View File

@@ -35,6 +35,8 @@ package extnet
import ( import (
"context" "context"
"fmt"
"net"
"strconv" "strconv"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -65,6 +67,19 @@ func resourceExtnetCreate(ctx context.Context, d *schema.ResourceData, m interfa
VLANID: uint64(d.Get("vlan_id").(int)), VLANID: uint64(d.Get("vlan_id").(int)),
} }
var diags diag.Diagnostics
if _, ipNet, err := net.ParseCIDR(req.IPCIDR); err == nil {
normalized := ipNet.String()
if normalized != req.IPCIDR {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "ipcidr will be normalized by the platform",
Detail: fmt.Sprintf("CIDR will be: %q", normalized),
})
}
}
if gateway, ok := d.GetOk("gateway"); ok { if gateway, ok := d.GetOk("gateway"); ok {
req.Gateway = gateway.(string) req.Gateway = gateway.(string)
} }
@@ -132,6 +147,10 @@ func resourceExtnetCreate(ctx context.Context, d *schema.ResourceData, m interfa
req.SecVNFDevIP = sec_vnfdev_ip.(string) req.SecVNFDevIP = sec_vnfdev_ip.(string)
} }
if vnfdev_ip, ok := d.GetOk("vnfdev_ip"); ok {
req.PriVNFDevIP = vnfdev_ip.(string)
}
if mtu, ok := d.GetOk("mtu"); ok { if mtu, ok := d.GetOk("mtu"); ok {
req.MTU = uint(mtu.(int)) req.MTU = uint(mtu.(int))
} }
@@ -170,12 +189,12 @@ func resourceExtnetCreate(ctx context.Context, d *schema.ResourceData, m interfa
if d.Get("shared_with").(*schema.Set).Len() > 0 { if d.Get("shared_with").(*schema.Set).Len() > 0 {
for _, id := range d.Get("shared_with").(*schema.Set).List() { for _, id := range d.Get("shared_with").(*schema.Set).List() {
req := extnet.AccessRemoveRequest{ req := extnet.AccessAddRequest{
NetID: uint64(d.Get("extnet_id").(int)), NetID: uint64(d.Get("extnet_id").(int)),
AccountID: uint64(id.(int)), AccountID: uint64(id.(int)),
} }
_, err := c.CloudBroker().ExtNet().AccessRemove(ctx, req) _, err := c.CloudBroker().ExtNet().AccessAdd(ctx, req)
if err != nil { if err != nil {
w.Add(err) w.Add(err)
} }
@@ -208,6 +227,12 @@ func resourceExtnetCreate(ctx context.Context, d *schema.ResourceData, m interfa
} }
} }
if qos, ok := d.GetOk("default_qos"); ok && len(qos.([]interface{})) > 0 {
if err := handleDefaultQOSUpdate(ctx, d, c, &extnet.RecordExtNet{ID: netID}); err != nil {
w.Add(err)
}
}
// for reserve IP extnet must be enabled // for reserve IP extnet must be enabled
if d.Get("reserved_ip").(*schema.Set).Len() > 0 { if d.Get("reserved_ip").(*schema.Set).Len() > 0 {
for _, reservedIP := range d.Get("reserved_ip").(*schema.Set).List() { for _, reservedIP := range d.Get("reserved_ip").(*schema.Set).List() {
@@ -236,7 +261,9 @@ func resourceExtnetCreate(ctx context.Context, d *schema.ResourceData, m interfa
} }
} }
return resourceExtnetRead(ctx, d, m) diags = append(diags, w.Get()...)
return append(diags, resourceExtnetRead(ctx, d, m)...)
} }
func resourceExtnetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { func resourceExtnetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {

View File

@@ -1,6 +1,8 @@
package extnet package extnet
import ( import (
"net"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
) )
@@ -690,7 +692,15 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
// ForceNew: true, // ForceNew: true,
Description: "IP network CIDR", Description: "IP network CIDR",
ValidateFunc: validation.IsCIDR,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
_, ipnet, err := net.ParseCIDR(new)
if err != nil {
return false
}
return old == ipnet.String()
},
}, },
"vlan_id": { "vlan_id": {
Type: schema.TypeInt, Type: schema.TypeInt,
@@ -699,10 +709,11 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Description: "VLAN ID", Description: "VLAN ID",
}, },
"gateway": { "gateway": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
Description: "External network gateway IP address", Description: "External network gateway IP address",
ValidateFunc: validation.IsIPAddress,
}, },
"zone_id": { "zone_id": {
Type: schema.TypeInt, Type: schema.TypeInt,
@@ -720,7 +731,8 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Optional: true, Optional: true,
Computed: true, Computed: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
}, },
Description: "List of DNS addresses", Description: "List of DNS addresses",
}, },
@@ -729,7 +741,8 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Optional: true, Optional: true,
Computed: true, Computed: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
}, },
Description: "List of NTP addresses", Description: "List of NTP addresses",
}, },
@@ -738,7 +751,8 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Optional: true, Optional: true,
Computed: true, Computed: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
}, },
Description: "IPs to check network availability", Description: "IPs to check network availability",
}, },
@@ -754,19 +768,22 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Description: "Optional description", Description: "Optional description",
}, },
"start_ip": { "start_ip": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "Start of IP range to be explicitly included", Description: "Start of IP range to be explicitly included",
ValidateFunc: validation.IsIPAddress,
}, },
"end_ip": { "end_ip": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "End of IP range to be explicitly included", Description: "End of IP range to be explicitly included",
ValidateFunc: validation.IsIPAddress,
}, },
"vnfdev_ip": { "vnfdev_ip": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "IP to create VNFDev with", Description: "IP to create VNFDev with",
ValidateFunc: validation.IsIPAddress,
}, },
"pre_reservations_num": { "pre_reservations_num": {
Type: schema.TypeInt, Type: schema.TypeInt,
@@ -796,7 +813,8 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeSet, Type: schema.TypeSet,
Optional: true, Optional: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
}, },
Description: "IPs to exclude in current extnet pool", Description: "IPs to exclude in current extnet pool",
}, },
@@ -806,12 +824,14 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"ip_start": { "ip_start": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ValidateFunc: validation.IsIPAddress,
}, },
"ip_end": { "ip_end": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ValidateFunc: validation.IsIPAddress,
}, },
}, },
}, },
@@ -881,7 +901,8 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeSet, Type: schema.TypeSet,
Optional: true, Optional: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
}, },
}, },
}, },
@@ -893,9 +914,10 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Computed: true, Computed: true,
}, },
"sec_vnfdev_ip": { "sec_vnfdev_ip": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
ValidateFunc: validation.IsIPAddress,
}, },
"mtu": { "mtu": {
Type: schema.TypeInt, Type: schema.TypeInt,
@@ -954,6 +976,10 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
}, },
"sec_vnfdev_id": {
Type: schema.TypeInt,
Computed: true,
},
"status": { "status": {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
@@ -979,6 +1005,10 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"desc": {
Type: schema.TypeString,
Computed: true,
},
"domain_name": { "domain_name": {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
@@ -1014,6 +1044,50 @@ func resourceExtnetSchemaMake() map[string]*schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
}, },
"pre_reservations": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeInt,
Computed: true,
},
"client_type": {
Type: schema.TypeString,
Computed: true,
},
"domain_name": {
Type: schema.TypeString,
Computed: true,
},
"hostname": {
Type: schema.TypeString,
Computed: true,
},
"desc": {
Type: schema.TypeString,
Computed: true,
},
"ip": {
Type: schema.TypeString,
Computed: true,
},
"mac": {
Type: schema.TypeString,
Computed: true,
},
"type": {
Type: schema.TypeString,
Computed: true,
},
"vm_id": {
Type: schema.TypeInt,
Computed: true,
},
},
},
},
"routes": { "routes": {
Type: schema.TypeList, Type: schema.TypeList,
Computed: true, Computed: true,

View File

@@ -37,6 +37,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8ci" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/k8ci"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/status"
) )
func flattenK8CIListItems(list *k8ci.ListK8CI) []map[string]interface{} { func flattenK8CIListItems(list *k8ci.ListK8CI) []map[string]interface{} {
@@ -69,6 +70,7 @@ func flattenK8CIItems(d *schema.ResourceData, data *k8ci.RecordK8CI) error {
log.Debugf("flattenK8CI: ID %d", data.ID) log.Debugf("flattenK8CI: ID %d", data.ID)
d.Set("desc", data.Description) d.Set("desc", data.Description)
d.Set("enabled", data.Status == status.Enabled)
d.Set("gid", data.GID) d.Set("gid", data.GID)
d.Set("guid", data.GUID) d.Set("guid", data.GUID)
d.Set("k8ci_id", data.ID) d.Set("k8ci_id", data.ID)

View File

@@ -93,22 +93,21 @@ func resourceK8CICreate(ctx context.Context, d *schema.ResourceData, m interface
warnings := dc.Warnings{} warnings := dc.Warnings{}
if enabled, ok := d.GetOk("enabled"); ok { enabled := d.Get("enabled").(bool)
log.Debugf("resourceK8CICreate: enable=%t k8ci_id %d after completing its resource configuration", enabled, k8ciID) log.Debugf("resourceK8CICreate: enable=%t k8ci_id %d after completing its resource configuration", enabled, k8ciID)
if enabled.(bool) { if enabled {
enabledReq := k8ci.EnableRequest{ enabledReq := k8ci.EnableRequest{
K8CIID: k8ciID, K8CIID: k8ciID,
} }
if _, err := c.CloudBroker().K8CI().Enable(ctx, enabledReq); err != nil { if _, err := c.CloudBroker().K8CI().Enable(ctx, enabledReq); err != nil {
warnings.Add(err) warnings.Add(err)
} }
} else { } else {
disableReq := k8ci.DisableRequest{ disableReq := k8ci.DisableRequest{
K8CIID: k8ciID, K8CIID: k8ciID,
} }
if _, err := c.CloudBroker().K8CI().Disable(ctx, disableReq); err != nil { if _, err := c.CloudBroker().K8CI().Disable(ctx, disableReq); err != nil {
warnings.Add(err) warnings.Add(err)
}
} }
} }
@@ -199,8 +198,8 @@ func resourceK8CIUpdate(ctx context.Context, d *schema.ResourceData, m interface
return diag.FromErr(err) return diag.FromErr(err)
} }
if enabled, ok := d.GetOk("enabled"); ok { if enabled, ok := d.Get("enabled").(bool); ok {
if enabled.(bool) { if enabled {
enabledReq := k8ci.EnableRequest{K8CIID: k8ciRec.ID} enabledReq := k8ci.EnableRequest{K8CIID: k8ciRec.ID}
_, err := c.CloudBroker().K8CI().Enable(ctx, enabledReq) _, err := c.CloudBroker().K8CI().Enable(ctx, enabledReq)
if err != nil { if err != nil {

View File

@@ -388,6 +388,7 @@ func resourceK8CISchemaMake() map[string]*schema.Schema {
"enabled": { "enabled": {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Default: true,
}, },
"permanently": { "permanently": {
Type: schema.TypeBool, Type: schema.TypeBool,

View File

@@ -4672,6 +4672,10 @@ func resourceComputeSchemaMake() map[string]*schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
}, },
"user_data": {
Type: schema.TypeString,
Computed: true,
},
"user_managed": { "user_managed": {
Type: schema.TypeBool, Type: schema.TypeBool,
Computed: true, Computed: true,

View File

@@ -62,9 +62,8 @@ func resourceStoragePolicyCreate(ctx context.Context, d *schema.ResourceData, m
d.SetId(strconv.FormatUint(storagePolicyID, 10)) d.SetId(strconv.FormatUint(storagePolicyID, 10))
if enabled, ok := d.GetOk("enabled"); ok { if enabled, ok := d.Get("enabled").(bool); ok {
isToEnable := enabled.(bool) if err := handleStoragePolicyEnabling(ctx, d, c, storagePolicyID, enabled); err != nil {
if err := handleStoragePolicyEnabling(ctx, d, c, storagePolicyID, isToEnable); err != nil {
warnings.Add(err) warnings.Add(err)
} }
} }

View File

@@ -42,6 +42,7 @@ func resourceStoragePolicySchemaMake() map[string]*schema.Schema {
"limit_iops": { "limit_iops": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Computed: true,
}, },
"enabled": { "enabled": {
Type: schema.TypeBool, Type: schema.TypeBool,

View File

@@ -0,0 +1,22 @@
package logicalports
import (
"bytes"
"fmt"
"hash/fnv"
)
func hashLogicalPortAddress(v interface{}) int {
m := v.(map[string]interface{})
var buf bytes.Buffer
fmt.Fprintf(&buf, "ip:%s;", m["ip"].(string))
fmt.Fprintf(&buf, "ip_type:%s;", m["ip_type"].(string))
fmt.Fprintf(&buf, "is_primary:%t;", m["is_primary"].(bool))
fmt.Fprintf(&buf, "is_discovered:%t;", m["is_discovered"].(bool))
fmt.Fprintf(&buf, "mac:%s;", m["mac"].(string))
hs := fnv.New32a()
hs.Write(buf.Bytes())
return int(hs.Sum32())
}

View File

@@ -24,6 +24,20 @@ func flattenLogicalPortResource(d *schema.ResourceData, logicalPort *logicalport
d.Set("bindings", flattenBindings(logicalPort.Bindings)) d.Set("bindings", flattenBindings(logicalPort.Bindings))
d.Set("labels", flattenLabels(logicalPort.Labels)) d.Set("labels", flattenLabels(logicalPort.Labels))
d.Set("created_at", logicalPort.CreatedAt) d.Set("created_at", logicalPort.CreatedAt)
d.Set("updated_at", logicalPort.UpdatedAt)
d.Set("exclude_firewall", flattenExcludeFirewall(logicalPort.ExcludeFirewall))
d.Set("logical_port_addresses", flattenLogicalPortAddresses(logicalPort.Bindings.LogicalPortAddresses))
}
func flattenExcludeFirewall(excludeFirewall logicalports.ExcludeFirewall) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
temp := map[string]interface{}{
"exclusion_reason": excludeFirewall.ExclusionReason,
"logical_port_addresses_excluded": excludeFirewall.LogicalPortAddressesExcluded,
"logical_port_excluded": excludeFirewall.LogicalPortExcluded,
}
res = append(res, temp)
return res
} }
func flattenLogicalPortList(lpl *logicalports.LogicalPortsList) []map[string]interface{} { func flattenLogicalPortList(lpl *logicalports.LogicalPortsList) []map[string]interface{} {
@@ -79,7 +93,7 @@ func flattenLogicalPort(d *schema.ResourceData, logicalPort *logicalports.Logica
func flattenLabels(labels logicalports.Labels) []map[string]interface{} { func flattenLabels(labels logicalports.Labels) []map[string]interface{} {
if labels.VMID == "" && labels.VMName == "" { if labels.VMID == "" && labels.VMName == "" {
return []map[string]interface{}{} return nil
} }
return []map[string]interface{}{ return []map[string]interface{}{
{ {
@@ -106,19 +120,23 @@ func flattenBindings(bindings logicalports.Bindings) []map[string]interface{} {
return res return res
} }
func flattenLogicalPortAddress(a logicalports.LogicalPortAddress) map[string]interface{} {
return map[string]interface{}{
"ip": a.IP,
"ip_type": a.IPType,
"mac": a.MAC,
"id": a.ID,
"logical_port_id": a.LogicalPortID,
"assigned_at": a.AssignedAt,
"is_discovered": a.IsDiscovered,
"is_primary": a.IsPrimary,
}
}
func flattenLogicalPortAddresses(addrs []logicalports.LogicalPortAddress) []map[string]interface{} { func flattenLogicalPortAddresses(addrs []logicalports.LogicalPortAddress) []map[string]interface{} {
res := make([]map[string]interface{}, 0, len(addrs)) res := make([]map[string]interface{}, 0, len(addrs))
for _, a := range addrs { for _, a := range addrs {
res = append(res, map[string]interface{}{ res = append(res, flattenLogicalPortAddress(a))
"ip": a.IP,
"ip_type": a.IPType,
"mac": a.MAC,
"id": a.ID,
"logical_port_id": a.LogicalPortID,
"assigned_at": a.AssignedAt,
"is_discovered": a.IsDiscovered,
"is_primary": a.IsPrimary,
})
} }
return res return res
} }

View File

@@ -0,0 +1,259 @@
package logicalports
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func resourceLogicalPortResourceV1() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"access_group_id": {
Type: schema.TypeString,
Required: true,
Description: "Access Group ID",
},
"description": {
Type: schema.TypeString,
Required: true,
Description: "Description",
},
"display_name": {
Type: schema.TypeString,
Required: true,
Description: "Display Name",
},
"enabled": {
Type: schema.TypeBool,
Required: true,
Description: "Whether the logical port should be enabled",
},
"hypervisor": {
Type: schema.TypeString,
Required: true,
Description: "Hypervisor",
},
"port_security": {
Type: schema.TypeBool,
Required: true,
Description: "Whether the port security is enabled",
},
"segment_id": {
Type: schema.TypeString,
Required: true,
Description: "Segment ID",
},
"adapter_mac": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Adapter MAC address",
},
"unique_identifier": {
Type: schema.TypeString,
Optional: true,
Description: "Unique identifier of the logical port",
},
"force": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"migrate": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "If true, triggers live migration to the hypervisor specified in the 'hypervisor' field. If false, hypervisor changes are applied via the regular update endpoint.",
},
"logical_port_addresses": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Type: schema.TypeString,
Required: true,
Description: "IP address of the logical port",
},
"ip_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"IPv4", "IPv6"}, false),
},
"is_primary": {
Type: schema.TypeBool,
Required: true,
},
"is_discovered": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"mac": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"id": {
Type: schema.TypeString,
Computed: true,
Description: "ID of the logical port",
},
"access_group_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the access group",
},
"address_detection": {
Type: schema.TypeBool,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"external_network_id": {
Type: schema.TypeString,
Computed: true,
},
"hypervisor_display_name": {
Type: schema.TypeString,
Computed: true,
},
"live_migration_target_hv": {
Type: schema.TypeString,
Computed: true,
},
"status": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"operation_status": {
Type: schema.TypeString,
Computed: true,
Description: "Operation status",
},
"hypervisor_status": {
Type: schema.TypeString,
Computed: true,
Description: "Hypervisor status",
},
"hypervisors": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"operation_status": {
Type: schema.TypeString,
Computed: true,
Description: "Operation status of the hypervisor",
},
"name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the hypervisor",
},
"display_name": {
Type: schema.TypeString,
Computed: true,
Description: "Display name of the hypervisor",
},
"hypervisor_status": {
Type: schema.TypeString,
Computed: true,
Description: "Status of the hypervisor",
},
"synced_at": {
Type: schema.TypeString,
Computed: true,
Description: "Sync time of the hypervisor",
},
},
},
},
},
},
},
"bindings": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "Unique identifier of the binding",
},
"segment_id": {
Type: schema.TypeString,
Computed: true,
Description: "Unique identifier of the segment",
},
"segment_display_name": {
Type: schema.TypeString,
Computed: true,
Description: "Display name of the segment",
},
"port_security": {
Type: schema.TypeBool,
Computed: true,
Description: "If the port is secured",
},
"address_detection": {
Type: schema.TypeBool,
Computed: true,
Description: "If the adapter address detection is enabled",
},
"version_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Version ID of the binding",
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "Creation time of the binding",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "Update time of the binding",
},
"logical_port_addresses": logicalPortAddressesSchema(),
},
},
},
"version_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Version ID of the logical port",
},
"labels": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"vm_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "VM ID label",
},
"vm_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "VM name label",
},
},
},
Description: "Labels",
},
},
}
}

View File

@@ -2,6 +2,8 @@ package logicalports
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -16,6 +18,8 @@ func resourceLogicalPortCreate(ctx context.Context, d *schema.ResourceData, m in
d.Get("display_name").(string)) d.Get("display_name").(string))
c := m.(*controller.ControllerCfg) c := m.(*controller.ControllerCfg)
var diags diag.Diagnostics
req := logicalports.CreateRequest{ req := logicalports.CreateRequest{
AccessGroupID: d.Get("access_group_id").(string), AccessGroupID: d.Get("access_group_id").(string),
Description: d.Get("description").(string), Description: d.Get("description").(string),
@@ -36,42 +40,74 @@ func resourceLogicalPortCreate(ctx context.Context, d *schema.ResourceData, m in
labelsList := labelsRaw.([]interface{}) labelsList := labelsRaw.([]interface{})
if len(labelsList) > 0 { if len(labelsList) > 0 {
labelsMap := labelsList[0].(map[string]interface{}) labelsMap := labelsList[0].(map[string]interface{})
req.Labels = logicalports.CreateLabels{ req.Labels = &logicalports.CreateLabels{
VMID: labelsMap["vm_id"].(string), VMID: labelsMap["vm_id"].(string),
VMName: labelsMap["vm_name"].(string), VMName: labelsMap["vm_name"].(string),
} }
} }
} }
if logicalPortAddresses, ok := d.GetOk("logical_port_addresses"); ok { if logicalPortAddresses, ok := d.GetOk("logical_port_addresses"); ok {
logicalPortAddressesList := logicalPortAddresses.([]interface{}) logicalPortAddressesList := logicalPortAddresses.(*schema.Set).List()
logicalPortsAddressesArr := make([]logicalports.LogicalPortAddress, 0) logicalPortsAddressesArr := make([]logicalports.LogicalPortAddressRequest, 0)
primaryCount := 0
ipMap := make(map[string]bool)
blankMACIPs := make([]string, 0)
for _, logicalPortAddressRaw := range logicalPortAddressesList { for _, logicalPortAddressRaw := range logicalPortAddressesList {
logicalPortAddressMap := logicalPortAddressRaw.(map[string]interface{}) logicalPortAddressMap := logicalPortAddressRaw.(map[string]interface{})
logicalPortAddress := logicalports.LogicalPortAddress{ ip := logicalPortAddressMap["ip"].(string)
IP: logicalPortAddressMap["ip"].(string), isPrimary := logicalPortAddressMap["is_primary"].(bool)
IPType: logicalPortAddressMap["ip_type"].(string),
IsPrimary: logicalPortAddressMap["is_primary"].(bool), if isPrimary {
primaryCount++
if primaryCount > 1 {
return diag.Errorf("logical_port_addresses: only one address can be primary (is_primary = true)")
}
} }
if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok {
logicalPortAddress.IsDiscovered = isDiscovered.(bool) if ipMap[ip] {
return diag.Errorf("logical_port_addresses: duplicate IP address %s is not allowed", ip)
} }
if mac, ok := logicalPortAddressMap["mac"]; ok { ipMap[ip] = true
logicalPortAddress.MAC = mac.(string)
mac := logicalPortAddressMap["mac"].(string)
if mac == "" {
blankMACIPs = append(blankMACIPs, ip)
} }
logicalPortsAddressesArr = append(logicalPortsAddressesArr, logicalPortAddress)
logicalPortsAddressesArr = append(logicalPortsAddressesArr, logicalports.LogicalPortAddressRequest{
IP: ip,
IPType: logicalPortAddressMap["ip_type"].(string),
IsPrimary: isPrimary,
IsDiscovered: logicalPortAddressMap["is_discovered"].(bool),
MAC: mac,
})
} }
if len(blankMACIPs) > 0 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "logical_port_addresses: mac not set, will be auto-generated",
Detail: fmt.Sprintf(
"MAC is not set for the following addresses, the platform will auto-generate it for each: %s. "+
"Every subsequent \"terraform plan\" will show these addresses as changed (a harmless no-op diff) until mac is set explicitly.",
strings.Join(blankMACIPs, ", "),
),
})
}
req.LogicalPortAddresses = logicalPortsAddressesArr req.LogicalPortAddresses = logicalPortsAddressesArr
} }
logicalPort, err := c.SDN().LogicalPorts().Create(ctx, req) logicalPort, err := c.SDN().LogicalPorts().Create(ctx, req)
if err != nil { if err != nil {
d.SetId("") d.SetId("")
return diag.FromErr(err) return append(diags, diag.FromErr(err)...)
} }
d.SetId(logicalPort.ID) d.SetId(logicalPort.ID)
return resourceLogicalPortRead(ctx, d, m) return append(diags, resourceLogicalPortRead(ctx, d, m)...)
} }
func resourceLogicalPortRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { func resourceLogicalPortRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
@@ -95,6 +131,12 @@ func resourceLogicalPortUpdate(ctx context.Context, d *schema.ResourceData, m in
migrate := d.Get("migrate").(bool) migrate := d.Get("migrate").(bool)
if _, ok := d.GetOk("adapter_mac"); !ok {
return diag.Errorf("the adapter_mac field is required when updating")
}
var diags diag.Diagnostics
req := logicalports.UpdateRequest{ req := logicalports.UpdateRequest{
LogicalPortID: d.Id(), LogicalPortID: d.Id(),
VersionID: uint64(d.Get("version_id").(int)), VersionID: uint64(d.Get("version_id").(int)),
@@ -106,132 +148,167 @@ func resourceLogicalPortUpdate(ctx context.Context, d *schema.ResourceData, m in
SegmentID: d.Get("segment_id").(string), SegmentID: d.Get("segment_id").(string),
} }
if !migrate { hvChanged := d.HasChange("hypervisor")
req.Hypervisor = d.Get("hypervisor").(string) if migrate && hvChanged {
} else {
old, _ := d.GetChange("hypervisor") old, _ := d.GetChange("hypervisor")
req.Hypervisor = old.(string) req.Hypervisor = old.(string)
} else {
req.Hypervisor = d.Get("hypervisor").(string)
if hvChanged {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "hypervisor changed without migrate=true",
Detail: "hypervisor was changed but flag \"migrate\" is false, this change is being sent via the regular " +
"update endpoint instead of a live migration. If you intended to live migrate this logical port, " +
"set migrate = true.",
})
}
} }
if d.HasChange("labels") { if labelsRaw, ok := d.GetOk("labels"); ok {
if labelsRaw, ok := d.GetOk("labels"); ok { labelsList := labelsRaw.([]interface{})
labelsList := labelsRaw.([]interface{}) if len(labelsList) > 0 {
if len(labelsList) > 0 { labelsMap := labelsList[0].(map[string]interface{})
labelsMap := labelsList[0].(map[string]interface{}) req.Labels = &logicalports.UpdateLabels{
req.Labels = logicalports.UpdateLabels{ VMID: labelsMap["vm_id"].(string),
VMID: labelsMap["vm_id"].(string), VMName: labelsMap["vm_name"].(string),
VMName: labelsMap["vm_name"].(string),
}
} }
} }
} }
if d.HasChange("logical_port_addresses") { if d.HasChange("logical_port_addresses") {
oldAddresses, newAddresses := d.GetChange("logical_port_addresses") oldAddresses, newAddresses := d.GetChange("logical_port_addresses")
oldAddressesList := oldAddresses.([]interface{})
newAddressesList := newAddresses.([]interface{}) oldAddressesList := oldAddresses.(*schema.Set).List()
oldAddressesMap := make(map[string]logicalports.LogicalPortAddress) oldAddressesMap := make(map[string]logicalports.LogicalPortAddress, len(oldAddressesList))
for _, oldAddressRaw := range oldAddressesList { for _, oldAddressRaw := range oldAddressesList {
logicalPortAddressMap := oldAddressRaw.(map[string]interface{}) logicalPortAddressMap := oldAddressRaw.(map[string]interface{})
logicalPortAddress := logicalports.LogicalPortAddress{ logicalPortAddress := logicalports.LogicalPortAddress{
IP: logicalPortAddressMap["ip"].(string), ID: logicalPortAddressMap["id"].(string),
IPType: logicalPortAddressMap["ip_type"].(string), IP: logicalPortAddressMap["ip"].(string),
IsPrimary: logicalPortAddressMap["is_primary"].(bool), IPType: logicalPortAddressMap["ip_type"].(string),
} IsPrimary: logicalPortAddressMap["is_primary"].(bool),
if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok { IsDiscovered: logicalPortAddressMap["is_discovered"].(bool),
logicalPortAddress.IsDiscovered = isDiscovered.(bool) MAC: logicalPortAddressMap["mac"].(string),
}
if mac, ok := logicalPortAddressMap["mac"]; ok {
logicalPortAddress.MAC = mac.(string)
} }
oldAddressesMap[logicalPortAddress.IP] = logicalPortAddress oldAddressesMap[logicalPortAddress.IP] = logicalPortAddress
} }
newAddressesMap := make(map[string]logicalports.LogicalPortAddress)
newAddressesList := newAddresses.(*schema.Set).List()
newAddressesMap := make(map[string]logicalports.LogicalPortAddress, len(newAddressesList))
primaryCount := 0
for _, newAddressRaw := range newAddressesList { for _, newAddressRaw := range newAddressesList {
logicalPortAddressMap := newAddressRaw.(map[string]interface{}) logicalPortAddressMap := newAddressRaw.(map[string]interface{})
logicalPortAddress := logicalports.LogicalPortAddress{ logicalPortAddress := logicalports.LogicalPortAddress{
IP: logicalPortAddressMap["ip"].(string), ID: logicalPortAddressMap["id"].(string),
IPType: logicalPortAddressMap["ip_type"].(string), IP: logicalPortAddressMap["ip"].(string),
IsPrimary: logicalPortAddressMap["is_primary"].(bool), IPType: logicalPortAddressMap["ip_type"].(string),
IsPrimary: logicalPortAddressMap["is_primary"].(bool),
IsDiscovered: logicalPortAddressMap["is_discovered"].(bool),
MAC: logicalPortAddressMap["mac"].(string),
} }
if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok { if logicalPortAddress.IsPrimary {
logicalPortAddress.IsDiscovered = isDiscovered.(bool) primaryCount++
if primaryCount > 1 {
return append(diags, diag.Errorf("logical_port_addresses: only one address can be primary (is_primary = true)")...)
}
} }
if mac, ok := logicalPortAddressMap["mac"]; ok { if logicalPortAddress.IP != "" {
logicalPortAddress.MAC = mac.(string) if bar, ok := newAddressesMap[logicalPortAddress.IP]; ok {
return append(diags, diag.Errorf("it is impossible to create ports with the same IP %s", bar.IP)...)
}
} }
newAddressesMap[logicalPortAddress.IP] = logicalPortAddress newAddressesMap[logicalPortAddress.IP] = logicalPortAddress
} }
removeAddresses := make([]string, 0) removeAddresses := make([]string, 0)
for addressIP := range oldAddressesMap { for addressIP, lp := range oldAddressesMap {
if _, exists := newAddressesMap[addressIP]; !exists { if _, exists := newAddressesMap[addressIP]; !exists {
removeAddresses = append(removeAddresses, addressIP) removeAddresses = append(removeAddresses, lp.ID)
} }
} }
req.RemoveAddresses = removeAddresses req.RemoveAddresses = removeAddresses
for addressIP, address := range newAddressesMap { for addressIP, address := range newAddressesMap {
if oldAddressIP, exists := oldAddressesMap[addressIP]; !exists || address != oldAddressIP { oldAddress, exists := oldAddressesMap[addressIP]
if !exists {
logicalPortAddress := logicalports.AddAddress{
IP: address.IP,
IPType: address.IPType,
IsPrimary: address.IsPrimary,
IsDiscovered: address.IsDiscovered,
MAC: address.MAC,
}
req.AddAddresses = append(req.AddAddresses, logicalPortAddress)
} else if address != oldAddressIP {
logicalPortAddress := logicalports.UpdateAddress{
IP: address.IP,
IPType: address.IPType,
IsPrimary: address.IsPrimary,
IsDiscovered: address.IsDiscovered,
MAC: address.MAC,
}
req.UpdateAddresses = append(req.UpdateAddresses, logicalPortAddress)
}
}
}
// mac field fallback
currMac := address.MAC
if exists && currMac == "" {
currMac = oldAddress.MAC
}
if !exists {
if currMac == "" {
return append(diags, diag.Errorf("logical_port_addresses: mac is required when adding a new address to an existing logical port (ip=%s)", address.IP)...)
}
req.AddAddresses = append(req.AddAddresses, logicalports.AddAddress{
IP: address.IP,
IPType: address.IPType,
IsPrimary: address.IsPrimary,
IsDiscovered: address.IsDiscovered,
MAC: currMac,
})
continue
}
// compare without compute fields: id, logical_port_id, assigned_at
changed := currMac != oldAddress.MAC ||
address.IPType != oldAddress.IPType ||
address.IsPrimary != oldAddress.IsPrimary ||
address.IsDiscovered != oldAddress.IsDiscovered
if !changed {
continue
}
if oldAddress.ID == "" {
return append(diags, diag.Errorf("logical_port_addresses: internal error, missing platform id for existing address (ip=%s)", address.IP)...)
}
req.UpdateAddresses = append(req.UpdateAddresses, logicalports.UpdateAddress{
ID: oldAddress.ID,
IP: address.IP,
IPType: address.IPType,
IsPrimary: address.IsPrimary,
IsDiscovered: address.IsDiscovered,
MAC: currMac,
})
}
} }
_, err := c.SDN().LogicalPorts().Update(ctx, req) _, err := c.SDN().LogicalPorts().Update(ctx, req)
if err != nil { if err != nil {
d.SetId("") return append(diags, diag.FromErr(err)...)
return diag.FromErr(err)
} }
if migrate { if migrate && hvChanged {
if targetHV, ok := d.GetOk("hypervisor"); ok { targetHV := d.Get("hypervisor").(string)
// Re-read version_id
lp, err := utilityLogicalPortCheckPresence(ctx, d, m)
if err != nil {
return diag.FromErr(err)
}
migrateReq := logicalports.MigrateStartRequest{ // re-read version_id
LogicalPortID: d.Id(), lp, err := utilityLogicalPortCheckPresence(ctx, d, m)
VersionID: lp.VersionID, if err != nil {
TargetHypervisor: targetHV.(string), return append(diags, diag.FromErr(err)...)
}
_, err = c.SDN().LogicalPorts().StartMigrate(ctx, migrateReq)
if err != nil {
return diag.FromErr(err)
}
// to prevent drift on the next plan
diags := resourceLogicalPortRead(ctx, d, m)
if diags.HasError() {
return diags
}
d.Set("hypervisor", targetHV.(string))
return diags
} }
migrateReq := logicalports.MigrateStartRequest{
LogicalPortID: d.Id(),
VersionID: lp.VersionID,
TargetHypervisor: targetHV,
}
_, err = c.SDN().LogicalPorts().StartMigrate(ctx, migrateReq)
if err != nil {
return append(diags, diag.FromErr(err)...)
}
// to prevent drift on the next plan
readDiags := resourceLogicalPortRead(ctx, d, m)
if readDiags.HasError() {
return append(diags, readDiags...)
}
d.Set("hypervisor", targetHV)
return append(diags, readDiags...)
} }
return resourceLogicalPortRead(ctx, d, m) return append(diags, resourceLogicalPortRead(ctx, d, m)...)
} }
func resourceLogicalPortDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { func resourceLogicalPortDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
@@ -259,7 +336,7 @@ func resourceLogicalPortDelete(ctx context.Context, d *schema.ResourceData, m in
func ResourceLogicalPort() *schema.Resource { func ResourceLogicalPort() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
SchemaVersion: 1, SchemaVersion: 2,
CreateContext: resourceLogicalPortCreate, CreateContext: resourceLogicalPortCreate,
ReadContext: resourceLogicalPortRead, ReadContext: resourceLogicalPortRead,
@@ -279,5 +356,12 @@ func ResourceLogicalPort() *schema.Resource {
}, },
Schema: resourceLogicalPortSchemaMake(), Schema: resourceLogicalPortSchemaMake(),
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceLogicalPortResourceV1().CoreConfigSchema().ImpliedType(),
Upgrade: resourceLogicalPortUpgradeV1,
Version: 1,
},
},
} }
} }

View File

@@ -48,12 +48,38 @@ func logicalPortAddressesSchema() *schema.Schema {
} }
} }
func excludeFirewallSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"exclusion_reason": {
Type: schema.TypeString,
Computed: true,
Description: "Reason for the firewall exclusion",
},
"logical_port_addresses_excluded": {
Type: schema.TypeBool,
Computed: true,
Description: "Whether logical port addresses are excluded from the firewall",
},
"logical_port_excluded": {
Type: schema.TypeBool,
Computed: true,
Description: "Whether the logical port is excluded from the firewall",
},
},
},
}
}
func resourceLogicalPortSchemaMake() map[string]*schema.Schema { func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
res := map[string]*schema.Schema{ res := map[string]*schema.Schema{
"access_group_id": { "access_group_id": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Access Group ID", Description: "UUID of the access group",
}, },
"description": { "description": {
Type: schema.TypeString, Type: schema.TypeString,
@@ -73,7 +99,7 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
"hypervisor": { "hypervisor": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Hypervisor", Description: "Associated hypervisor",
}, },
"port_security": { "port_security": {
Type: schema.TypeBool, Type: schema.TypeBool,
@@ -83,7 +109,7 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
"segment_id": { "segment_id": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Segment ID", Description: "UUID of the network segment",
}, },
"adapter_mac": { "adapter_mac": {
Type: schema.TypeString, Type: schema.TypeString,
@@ -94,6 +120,7 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
"unique_identifier": { "unique_identifier": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
Description: "Unique identifier of the logical port", Description: "Unique identifier of the logical port",
}, },
"force": { "force": {
@@ -105,26 +132,29 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Default: false, Default: false,
Description: "If true, triggers live migration to the hypervisor specified in the 'hypervisor' field. If false, hypervisor changes are applied via the regular update endpoint.", Description: "If true, triggers live migration to the hypervisor specified in the 'hypervisor' field. If false, hypervisor changes are applied via the regular update endpoint",
}, },
"logical_port_addresses": { "logical_port_addresses": {
Type: schema.TypeList, Type: schema.TypeSet,
Optional: true, Optional: true,
Set: hashLogicalPortAddress,
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"ip_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"IPv4", "IPv6"}, false),
Description: "IP type",
},
"ip": { "ip": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "IP address of the logical port", Description: "IP address of the logical port",
}, },
"ip_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"IPv4", "IPv6"}, false),
},
"is_primary": { "is_primary": {
Type: schema.TypeBool, Type: schema.TypeBool,
Required: true, Optional: true,
Default: false,
}, },
"is_discovered": { "is_discovered": {
Type: schema.TypeBool, Type: schema.TypeBool,
@@ -132,8 +162,26 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
Default: false, Default: false,
}, },
"mac": { "mac": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
ValidateFunc: validation.IsMACAddress,
Description: "MAC address; optional on initial port creation & required when adding or updating addresses in an existing logical port",
},
"id": {
Type: schema.TypeString,
Computed: true,
Description: "ID of the address, assigned by the platform",
},
"logical_port_id": {
Type: schema.TypeString,
Computed: true,
Description: "ID of the parent logical port",
},
"assigned_at": {
Type: schema.TypeString,
Computed: true,
Description: "Timestamp when the address was assigned",
}, },
}, },
}, },
@@ -156,6 +204,11 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "Last update time of the logical port",
},
"external_network_id": { "external_network_id": {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
@@ -168,6 +221,7 @@ func resourceLogicalPortSchemaMake() map[string]*schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"exclude_firewall": excludeFirewallSchema(),
"status": { "status": {
Type: schema.TypeList, Type: schema.TypeList,
Computed: true, Computed: true,

View File

@@ -0,0 +1,13 @@
package logicalports
import (
"context"
log "github.com/sirupsen/logrus"
)
// "logical_port_addresses" from TypeList to TypeSet
func resourceLogicalPortUpgradeV1(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
log.Debug("resourceLogicalPortUpgradeV1: upgrading state")
return rawState, nil
}

View File

@@ -171,7 +171,7 @@ resource "decort_cb_extnet" "new_extnet" {
#опциональный параметр #опциональный параметр
#тип - блок, #тип - блок,
#тип вложенных полей - целое число #тип вложенных полей - целое число
#используется при обновлении #используется при создании и обновлении
#default_qos { #default_qos {
#ограничение внутреннего трафика в кбит #ограничение внутреннего трафика в кбит
#опциональный параметр #опциональный параметр

View File

@@ -75,12 +75,14 @@ resource "decort_cb_k8ci" "k8ci" {
#доступность k8ci #доступность k8ci
#опциональный параметр #опциональный параметр
#по умолчанию - true
#тип - булев #тип - булев
#используется при создании и обновлении #используется при создании и обновлении
#enabled = false #enabled = false
#флаг для удаления k8ci, без возможности восстановления #флаг для удаления k8ci, без возможности восстановления
#опциональный параметр #опциональный параметр
#по умолчанию - false
#тип - булев #тип - булев
#используется при удалении #используется при удалении
#permanently = true #permanently = true

View File

@@ -21,6 +21,7 @@ terraform {
} }
} }
*/ */
provider "decort" { provider "decort" {
authenticator = "decs3o" authenticator = "decs3o"
#controller_url = <DECORT_CONTROLLER_URL> #controller_url = <DECORT_CONTROLLER_URL>
@@ -31,7 +32,7 @@ provider "decort" {
} }
resource "decort_sdn_logical_port" "test" { resource "decort_sdn_logical_port" "test" {
#id группы доступа #uuid группы доступа
#обязательный параметр #обязательный параметр
#тип - строка #тип - строка
#используется при создании #используется при создании
@@ -49,17 +50,18 @@ resource "decort_sdn_logical_port" "test" {
#используется при создании и обновлении #используется при создании и обновлении
display_name = "name" display_name = "name"
#включенение порта #включение порта
#обязательный параметр #обязательный параметр
#тип - булев #тип - булев
#используется при создании и обновлении #используется при создании и обновлении
enabled = true enabled = true
#связанный гипервизор #имя связанного гипервизора
#обязательный параметр #обязательный параметр
#тип - строка #тип - строка
#для миграции на другой гипервизор необходимо выставить флаг migrate = true
#используется при создании и обновлении #используется при создании и обновлении
hypervisor = "hypervisor" hypervisor = "hypervisor-name"
#статус безопасности порта #статус безопасности порта
#обязательный параметр #обязательный параметр
@@ -67,56 +69,58 @@ resource "decort_sdn_logical_port" "test" {
#используется при создании и обновлении #используется при создании и обновлении
port_security = true port_security = true
#id сегмента сети #uuid сегмента сети
#обязательный параметр #обязательный параметр
#тип - строка #тип - строка
#используется при создании и обновлении #используется при создании и обновлении
segment_id = "a866059a-1900-4f7f-89c1-0f98880f5f4c" segment_id = "a866059a-1900-4f7f-89c1-0f98880f5f4c"
#MAC адрес адаптера #MAC адрес адаптера
#опциональный параметр #опциональный параметр при создании
#тип - строка
#используется при создании и обновлении #используется при создании и обновлении
#adapter_mac = "^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$ default: null" #adapter_mac = "52:54:00:12:34:56"
#уникальный идентификатор порта #uuid порта
#опциональный параметр #опциональный параметр
#тип - строка #тип - строка
#используется при создании #используется при создании
#unique_identifier = "b2c3d4e5-f6g7-8901-h2i3-j4k5l6m7n8o9" #unique_identifier = "b2c3d4e5-f6g7-8901-h2i3-j4k5l6m7n8o9"
#управление списком адресов логических портов #список ip-адресов логического порта
#опциональный параметр #опциональный параметр
#тип - блок #тип - множество блоков
#используется при создании и обновлении #используется при создании и обновлении
#logical_port_addresses { #logical_port_addresses {
#ip #ip адрес
#обязательный параметр #обязательный параметр
#тип - строка #тип - строка
#ip = "192.168.0.1" #используется при создании и обновлении
#ip = "192.168.1.14"
#тип ip #тип ip
#обязательный параметр #обязательный параметр
#тип - строка #тип - строка
#возможные значения - IPv4, IPv6 #допустимые значения: "IPv4", "IPv6"
#ip_type = IPv4" #ip_type = "IPv4"
#обнаружен/не обнаружен #является ли адрес основным (primary) для порта
#опциональный параметр #опциональный параметр
#тип - булев #по умолчанию: false
#значение по умолчанию - false #среди всех блоков logical_port_addresses максимум один может иметь is_primary = true
#is_discovered = false #тип - булев
#is_primary = true
#является ли основным #был ли адрес обнаружен автоматически
#обязательный параметр #опциональный параметр
#тип - булев #по умолчанию: false
#значение по умолчанию - true #тип - булев
#is_primary = true #is_discovered = false
#MAC адрес #MAC адрес, привязанный к ip-адресу
#опциональный параметр #опциональный параметр при создании порта (сервер сгенерирует, если не указан)
#тип - строка #обязателен, если адрес добавляется на уже существующий порт
#mac = "^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$" #тип - строка
#mac = "aa:bb:cc:dd:ee:fd"
#} #}
#метки #метки
@@ -127,25 +131,26 @@ resource "decort_sdn_logical_port" "test" {
# #идентификатор виртуальной машины # #идентификатор виртуальной машины
# #опциональный параметр # #опциональный параметр
# #тип - строка # #тип - строка
# #vm_id = "vmid-420" # vm_id = "vmid-420"
# #имя виртуальной машины # #имя виртуальной машины
# #опциональный параметр # #опциональный параметр
# #тип - строка # #тип - строка
# #vm_name = "my-vm" # vm_name = "my-vm"
#} #}
#флаг инициации миграции на гипервизор из поля hypervisor #флаг инициации миграции на гипервизор из поля hypervisor
#опциональный параметр #опциональный параметр
#тип - булев #тип - булев
#значение по умолчанию - false #значение по умолчанию - false
#используется при обновлении #используется только при обновлении
#migrate = true #migrate = true
#флаг принудительного удаления #флаг принудительного удаления
#опциональный параметр #опциональный параметр
#тип - булев #тип - булев
#используется при обновлении #значение по умолчанию - false
#используется только при удалении
#force = true #force = true
} }