mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-06 14:03:58 +00:00
Merge 48bb57f0c7
into 221faafc8c
This commit is contained in:
commit
7c9c08f0ab
41
docs/data-sources/virtual_environment_sdn_subnet.md
Normal file
41
docs/data-sources/virtual_environment_sdn_subnet.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_subnet
|
||||||
|
parent: Data Sources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Retrieve details about a specific SDN Subnet in Proxmox VE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Data Source: proxmox_virtual_environment_sdn_subnet
|
||||||
|
|
||||||
|
Retrieve details about a specific SDN Subnet in Proxmox VE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `subnet` (String)
|
||||||
|
- `vnet` (String) The VNet this subnet belongs to.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `canonical_name` (String)
|
||||||
|
- `dhcp_dns_server` (String) The DNS server used for DHCP.
|
||||||
|
- `dhcp_range` (Attributes List) List of DHCP ranges (start and end IPs). (see [below for nested schema](#nestedatt--dhcp_range))
|
||||||
|
- `dnszoneprefix` (String) Prefix used for DNS zone delegation.
|
||||||
|
- `gateway` (String) The gateway address for the subnet.
|
||||||
|
- `id` (String) The full ID in the format 'vnet-id/subnet-id'.
|
||||||
|
- `snat` (Boolean) Whether SNAT is enabled for the subnet.
|
||||||
|
- `type` (String)
|
||||||
|
|
||||||
|
<a id="nestedatt--dhcp_range"></a>
|
||||||
|
### Nested Schema for `dhcp_range`
|
||||||
|
|
||||||
|
Read-Only:
|
||||||
|
|
||||||
|
- `end_address` (String) End of the DHCP range.
|
||||||
|
- `start_address` (String) Start of the DHCP range.
|
32
docs/data-sources/virtual_environment_sdn_vnet.md
Normal file
32
docs/data-sources/virtual_environment_sdn_vnet.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_vnet
|
||||||
|
parent: Data Sources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Retrieves information about an existing SDN Vnet in Proxmox VE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Data Source: proxmox_virtual_environment_sdn_vnet
|
||||||
|
|
||||||
|
Retrieves information about an existing SDN Vnet in Proxmox VE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) The name of the vnet.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `alias` (String) - An alias for this vnet.
|
||||||
|
- `id` (String) - The ID of the vnet (usually the name).
|
||||||
|
- `isolate_ports` (Boolean) - Whether ports are isolated.
|
||||||
|
- `tag` (Number) - VLAN/VXLAN tag.
|
||||||
|
- `type` (String) - Type of the vnet.
|
||||||
|
- `vlanaware` (Boolean) - Whether this vnet is VLAN aware.
|
||||||
|
- `zone` (String) - The zone associated with the vnet.
|
||||||
|
- `zonetype` (String) - The type of the zone associated with this vnet.
|
45
docs/data-sources/virtual_environment_sdn_zone.md
Normal file
45
docs/data-sources/virtual_environment_sdn_zone.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_zone
|
||||||
|
parent: Data Sources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Fetch a Proxmox SDN Zone by name.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Data Source: proxmox_virtual_environment_sdn_zone
|
||||||
|
|
||||||
|
|
||||||
|
This data source allows you to fetch information about an existing SDN zone in a Proxmox Virtual Environment (PVE) cluster by its name.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) Name (ID) of the SDN zone.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `advertise_subnets` (Boolean) - Whether to advertise subnets to the zone.
|
||||||
|
- `bridge` (String) – Linux bridge device used (if applicable).
|
||||||
|
- `controller` (String) – Controller for EVPN zones.
|
||||||
|
- `disable_arp_nd_suppression` (Boolean) – Whether ARP/ND suppression is disabled.
|
||||||
|
- `dns` (String) – DNS server configured for the zone.
|
||||||
|
- `dns_zone` (String) – The DNS zone name used by this SDN zone.
|
||||||
|
- `exit_nodes` (String) – Nodes designated as exit points.
|
||||||
|
- `exit_nodes_local_routing` (Boolean) – Whether local routing is enabled for exit nodes.
|
||||||
|
- `id` (String) - The ID of the SDN zone.
|
||||||
|
- `ipam` (String) – The IP Address Management (IPAM) method used in the zone.
|
||||||
|
- `mtu` (Number) – Maximum Transmission Unit for this zone.
|
||||||
|
- `nodes` (String) – Comma-separated list of node names associated with the zone.
|
||||||
|
- `peers` (String) – Peers used for some zone types only.
|
||||||
|
- `primary_exit_node` (String) – The main exit node.
|
||||||
|
- `reversedns` (String) – Reverse DNS server for the zone.
|
||||||
|
- `rt_import` (String) – Route targets to import.
|
||||||
|
- `tag` (Number) – VLAN tag or other numeric identifier.
|
||||||
|
- `type` (String) – The SDN zone type (e.g., `simple`, `vlan`, `vxlan`, `evpn`).
|
||||||
|
- `vlan_protocol` (String) – VLAN protocol used.
|
||||||
|
- `vrf_vxlan` (Number) – VXLAN ID associated with VRF zones.
|
44
docs/resources/virtual_environment_sdn_subnet.md
Normal file
44
docs/resources/virtual_environment_sdn_subnet.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_subnet
|
||||||
|
parent: Resources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Manages SDN Subnets in Proxmox VE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Resource: proxmox_virtual_environment_sdn_subnet
|
||||||
|
|
||||||
|
Manages SDN Subnets in Proxmox VE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `subnet` (String) The name/ID of the subnet.
|
||||||
|
- `vnet` (String) The VNet to which this subnet belongs.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `dhcp_dns_server` (String) The DNS server used for DHCP.
|
||||||
|
- `dhcp_range` (Attributes List) List of DHCP ranges (start and end IPs). (see [below for nested schema](#nestedatt--dhcp_range))
|
||||||
|
- `dnszoneprefix` (String) Prefix used for DNS zone delegation.
|
||||||
|
- `gateway` (String) The gateway address for the subnet.
|
||||||
|
- `snat` (Boolean) Whether SNAT is enabled for the subnet.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `canonical_name` (String) Canonical name of the subnet (e.g. zoneM-10.10.0.0-24).
|
||||||
|
- `id` (String) The unique identifier of this resource.
|
||||||
|
- `type` (String) Subnet type (set default at 'subnet')
|
||||||
|
|
||||||
|
<a id="nestedatt--dhcp_range"></a>
|
||||||
|
### Nested Schema for `dhcp_range`
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- `end_address` (String) End of the DHCP range.
|
||||||
|
- `start_address` (String) Start of the DHCP range.
|
35
docs/resources/virtual_environment_sdn_vnet.md
Normal file
35
docs/resources/virtual_environment_sdn_vnet.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_vnet
|
||||||
|
parent: Resources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Manages Proxmox VE SDN vnet.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Resource: proxmox_virtual_environment_sdn_vnet
|
||||||
|
|
||||||
|
Manages Proxmox VE SDN vnet.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) Unique identifier for the vnet.
|
||||||
|
- `zone` (String) The zone to which this vnet belongs.
|
||||||
|
- `zonetype` (String) Parent's zone type. MUST be specified.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `alias` (String) An optional alias for this vnet.
|
||||||
|
- `isolate_ports` (Boolean) Whether to isolate ports within this vnet.
|
||||||
|
- `tag` (Number) Tag value for VLAN/VXLAN (depends on zone type).
|
||||||
|
- `vlanaware` (Boolean) Whether this vnet is VLAN aware.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `id` (String) The unique identifier of this resource.
|
||||||
|
- `type` (String) Type of vnet (e.g. 'vnet').
|
60
docs/resources/virtual_environment_sdn_zone.md
Normal file
60
docs/resources/virtual_environment_sdn_zone.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: proxmox_virtual_environment_sdn_zone
|
||||||
|
parent: Resources
|
||||||
|
subcategory: Virtual Environment
|
||||||
|
description: |-
|
||||||
|
Manages SDN Zones in Proxmox VE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Resource: proxmox_virtual_environment_sdn_zone
|
||||||
|
|
||||||
|
Manages SDN Zones in Proxmox VE.
|
||||||
|
Some attributes in the `proxmox_virtual_environment_sdn_zone` resource or data source are only applicable to certain zone types. For example:
|
||||||
|
|
||||||
|
`bridge` is relevant only for `vlan` zones.
|
||||||
|
|
||||||
|
`peers`, `controller`, `vrf_vxlan`, and related attributes are specific to `vxlan` and `evpn` zone types.
|
||||||
|
|
||||||
|
`service_vlan` and `vlan_protocol` apply to `qinq` zones.
|
||||||
|
|
||||||
|
While the Proxmox API does not explicitly document these constraints, they are enforced by the Proxmox backend and have been validated manually through API experimentation.
|
||||||
|
|
||||||
|
The Terraform provider implements field-level validation to ensure that only compatible attributes are used with each zone type. If incompatible attributes are set, Terraform will raise a configuration error during plan or apply to prevent invalid requests to the Proxmox API.
|
||||||
|
|
||||||
|
This design helps ensure correctness and avoids unexpected API failures when managing SDN zones across different zone types.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) The unique ID of the SDN zone.
|
||||||
|
- `type` (String) Zone type (e.g. simple, vlan, qinq, vxlan, evpn).
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `advertise_subnets` (Boolean) Enable subnet advertisement for EVPN.
|
||||||
|
- `bridge` (String) Bridge interface for VLAN/QinQ.
|
||||||
|
- `controller` (String) EVPN controller address.
|
||||||
|
- `disable_arp_nd_suppression` (Boolean) Disable ARP/ND suppression for EVPN.
|
||||||
|
- `dns` (String) DNS server address.
|
||||||
|
- `dns_zone` (String) DNS zone name.
|
||||||
|
- `exit_nodes` (String) Comma-separated list of exit nodes for EVPN.
|
||||||
|
- `exit_nodes_local_routing` (Boolean) Enable local routing for EVPN exit nodes.
|
||||||
|
- `ipam` (String) IP Address Management system.
|
||||||
|
- `mtu` (Number) MTU value for the zone.
|
||||||
|
- `nodes` (String) Comma-separated list of Proxmox node names.
|
||||||
|
- `peers` (String) Peers list for VXLAN.
|
||||||
|
- `primary_exit_node` (String) Primary exit node for EVPN.
|
||||||
|
- `reversedns` (String) Reverse DNS settings.
|
||||||
|
- `rt_import` (String) Route target import for EVPN.
|
||||||
|
- `tag` (Number) Service VLAN tag for QinQ.
|
||||||
|
- `vlan_protocol` (String) Service VLAN protocol for QinQ.
|
||||||
|
- `vrf_vxlan` (Number) EVPN VRF VXLAN ID.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `id` (String) The unique identifier of this resource.
|
@ -4,13 +4,13 @@ resource "proxmox_virtual_environment_container" "example_template" {
|
|||||||
start_on_boot = "true"
|
start_on_boot = "true"
|
||||||
|
|
||||||
disk {
|
disk {
|
||||||
datastore_id = "local-lvm"
|
datastore_id = var.virtual_environment_storage
|
||||||
size = 4
|
size = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
mount_point {
|
mount_point {
|
||||||
// volume mount
|
// volume mount
|
||||||
volume = "local-lvm"
|
volume = var.virtual_environment_storage
|
||||||
size = "4G"
|
size = "4G"
|
||||||
path = "mnt/local"
|
path = "mnt/local"
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ resource "proxmox_virtual_environment_container" "example_template" {
|
|||||||
|
|
||||||
resource "proxmox_virtual_environment_container" "example" {
|
resource "proxmox_virtual_environment_container" "example" {
|
||||||
disk {
|
disk {
|
||||||
datastore_id = "local-lvm"
|
datastore_id = var.virtual_environment_storage
|
||||||
}
|
}
|
||||||
|
|
||||||
clone {
|
clone {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
resource "proxmox_virtual_environment_download_file" "release_20240725_ubuntu_24_noble_lxc_img" {
|
resource "proxmox_virtual_environment_download_file" "release_20240725_ubuntu_24_noble_lxc_img" {
|
||||||
content_type = "vztmpl"
|
content_type = "vztmpl"
|
||||||
datastore_id = "local"
|
datastore_id = "local"
|
||||||
node_name = "pve"
|
node_name = var.virtual_environment_node_name
|
||||||
url = var.release_20240725_ubuntu_24_noble_lxc_img_url
|
url = var.release_20240725_ubuntu_24_noble_lxc_img_url
|
||||||
checksum = var.release_20240725_ubuntu_24_noble_lxc_img_checksum
|
checksum = var.release_20240725_ubuntu_24_noble_lxc_img_checksum
|
||||||
checksum_algorithm = "sha256"
|
checksum_algorithm = "sha256"
|
||||||
@ -15,7 +15,7 @@ resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_
|
|||||||
content_type = "iso"
|
content_type = "iso"
|
||||||
datastore_id = "local"
|
datastore_id = "local"
|
||||||
file_name = "debian-12-generic-amd64.img"
|
file_name = "debian-12-generic-amd64.img"
|
||||||
node_name = "pve"
|
node_name = var.virtual_environment_node_name
|
||||||
url = var.latest_debian_12_bookworm_qcow2_img_url
|
url = var.latest_debian_12_bookworm_qcow2_img_url
|
||||||
overwrite = true
|
overwrite = true
|
||||||
overwrite_unmanaged = true
|
overwrite_unmanaged = true
|
||||||
|
108
example/resource_virtual_environment_sdn.tf
Normal file
108
example/resource_virtual_environment_sdn.tf
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# --- SDN Zones ---
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone" "zone_simple" {
|
||||||
|
name = "zoneS"
|
||||||
|
type = "simple"
|
||||||
|
nodes = var.virtual_environment_node_name
|
||||||
|
mtu = 1496
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone" "zone_vlan" {
|
||||||
|
name = "zoneVLAN"
|
||||||
|
type = "vlan"
|
||||||
|
nodes = var.virtual_environment_node_name
|
||||||
|
mtu = 1500
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- SDN Vnets ---
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_vnet" "vnet_simple" {
|
||||||
|
name = "vnetM"
|
||||||
|
zone = proxmox_virtual_environment_sdn_zone.zone_simple.name
|
||||||
|
alias = "vnet in zoneM"
|
||||||
|
isolate_ports = "0"
|
||||||
|
vlanaware = "0"
|
||||||
|
zonetype = proxmox_virtual_environment_sdn_zone.zone_simple.type
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_vnet" "vnet_vlan" {
|
||||||
|
name = "vnetVLAN"
|
||||||
|
zone = proxmox_virtual_environment_sdn_zone.zone_vlan.name
|
||||||
|
alias = "vnet in zoneVLAN"
|
||||||
|
tag = 1000
|
||||||
|
zonetype = proxmox_virtual_environment_sdn_zone.zone_vlan.type
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- SDN Subnets ---
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_simple" {
|
||||||
|
subnet = "10.10.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_simple.name
|
||||||
|
dhcp_dns_server = "10.10.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.10.0.10"
|
||||||
|
end_address = "10.10.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.10.0.1"
|
||||||
|
snat = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_simple2" {
|
||||||
|
subnet = "10.40.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_simple.name
|
||||||
|
dhcp_dns_server = "10.40.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.40.0.10"
|
||||||
|
end_address = "10.40.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.40.0.1"
|
||||||
|
snat = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_vlan" {
|
||||||
|
subnet = "10.20.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_vlan.name
|
||||||
|
dhcp_dns_server = "10.20.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.20.0.10"
|
||||||
|
end_address = "10.20.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.20.0.100"
|
||||||
|
snat = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Data Sources ---
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_sdn_zone" "zone_ex" {
|
||||||
|
name = "ZoneEx"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_sdn_vnet" "vnet_ex" {
|
||||||
|
name = "VnetEx"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_sdn_subnet" "subnet_ex" {
|
||||||
|
subnet = "ZoneEx-100.100.0.0-24"
|
||||||
|
vnet = data.proxmox_virtual_environment_sdn_vnet.vnet_ex.id
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Outputs ---
|
||||||
|
|
||||||
|
output "sdn_zone" {
|
||||||
|
value = data.proxmox_virtual_environment_sdn_zone.zone_ex
|
||||||
|
}
|
||||||
|
|
||||||
|
output "sdn_vnet" {
|
||||||
|
value = data.proxmox_virtual_environment_sdn_vnet.vnet_ex
|
||||||
|
}
|
||||||
|
|
||||||
|
output "sdn_subnet" {
|
||||||
|
value = data.proxmox_virtual_environment_sdn_subnet.subnet_ex
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
locals {
|
locals {
|
||||||
datastore_id = "local-lvm"
|
datastore_id = var.virtual_environment_storage
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "proxmox_virtual_environment_vm" "example_template" {
|
resource "proxmox_virtual_environment_vm" "example_template" {
|
||||||
|
@ -13,6 +13,18 @@ variable "virtual_environment_ssh_username" {
|
|||||||
description = "The username for the Proxmox Virtual Environment API"
|
description = "The username for the Proxmox Virtual Environment API"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "virtual_environment_node_name" {
|
||||||
|
description = "Name of the Proxmox node"
|
||||||
|
type = string
|
||||||
|
default = "pve"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "virtual_environment_storage" {
|
||||||
|
description = "Name of the Proxmox storage"
|
||||||
|
type = string
|
||||||
|
default = "local-lvm"
|
||||||
|
}
|
||||||
|
|
||||||
variable "latest_debian_12_bookworm_qcow2_img_url" {
|
variable "latest_debian_12_bookworm_qcow2_img_url" {
|
||||||
type = string
|
type = string
|
||||||
description = "The URL for the latest Debian 12 Bookworm qcow2 image"
|
description = "The URL for the latest Debian 12 Bookworm qcow2 image"
|
||||||
|
152
fwprovider/cluster/sdn/datasource_sdn_subnets.go
Normal file
152
fwprovider/cluster/sdn/datasource_sdn_subnets.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/subnets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ datasource.DataSource = &sdnSubnetDataSource{}
|
||||||
|
|
||||||
|
var _ datasource.DataSourceWithConfigure = &sdnSubnetDataSource{}
|
||||||
|
|
||||||
|
type sdnSubnetDataSource struct {
|
||||||
|
client *subnets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNSubnetDataSource() datasource.DataSource {
|
||||||
|
return &sdnSubnetDataSource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnSubnetDataSource) Metadata(
|
||||||
|
ctx context.Context,
|
||||||
|
req datasource.MetadataRequest,
|
||||||
|
resp *datasource.MetadataResponse,
|
||||||
|
) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_subnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnSubnetDataSource) Configure(
|
||||||
|
ctx context.Context,
|
||||||
|
req datasource.ConfigureRequest,
|
||||||
|
resp *datasource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.DataSource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Provider Configuration",
|
||||||
|
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.client = cfg.Client.Cluster().SDNSubnets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnSubnetDataSource) Schema(
|
||||||
|
ctx context.Context,
|
||||||
|
req datasource.SchemaRequest,
|
||||||
|
resp *datasource.SchemaResponse,
|
||||||
|
) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Retrieve details about a specific SDN Subnet in Proxmox VE.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The full ID in the format 'vnet-id/subnet-id'.",
|
||||||
|
},
|
||||||
|
"subnet": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"canonical_name": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"vnet": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "The VNet this subnet belongs to.",
|
||||||
|
},
|
||||||
|
"dhcp_dns_server": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The DNS server used for DHCP.",
|
||||||
|
},
|
||||||
|
"dhcp_range": schema.ListNestedAttribute{
|
||||||
|
Optional: false,
|
||||||
|
Computed: true,
|
||||||
|
Description: "List of DHCP ranges (start and end IPs).",
|
||||||
|
NestedObject: schema.NestedAttributeObject{
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"start_address": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Start of the DHCP range.",
|
||||||
|
},
|
||||||
|
"end_address": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "End of the DHCP range.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dnszoneprefix": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Prefix used for DNS zone delegation.",
|
||||||
|
},
|
||||||
|
"gateway": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The gateway address for the subnet.",
|
||||||
|
},
|
||||||
|
"snat": schema.BoolAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Whether SNAT is enabled for the subnet.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnSubnetDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||||
|
var config sdnSubnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subnet, err := d.client.GetSubnet(ctx, config.Vnet.ValueString(), config.Subnet.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Subnet not found", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Failed to retrieve subnet", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the state.
|
||||||
|
state := &sdnSubnetModel{}
|
||||||
|
state.Subnet = config.Subnet
|
||||||
|
state.Vnet = config.Vnet
|
||||||
|
state.importFromAPI(config.Subnet.ValueString(), subnet)
|
||||||
|
|
||||||
|
// Set canonical name and ID (both = user-supplied subnet).
|
||||||
|
state.ID = config.Subnet
|
||||||
|
state.CanonicalName = config.Subnet
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
|
||||||
|
}
|
131
fwprovider/cluster/sdn/datasource_sdn_vnets.go
Normal file
131
fwprovider/cluster/sdn/datasource_sdn_vnets.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/vnets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ datasource.DataSource = &sdnVnetDataSource{}
|
||||||
|
|
||||||
|
var _ datasource.DataSourceWithConfigure = &sdnVnetDataSource{}
|
||||||
|
|
||||||
|
type sdnVnetDataSource struct {
|
||||||
|
client *vnets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNVnetDataSource() datasource.DataSource {
|
||||||
|
return &sdnVnetDataSource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnVnetDataSource) Metadata(
|
||||||
|
ctx context.Context,
|
||||||
|
req datasource.MetadataRequest,
|
||||||
|
resp *datasource.MetadataResponse,
|
||||||
|
) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_vnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnVnetDataSource) Configure(
|
||||||
|
ctx context.Context,
|
||||||
|
req datasource.ConfigureRequest,
|
||||||
|
resp *datasource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.DataSource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Provider Data",
|
||||||
|
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.client = cfg.Client.Cluster().SDNVnets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnVnetDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Retrieves information about an existing SDN Vnet in Proxmox VE.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: "The ID of the vnet (usually the name).",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "The name of the vnet.",
|
||||||
|
},
|
||||||
|
"zone": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The zone associated with the vnet.",
|
||||||
|
},
|
||||||
|
"zonetype": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The type of the zone associated with this vnet.",
|
||||||
|
},
|
||||||
|
"alias": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "An alias for this vnet.",
|
||||||
|
},
|
||||||
|
"isolate_ports": schema.BoolAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Whether ports are isolated.",
|
||||||
|
},
|
||||||
|
"tag": schema.Int64Attribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "VLAN/VXLAN tag.",
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Type of the vnet.",
|
||||||
|
},
|
||||||
|
"vlanaware": schema.BoolAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Whether this vnet is VLAN aware.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnVnetDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||||
|
var config sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vnetID := config.Name.ValueString()
|
||||||
|
|
||||||
|
vnet, err := d.client.GetVnet(ctx, vnetID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Vnet not found", fmt.Sprintf("No vnet with ID %q exists", vnetID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Error retrieving vnet", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state := sdnVnetModel{}
|
||||||
|
state.importFromAPI(vnetID, vnet)
|
||||||
|
state.ID = types.StringValue(vnetID)
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||||
|
}
|
109
fwprovider/cluster/sdn/datasource_sdn_zones.go
Normal file
109
fwprovider/cluster/sdn/datasource_sdn_zones.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ datasource.DataSource = &sdnZoneDataSource{}
|
||||||
|
|
||||||
|
var _ datasource.DataSourceWithConfigure = &sdnZoneDataSource{}
|
||||||
|
|
||||||
|
type sdnZoneDataSource struct {
|
||||||
|
client *zones.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNZoneDataSource() datasource.DataSource {
|
||||||
|
return &sdnZoneDataSource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnZoneDataSource) Metadata(
|
||||||
|
_ context.Context,
|
||||||
|
req datasource.MetadataRequest,
|
||||||
|
resp *datasource.MetadataResponse,
|
||||||
|
) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_zone"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnZoneDataSource) Configure(
|
||||||
|
_ context.Context,
|
||||||
|
req datasource.ConfigureRequest,
|
||||||
|
resp *datasource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.DataSource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Provider Configuration",
|
||||||
|
fmt.Sprintf("Expected config.DataSource but got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.client = cfg.Client.Cluster().SDNZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnZoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Fetch a Proxmox SDN Zone by name.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "The ID of the SDN zone.",
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "Name (ID) of the SDN zone.",
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{Computed: true},
|
||||||
|
"ipam": schema.StringAttribute{Computed: true},
|
||||||
|
"dns": schema.StringAttribute{Computed: true},
|
||||||
|
"reversedns": schema.StringAttribute{Computed: true},
|
||||||
|
"dns_zone": schema.StringAttribute{Computed: true},
|
||||||
|
"nodes": schema.StringAttribute{Computed: true},
|
||||||
|
"mtu": schema.Int64Attribute{Computed: true},
|
||||||
|
"bridge": schema.StringAttribute{Computed: true},
|
||||||
|
"tag": schema.Int64Attribute{Computed: true},
|
||||||
|
"vlan_protocol": schema.StringAttribute{Computed: true},
|
||||||
|
"peers": schema.StringAttribute{Computed: true},
|
||||||
|
"controller": schema.StringAttribute{Computed: true},
|
||||||
|
"vrf_vxlan": schema.Int64Attribute{Computed: true},
|
||||||
|
"exit_nodes": schema.StringAttribute{Computed: true},
|
||||||
|
"primary_exit_node": schema.StringAttribute{Computed: true},
|
||||||
|
"exit_nodes_local_routing": schema.BoolAttribute{Computed: true},
|
||||||
|
"advertise_subnets": schema.BoolAttribute{Computed: true},
|
||||||
|
"disable_arp_nd_suppression": schema.BoolAttribute{Computed: true},
|
||||||
|
"rt_import": schema.StringAttribute{Computed: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sdnZoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||||
|
var data sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zone, err := d.client.GetZone(ctx, data.Name.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Unable to fetch SDN Zone", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnZoneModel{}
|
||||||
|
readModel.importFromAPI(zone.ID, zone)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
379
fwprovider/cluster/sdn/resource_sdn_subnets.go
Normal file
379
fwprovider/cluster/sdn/resource_sdn_subnets.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/subnets"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ resource.Resource = &sdnSubnetResource{}
|
||||||
|
_ resource.ResourceWithConfigure = &sdnSubnetResource{}
|
||||||
|
_ resource.ResourceWithImportState = &sdnSubnetResource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnSubnetResource struct {
|
||||||
|
client *subnets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNSubnetResource() resource.Resource {
|
||||||
|
return &sdnSubnetResource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_subnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Configure(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.ConfigureRequest,
|
||||||
|
resp *resource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.Resource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Resource Configure Type",
|
||||||
|
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = cfg.Client.Cluster().SDNSubnets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Manages SDN Subnets in Proxmox VE.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": attribute.ResourceID(),
|
||||||
|
"subnet": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "The name/ID of the subnet.",
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"canonical_name": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Canonical name of the subnet (e.g. zoneM-10.10.0.0-24).",
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Subnet type (set default at 'subnet')",
|
||||||
|
Default: stringdefault.StaticString("subnet"),
|
||||||
|
},
|
||||||
|
"vnet": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "The VNet to which this subnet belongs.",
|
||||||
|
},
|
||||||
|
"dhcp_dns_server": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "The DNS server used for DHCP.",
|
||||||
|
},
|
||||||
|
"dhcp_range": schema.ListNestedAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "List of DHCP ranges (start and end IPs).",
|
||||||
|
NestedObject: schema.NestedAttributeObject{
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"start_address": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "Start of the DHCP range.",
|
||||||
|
},
|
||||||
|
"end_address": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "End of the DHCP range.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dnszoneprefix": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Prefix used for DNS zone delegation.",
|
||||||
|
},
|
||||||
|
"gateway": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "The gateway address for the subnet.",
|
||||||
|
},
|
||||||
|
"snat": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Whether SNAT is enabled for the subnet.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
var plan sdnSubnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if plan.Vnet.IsNull() || plan.Vnet.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("vnet"),
|
||||||
|
"missing required field",
|
||||||
|
"Missing the parent vnet's ID attribute, which is required to define a subnet")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.CreateSubnet(ctx, plan.Vnet.ValueString(), plan.toAPIRequestBody(ctx))
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error creating subnet", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Debug(ctx, "Created object's ID", map[string]any{"plan name:": plan.Subnet})
|
||||||
|
plan.ID = plan.Subnet
|
||||||
|
|
||||||
|
/* Because proxmox API doesn't return the created object's properties and the subnet's name gets modified by
|
||||||
|
proxmox internally.
|
||||||
|
Read it back to get the canonical-ID from proxmox.*/
|
||||||
|
canonicalID, err := resolveCanonicalSubnetID(ctx, r.client, plan.Vnet.ValueString(), plan.Subnet.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error resolving canonical subnet ID", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.ID = types.StringValue(canonicalID)
|
||||||
|
plan.CanonicalName = types.StringValue(canonicalID)
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
var state sdnSubnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subnet, err := r.client.GetSubnet(ctx, state.Vnet.ValueString(), state.ID.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Error reading subnet", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnSubnetModel{}
|
||||||
|
readModel.Subnet = state.Subnet
|
||||||
|
readModel.importFromAPI(state.ID.ValueString(), subnet)
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
var plan sdnSubnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := plan.toAPIRequestBody(ctx)
|
||||||
|
|
||||||
|
if plan.Vnet.IsNull() || plan.Vnet.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("vnet"),
|
||||||
|
"missing required field",
|
||||||
|
"Missing the parent vnet's ID attribute, which is required to define a subnet")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.UpdateSubnet(ctx, plan.Vnet.ValueString(), reqData)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error updating subnet", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
var state sdnSubnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.DeleteSubnet(ctx, state.Vnet.ValueString(), state.ID.ValueString())
|
||||||
|
if err != nil && !errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Error deleting subnet", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnSubnetResource) ImportState(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ImportStateRequest,
|
||||||
|
resp *resource.ImportStateResponse,
|
||||||
|
) {
|
||||||
|
// Expect ID format: "vnet/subnet".
|
||||||
|
parts := strings.Split(req.ID, "/")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Import Identifier",
|
||||||
|
"Expected import identifier in format 'vnet-id/subnet-id'.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vnetID := parts[0]
|
||||||
|
subnetID := parts[1]
|
||||||
|
|
||||||
|
subnet, err := r.client.GetSubnet(ctx, vnetID, subnetID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Subnet does not exist", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Unable to import subnet", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnSubnetModel{}
|
||||||
|
readModel.importFromAPI(req.ID, subnet)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveCanonicalSubnetID(
|
||||||
|
ctx context.Context,
|
||||||
|
client *subnets.Client,
|
||||||
|
vnet string,
|
||||||
|
originalID string,
|
||||||
|
) (string, error) {
|
||||||
|
subnets, err := client.GetSubnets(ctx, vnet)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to list subnets for canonical name resolution: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
if subnet.ID == originalID {
|
||||||
|
return subnet.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxmox canonical format is usually zone-prefixed.
|
||||||
|
// e.g., zoneM-10-10-0-0-24 instead of 10.10.0.0/24.
|
||||||
|
if strings.HasSuffix(subnet.ID, strings.ReplaceAll(originalID, "/", "-")) {
|
||||||
|
return subnet.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not resolve canonical subnet ID for %s", originalID)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ValidateConfig checks that the subnet's field are correctly set.
|
||||||
|
Particularly that gateway, dhcp and dns are within CIDR.
|
||||||
|
*/
|
||||||
|
func (r *sdnSubnetResource) ValidateConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ValidateConfigRequest,
|
||||||
|
resp *resource.ValidateConfigResponse,
|
||||||
|
) {
|
||||||
|
var config sdnSubnetModel
|
||||||
|
diags := req.Config.Get(ctx, &config)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ipnet, err := net.ParseCIDR(config.Subnet.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("subnet"),
|
||||||
|
"Invalid Subnet",
|
||||||
|
fmt.Sprintf("Could not parse subnet: %s", err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIPInCIDR := func(attrName string, ipVal types.String) {
|
||||||
|
if !ipVal.IsNull() {
|
||||||
|
ip := net.ParseIP(ipVal.ValueString())
|
||||||
|
if ip == nil {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root(attrName),
|
||||||
|
"Invalid IP Address",
|
||||||
|
fmt.Sprintf("Could not parse IP address: %s", ipVal.ValueString()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ipnet.Contains(ip) {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root(attrName),
|
||||||
|
"Invalid IP for Subnet",
|
||||||
|
fmt.Sprintf("%s must be within the subnet %s", ipVal.ValueString(), config.Subnet.ValueString()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIPInCIDR("gateway", config.Gateway)
|
||||||
|
checkIPInCIDR("dhcp_dns_server", config.DhcpDnsServer)
|
||||||
|
|
||||||
|
for i, r := range config.DhcpRange {
|
||||||
|
if !r.StartAddress.IsNull() {
|
||||||
|
ip := net.ParseIP(r.StartAddress.ValueString())
|
||||||
|
if !ipnet.Contains(ip) {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("dhcp_range").AtListIndex(i).AtMapKey("start_address"),
|
||||||
|
"Invalid DHCP Range Start Address",
|
||||||
|
fmt.Sprintf("Start address %s must be within the subnet %s", ip, config.Subnet.ValueString()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.EndAddress.IsNull() {
|
||||||
|
ip := net.ParseIP(r.EndAddress.ValueString())
|
||||||
|
if !ipnet.Contains(ip) {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("dhcp_range").AtListIndex(i).AtMapKey("end_address"),
|
||||||
|
"Invalid DHCP Range End Address",
|
||||||
|
fmt.Sprintf("End address %s must be within the subnet %s", ip, config.Subnet.ValueString()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
331
fwprovider/cluster/sdn/resource_sdn_vnets.go
Normal file
331
fwprovider/cluster/sdn/resource_sdn_vnets.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/vnets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ resource.Resource = &sdnVnetResource{}
|
||||||
|
_ resource.ResourceWithConfigure = &sdnVnetResource{}
|
||||||
|
_ resource.ResourceWithImportState = &sdnVnetResource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnVnetResource struct {
|
||||||
|
client *vnets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNVnetResource() resource.Resource {
|
||||||
|
return &sdnVnetResource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Metadata(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.MetadataRequest,
|
||||||
|
resp *resource.MetadataResponse,
|
||||||
|
) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_vnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Configure(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.ConfigureRequest,
|
||||||
|
resp *resource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.Resource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Resource Configure Type",
|
||||||
|
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = cfg.Client.Cluster().SDNVnets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Schema(
|
||||||
|
_ context.Context,
|
||||||
|
_ resource.SchemaRequest,
|
||||||
|
resp *resource.SchemaResponse,
|
||||||
|
) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Manages Proxmox VE SDN vnet.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": attribute.ResourceID(),
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: "Unique identifier for the vnet.",
|
||||||
|
Required: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"zonetype": schema.StringAttribute{
|
||||||
|
Required: true,
|
||||||
|
Description: "Parent's zone type. MUST be specified.",
|
||||||
|
},
|
||||||
|
"zone": schema.StringAttribute{
|
||||||
|
Description: "The zone to which this vnet belongs.",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"alias": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "An optional alias for this vnet.",
|
||||||
|
},
|
||||||
|
"isolate_ports": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Whether to isolate ports within this vnet.",
|
||||||
|
},
|
||||||
|
"tag": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Tag value for VLAN/VXLAN (depends on zone type).",
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{
|
||||||
|
Computed: true,
|
||||||
|
Description: "Type of vnet (e.g. 'vnet').",
|
||||||
|
Default: stringdefault.StaticString("vnet"),
|
||||||
|
},
|
||||||
|
"vlanaware": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Whether this vnet is VLAN aware.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.CreateRequest,
|
||||||
|
resp *resource.CreateResponse,
|
||||||
|
) {
|
||||||
|
var plan sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.CreateVnet(ctx, plan.toAPIRequestBody())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error creating vnet", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.ID = plan.Name
|
||||||
|
tflog.Info(ctx, "ZONETYPE value", map[string]any{"zonetype": plan.ZoneType.ValueString()})
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Read(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ReadRequest,
|
||||||
|
resp *resource.ReadResponse,
|
||||||
|
) {
|
||||||
|
var state sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := r.client.GetVnet(ctx, state.ID.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Error reading vnet", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnVnetModel{}
|
||||||
|
readModel.importFromAPI(state.ID.ValueString(), data)
|
||||||
|
// Preserve provider-only field.
|
||||||
|
readModel.ZoneType = state.ZoneType
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.UpdateRequest,
|
||||||
|
resp *resource.UpdateResponse,
|
||||||
|
) {
|
||||||
|
var plan sdnVnetModel
|
||||||
|
|
||||||
|
var state sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var toDelete []string
|
||||||
|
|
||||||
|
checkDelete(plan.Alias, state.Alias, &toDelete, "alias")
|
||||||
|
checkDelete(plan.IsolatePorts, state.IsolatePorts, &toDelete, "isolate-ports")
|
||||||
|
checkDelete(plan.Tag, state.Tag, &toDelete, "tag")
|
||||||
|
checkDelete(plan.Type, state.Type, &toDelete, "type")
|
||||||
|
checkDelete(plan.VlanAware, state.VlanAware, &toDelete, "vlanaware")
|
||||||
|
|
||||||
|
reqData := plan.toAPIRequestBody()
|
||||||
|
reqData.Delete = toDelete
|
||||||
|
|
||||||
|
err := r.client.UpdateVnet(ctx, reqData)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error updating vnet", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.DeleteRequest,
|
||||||
|
resp *resource.DeleteResponse,
|
||||||
|
) {
|
||||||
|
var state sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.DeleteVnet(ctx, state.ID.ValueString())
|
||||||
|
if err != nil && !errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Error deleting vnet", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) ImportState(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ImportStateRequest,
|
||||||
|
resp *resource.ImportStateResponse,
|
||||||
|
) {
|
||||||
|
data, err := r.client.GetVnet(ctx, req.ID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Resource does not exist", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Failed to import resource", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnVnetModel{}
|
||||||
|
readModel.importFromAPI(req.ID, data)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDelete(planField, stateField attr.Value, toDelete *[]string, apiName string) {
|
||||||
|
if planField.IsNull() && !stateField.IsNull() {
|
||||||
|
*toDelete = append(*toDelete, apiName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnVnetResource) ValidateConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ValidateConfigRequest,
|
||||||
|
resp *resource.ValidateConfigResponse,
|
||||||
|
) {
|
||||||
|
var data sdnVnetModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Zone.IsNull() || data.Zone.IsUnknown() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ZoneType.IsNull() || data.ZoneType.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root("zonetype"),
|
||||||
|
"Missing Required Field",
|
||||||
|
"No Zone linked, please set the 'zonetype' property. \nEither from a created zone or a datasource import.")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneType := data.ZoneType.ValueString()
|
||||||
|
|
||||||
|
required := map[string][]string{
|
||||||
|
"simple": {"name", "zone"},
|
||||||
|
"vlan": {"name", "zone", "tag"},
|
||||||
|
"qinq": {"name", "zone"},
|
||||||
|
"vxlan": {"name", "zone", "tag"},
|
||||||
|
"evpn": {"name", "zone", "tag"},
|
||||||
|
}
|
||||||
|
|
||||||
|
authorized := map[string]map[string]bool{
|
||||||
|
"simple": {"name": true, "alias": true, "zone": true, "isolate_ports": true, "vlanaware": true},
|
||||||
|
"vlan": {"name": true, "alias": true, "zone": true, "tag": true, "isolate_ports": true, "vlanaware": true},
|
||||||
|
"qinq": {"name": true, "alias": true, "zone": true, "tag": true, "isolate_ports": true, "vlanaware": true},
|
||||||
|
"vxlan": {"name": true, "alias": true, "zone": true, "tag": true, "isolate_ports": true, "vlanaware": true},
|
||||||
|
"evpn": {"name": true, "alias": true, "zone": true, "tag": true, "isolate_ports": true},
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldMap := map[string]attr.Value{
|
||||||
|
"name": data.Name,
|
||||||
|
"zone": data.Zone,
|
||||||
|
"alias": data.Alias,
|
||||||
|
"tag": data.Tag,
|
||||||
|
"isolate_ports": data.IsolatePorts,
|
||||||
|
"vlanaware": data.VlanAware,
|
||||||
|
"type": data.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check required fields.
|
||||||
|
for _, field := range required[zoneType] {
|
||||||
|
if val, ok := fieldMap[field]; ok {
|
||||||
|
if val.IsNull() || val.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root(field),
|
||||||
|
"Missing Required Attribute",
|
||||||
|
fmt.Sprintf("The attribute %q is required for SDN VNETs in a %q zone.", field, zoneType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for fieldName, val := range fieldMap {
|
||||||
|
if !authorized[zoneType][fieldName] && !val.IsNull() && !val.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root(fieldName),
|
||||||
|
"Unauthorized Attribute for Zone Type",
|
||||||
|
fmt.Sprintf("The attribute %q is not allowed in VNETs under a %q zone.", fieldName, zoneType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
335
fwprovider/cluster/sdn/resource_sdn_zones.go
Normal file
335
fwprovider/cluster/sdn/resource_sdn_zones.go
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ resource.Resource = &sdnZoneResource{}
|
||||||
|
_ resource.ResourceWithConfigure = &sdnZoneResource{}
|
||||||
|
_ resource.ResourceWithImportState = &sdnZoneResource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnZoneResource struct {
|
||||||
|
client *zones.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSDNZoneResource() resource.Resource {
|
||||||
|
return &sdnZoneResource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Metadata(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.MetadataRequest,
|
||||||
|
resp *resource.MetadataResponse,
|
||||||
|
) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_sdn_zone"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Configure(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.ConfigureRequest,
|
||||||
|
resp *resource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.Resource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Resource Configure Type",
|
||||||
|
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = cfg.Client.Cluster().SDNZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Schema(
|
||||||
|
_ context.Context,
|
||||||
|
_ resource.SchemaRequest,
|
||||||
|
resp *resource.SchemaResponse,
|
||||||
|
) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: "Manages SDN Zones in Proxmox VE.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": attribute.ResourceID(),
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: "The unique ID of the SDN zone.",
|
||||||
|
Required: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": schema.StringAttribute{
|
||||||
|
Description: "Zone type (e.g. simple, vlan, qinq, vxlan, evpn).",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"ipam": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "IP Address Management system.",
|
||||||
|
},
|
||||||
|
"dns": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "DNS server address.",
|
||||||
|
},
|
||||||
|
"reversedns": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Reverse DNS settings.",
|
||||||
|
},
|
||||||
|
"dns_zone": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "DNS zone name.",
|
||||||
|
},
|
||||||
|
"nodes": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Comma-separated list of Proxmox node names.",
|
||||||
|
},
|
||||||
|
"mtu": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "MTU value for the zone.",
|
||||||
|
},
|
||||||
|
"bridge": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Bridge interface for VLAN/QinQ.",
|
||||||
|
},
|
||||||
|
"tag": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Service VLAN tag for QinQ.",
|
||||||
|
},
|
||||||
|
"vlan_protocol": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Service VLAN protocol for QinQ.",
|
||||||
|
},
|
||||||
|
"peers": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Peers list for VXLAN.",
|
||||||
|
},
|
||||||
|
"controller": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "EVPN controller address.",
|
||||||
|
},
|
||||||
|
"vrf_vxlan": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "EVPN VRF VXLAN ID.",
|
||||||
|
},
|
||||||
|
"exit_nodes": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Comma-separated list of exit nodes for EVPN.",
|
||||||
|
},
|
||||||
|
"primary_exit_node": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Primary exit node for EVPN.",
|
||||||
|
},
|
||||||
|
"exit_nodes_local_routing": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Enable local routing for EVPN exit nodes.",
|
||||||
|
},
|
||||||
|
"advertise_subnets": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Enable subnet advertisement for EVPN.",
|
||||||
|
},
|
||||||
|
"disable_arp_nd_suppression": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Disable ARP/ND suppression for EVPN.",
|
||||||
|
},
|
||||||
|
"rt_import": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Route target import for EVPN.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.CreateRequest,
|
||||||
|
resp *resource.CreateResponse,
|
||||||
|
) {
|
||||||
|
var plan sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := plan.toAPIRequestBody()
|
||||||
|
|
||||||
|
err := r.client.CreateZone(ctx, reqData)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Unable to Create SDN Zone", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.ID = plan.Name
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Read(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ReadRequest,
|
||||||
|
resp *resource.ReadResponse,
|
||||||
|
) {
|
||||||
|
var state sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Unable to Read SDN Zone", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnZoneModel{}
|
||||||
|
readModel.importFromAPI(zone.ID, zone)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.UpdateRequest,
|
||||||
|
resp *resource.UpdateResponse,
|
||||||
|
) {
|
||||||
|
var plan sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := plan.toAPIRequestBody()
|
||||||
|
|
||||||
|
err := r.client.UpdateZone(ctx, reqData)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Unable to Update SDN Zone", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.DeleteRequest,
|
||||||
|
resp *resource.DeleteResponse,
|
||||||
|
) {
|
||||||
|
var state sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.DeleteZone(ctx, state.ID.ValueString())
|
||||||
|
if err != nil && !errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Unable to Delete SDN Zone", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) ImportState(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ImportStateRequest,
|
||||||
|
resp *resource.ImportStateResponse,
|
||||||
|
) {
|
||||||
|
zone, err := r.client.GetZone(ctx, req.ID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError("Zone does not exist", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError("Unable to Import SDN Zone", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := &sdnZoneModel{}
|
||||||
|
readModel.importFromAPI(zone.ID, zone)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdnZoneResource) ValidateConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
req resource.ValidateConfigRequest,
|
||||||
|
resp *resource.ValidateConfigResponse,
|
||||||
|
) {
|
||||||
|
var data sdnZoneModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the type field.
|
||||||
|
if data.Type.IsNull() || data.Type.IsUnknown() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
required := map[string][]string{
|
||||||
|
"vlan": {"bridge"},
|
||||||
|
"qinq": {"bridge", "service_vlan"},
|
||||||
|
"vxlan": {"peers"},
|
||||||
|
"evpn": {"controller", "vrf_vxlan"},
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneType := data.Type.ValueString()
|
||||||
|
|
||||||
|
// Extracts required fields and at the same time checks zone type validity.
|
||||||
|
fields, ok := required[zoneType]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of field names to their values from data.
|
||||||
|
fieldMap := map[string]attr.Value{
|
||||||
|
"bridge": data.Bridge,
|
||||||
|
"service_vlan": data.ServiceVLAN,
|
||||||
|
"peers": data.Peers,
|
||||||
|
"controller": data.Controller,
|
||||||
|
"vrf_vxlan": data.VRFVXLANID,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
val, exists := fieldMap[field]
|
||||||
|
if !exists || val.IsNull() || val.IsUnknown() {
|
||||||
|
resp.Diagnostics.AddAttributeError(
|
||||||
|
path.Root(field),
|
||||||
|
"Missing Required Field",
|
||||||
|
fmt.Sprintf("Attribute %q is required when type is %q.", field, zoneType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
fwprovider/cluster/sdn/sdn_subnet_model.go
Normal file
102
fwprovider/cluster/sdn/sdn_subnet_model.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
/*
|
||||||
|
SUBNET MODEL TERRAFORM
|
||||||
|
|
||||||
|
Note: Currently in the API there are Delete and Digest options which are not available
|
||||||
|
in the UI so the choice was made to remove them temporary, waiting for a fix.
|
||||||
|
Also, it is not really in the way of working with terraform to use such parameters.
|
||||||
|
*/
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/helpers/ptrConversion"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/subnets"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnSubnetModel struct {
|
||||||
|
ID types.String `tfsdk:"id"`
|
||||||
|
Subnet types.String `tfsdk:"subnet"`
|
||||||
|
CanonicalName types.String `tfsdk:"canonical_name"`
|
||||||
|
Type types.String `tfsdk:"type"`
|
||||||
|
Vnet types.String `tfsdk:"vnet"`
|
||||||
|
DhcpDnsServer types.String `tfsdk:"dhcp_dns_server"`
|
||||||
|
DhcpRange []dhcpRangeModel `tfsdk:"dhcp_range"`
|
||||||
|
DnsZonePrefix types.String `tfsdk:"dnszoneprefix"`
|
||||||
|
Gateway types.String `tfsdk:"gateway"`
|
||||||
|
Snat types.Bool `tfsdk:"snat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhcpRangeModel struct {
|
||||||
|
StartAddress types.String `tfsdk:"start_address"`
|
||||||
|
EndAddress types.String `tfsdk:"end_address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnSubnetModel) importFromAPI(name string, data *subnets.SubnetData) {
|
||||||
|
m.ID = types.StringValue(name)
|
||||||
|
m.CanonicalName = types.StringValue(name)
|
||||||
|
|
||||||
|
m.Type = types.StringPointerValue(data.Type)
|
||||||
|
m.Vnet = types.StringPointerValue(data.Vnet)
|
||||||
|
|
||||||
|
m.DhcpDnsServer = types.StringPointerValue(data.DHCPDNSServer)
|
||||||
|
|
||||||
|
if data.DHCPRange != nil {
|
||||||
|
var ranges []dhcpRangeModel
|
||||||
|
for _, r := range data.DHCPRange {
|
||||||
|
ranges = append(ranges, dhcpRangeModel{
|
||||||
|
StartAddress: types.StringValue(r.StartAddress),
|
||||||
|
EndAddress: types.StringValue(r.EndAddress),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.DhcpRange = ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
m.DnsZonePrefix = types.StringPointerValue(data.DNSZonePrefix)
|
||||||
|
m.Gateway = types.StringPointerValue(data.Gateway)
|
||||||
|
m.Snat = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.SNAT))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnSubnetModel) toAPIRequestBody(ctx context.Context) *subnets.SubnetRequestData {
|
||||||
|
data := &subnets.SubnetRequestData{}
|
||||||
|
|
||||||
|
// When creating the subnet it is ok to pass subnet cidr, but when updating need to pass canonical name.
|
||||||
|
if m.CanonicalName.ValueString() == "" {
|
||||||
|
data.ID = m.Subnet.ValueString()
|
||||||
|
} else {
|
||||||
|
data.ID = m.CanonicalName.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Warn(ctx, "TO API", map[string]any{
|
||||||
|
"canonical name": m.CanonicalName.ValueString(),
|
||||||
|
"ID": m.ID.ValueString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
data.Type = m.Type.ValueStringPointer()
|
||||||
|
data.Vnet = m.Vnet.ValueStringPointer()
|
||||||
|
|
||||||
|
data.DHCPDNSServer = m.DhcpDnsServer.ValueStringPointer()
|
||||||
|
|
||||||
|
if m.DhcpRange != nil {
|
||||||
|
var dhcpRanges []string
|
||||||
|
for _, r := range m.DhcpRange {
|
||||||
|
dhcpRanges = append(
|
||||||
|
dhcpRanges,
|
||||||
|
fmt.Sprintf("start-address=%s,end-address=%s",
|
||||||
|
r.StartAddress.ValueString(),
|
||||||
|
r.EndAddress.ValueString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
data.DHCPRange = dhcpRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
data.DNSZonePrefix = m.DnsZonePrefix.ValueStringPointer()
|
||||||
|
data.Gateway = m.Gateway.ValueStringPointer()
|
||||||
|
data.SNAT = ptrConversion.BoolToInt64Ptr(m.Snat.ValueBoolPointer())
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
50
fwprovider/cluster/sdn/sdn_vnet_model.go
Normal file
50
fwprovider/cluster/sdn/sdn_vnet_model.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
/*
|
||||||
|
VNET MODEL TERRAFORM
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/helpers/ptrConversion"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/vnets"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnVnetModel struct {
|
||||||
|
ID types.String `tfsdk:"id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
Zone types.String `tfsdk:"zone"`
|
||||||
|
Alias types.String `tfsdk:"alias"`
|
||||||
|
IsolatePorts types.Bool `tfsdk:"isolate_ports"`
|
||||||
|
Tag types.Int64 `tfsdk:"tag"`
|
||||||
|
Type types.String `tfsdk:"type"`
|
||||||
|
VlanAware types.Bool `tfsdk:"vlanaware"`
|
||||||
|
ZoneType types.String `tfsdk:"zonetype"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnVnetModel) importFromAPI(name string, data *vnets.VnetData) {
|
||||||
|
m.ID = types.StringValue(name)
|
||||||
|
m.Name = types.StringValue(name)
|
||||||
|
|
||||||
|
m.Zone = types.StringPointerValue(data.Zone)
|
||||||
|
m.Alias = types.StringPointerValue(data.Alias)
|
||||||
|
m.IsolatePorts = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.IsolatePorts))
|
||||||
|
m.Tag = types.Int64PointerValue(data.Tag)
|
||||||
|
m.Type = types.StringPointerValue(data.Type)
|
||||||
|
m.VlanAware = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.VlanAware))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnVnetModel) toAPIRequestBody() *vnets.VnetRequestData {
|
||||||
|
data := &vnets.VnetRequestData{}
|
||||||
|
|
||||||
|
data.ID = m.Name.ValueString()
|
||||||
|
|
||||||
|
data.Zone = m.Zone.ValueStringPointer()
|
||||||
|
data.Alias = m.Alias.ValueStringPointer()
|
||||||
|
data.IsolatePorts = ptrConversion.BoolToInt64Ptr(m.IsolatePorts.ValueBoolPointer())
|
||||||
|
data.Tag = m.Tag.ValueInt64Pointer()
|
||||||
|
data.Type = m.Type.ValueStringPointer()
|
||||||
|
data.VlanAware = ptrConversion.BoolToInt64Ptr(m.VlanAware.ValueBoolPointer())
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
88
fwprovider/cluster/sdn/sdn_zone_model.go
Normal file
88
fwprovider/cluster/sdn/sdn_zone_model.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/helpers/ptrConversion"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdnZoneModel struct {
|
||||||
|
ID types.String `tfsdk:"id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
Type types.String `tfsdk:"type"`
|
||||||
|
IPAM types.String `tfsdk:"ipam"`
|
||||||
|
DNS types.String `tfsdk:"dns"`
|
||||||
|
ReverseDNS types.String `tfsdk:"reversedns"`
|
||||||
|
DNSZone types.String `tfsdk:"dns_zone"`
|
||||||
|
Nodes types.String `tfsdk:"nodes"`
|
||||||
|
MTU types.Int64 `tfsdk:"mtu"`
|
||||||
|
// VLAN.
|
||||||
|
Bridge types.String `tfsdk:"bridge"`
|
||||||
|
// QinQ.
|
||||||
|
ServiceVLAN types.Int64 `tfsdk:"tag"`
|
||||||
|
ServiceVLANProtocol types.String `tfsdk:"vlan_protocol"`
|
||||||
|
// VXLAN.
|
||||||
|
Peers types.String `tfsdk:"peers"`
|
||||||
|
// EVPN.
|
||||||
|
Controller types.String `tfsdk:"controller"`
|
||||||
|
ExitNodes types.String `tfsdk:"exit_nodes"`
|
||||||
|
PrimaryExitNode types.String `tfsdk:"primary_exit_node"`
|
||||||
|
RouteTargetImport types.String `tfsdk:"rt_import"`
|
||||||
|
VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"`
|
||||||
|
ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"`
|
||||||
|
AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"`
|
||||||
|
DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnZoneModel) importFromAPI(name string, data *zones.ZoneData) {
|
||||||
|
m.ID = types.StringValue(name)
|
||||||
|
m.Name = types.StringValue(name)
|
||||||
|
|
||||||
|
m.Type = types.StringPointerValue(data.Type)
|
||||||
|
m.IPAM = types.StringPointerValue(data.IPAM)
|
||||||
|
m.DNS = types.StringPointerValue(data.DNS)
|
||||||
|
m.ReverseDNS = types.StringPointerValue(data.ReverseDNS)
|
||||||
|
m.DNSZone = types.StringPointerValue(data.DNSZone)
|
||||||
|
m.Nodes = types.StringPointerValue(data.Nodes)
|
||||||
|
m.MTU = types.Int64PointerValue(data.MTU)
|
||||||
|
m.Bridge = types.StringPointerValue(data.Bridge)
|
||||||
|
m.ServiceVLAN = types.Int64PointerValue(data.ServiceVLAN)
|
||||||
|
m.ServiceVLANProtocol = types.StringPointerValue(data.ServiceVLANProtocol)
|
||||||
|
m.Peers = types.StringPointerValue(data.Peers)
|
||||||
|
m.Controller = types.StringPointerValue(data.Controller)
|
||||||
|
m.ExitNodes = types.StringPointerValue(data.ExitNodes)
|
||||||
|
m.PrimaryExitNode = types.StringPointerValue(data.PrimaryExitNode)
|
||||||
|
m.RouteTargetImport = types.StringPointerValue(data.RouteTargetImport)
|
||||||
|
m.VRFVXLANID = types.Int64PointerValue(data.VRFVXLANID)
|
||||||
|
m.ExitNodesLocalRouting = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.ExitNodesLocalRouting))
|
||||||
|
m.AdvertiseSubnets = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.AdvertiseSubnets))
|
||||||
|
m.DisableARPNDSuppression = types.BoolPointerValue(ptrConversion.Int64ToBoolPtr(data.DisableARPNDSuppression))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sdnZoneModel) toAPIRequestBody() *zones.ZoneRequestData {
|
||||||
|
data := &zones.ZoneRequestData{}
|
||||||
|
|
||||||
|
data.ID = m.Name.ValueString()
|
||||||
|
|
||||||
|
data.Type = m.Type.ValueStringPointer()
|
||||||
|
data.IPAM = m.IPAM.ValueStringPointer()
|
||||||
|
data.DNS = m.DNS.ValueStringPointer()
|
||||||
|
data.ReverseDNS = m.ReverseDNS.ValueStringPointer()
|
||||||
|
data.DNSZone = m.DNSZone.ValueStringPointer()
|
||||||
|
data.Nodes = m.Nodes.ValueStringPointer()
|
||||||
|
data.MTU = m.MTU.ValueInt64Pointer()
|
||||||
|
data.Bridge = m.Bridge.ValueStringPointer()
|
||||||
|
data.ServiceVLAN = m.ServiceVLAN.ValueInt64Pointer()
|
||||||
|
data.ServiceVLANProtocol = m.ServiceVLANProtocol.ValueStringPointer()
|
||||||
|
data.Peers = m.Peers.ValueStringPointer()
|
||||||
|
data.Controller = m.Controller.ValueStringPointer()
|
||||||
|
data.ExitNodes = m.ExitNodes.ValueStringPointer()
|
||||||
|
data.PrimaryExitNode = m.PrimaryExitNode.ValueStringPointer()
|
||||||
|
data.RouteTargetImport = m.RouteTargetImport.ValueStringPointer()
|
||||||
|
data.VRFVXLANID = m.VRFVXLANID.ValueInt64Pointer()
|
||||||
|
data.ExitNodesLocalRouting = ptrConversion.BoolToInt64Ptr(m.ExitNodesLocalRouting.ValueBoolPointer())
|
||||||
|
data.AdvertiseSubnets = ptrConversion.BoolToInt64Ptr(m.AdvertiseSubnets.ValueBoolPointer())
|
||||||
|
data.DisableARPNDSuppression = ptrConversion.BoolToInt64Ptr(m.DisableARPNDSuppression.ValueBoolPointer())
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
33
fwprovider/helpers/ptrConversion/ptr_conversion.go
Normal file
33
fwprovider/helpers/ptrConversion/ptr_conversion.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package ptrConversion
|
||||||
|
|
||||||
|
func BoolToInt64Ptr(boolPtr *bool) *int64 {
|
||||||
|
if boolPtr != nil {
|
||||||
|
var result int64
|
||||||
|
|
||||||
|
if *boolPtr {
|
||||||
|
result = int64(1)
|
||||||
|
} else {
|
||||||
|
result = int64(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int64ToBoolPtr(int64ptr *int64) *bool {
|
||||||
|
if int64ptr != nil {
|
||||||
|
var result bool
|
||||||
|
|
||||||
|
if *int64ptr == 0 {
|
||||||
|
result = false
|
||||||
|
} else {
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/hardwaremapping"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/hardwaremapping"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/metrics"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/metrics"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/options"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/options"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/sdn"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/nodes"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/nodes"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/nodes/apt"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/nodes/apt"
|
||||||
@ -515,6 +516,9 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
|
|||||||
nodes.NewDownloadFileResource,
|
nodes.NewDownloadFileResource,
|
||||||
options.NewClusterOptionsResource,
|
options.NewClusterOptionsResource,
|
||||||
vm.NewResource,
|
vm.NewResource,
|
||||||
|
sdn.NewSDNZoneResource,
|
||||||
|
sdn.NewSDNVnetResource,
|
||||||
|
sdn.NewSDNSubnetResource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,6 +542,9 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
|
|||||||
hardwaremapping.NewUSBDataSource,
|
hardwaremapping.NewUSBDataSource,
|
||||||
metrics.NewMetricsServerDatasource,
|
metrics.NewMetricsServerDatasource,
|
||||||
vm.NewDataSource,
|
vm.NewDataSource,
|
||||||
|
sdn.NewSDNZoneDataSource,
|
||||||
|
sdn.NewSDNVnetDataSource,
|
||||||
|
sdn.NewSDNSubnetDataSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
fwprovider/test/datasource_sdn_subnet_test.go
Normal file
64
fwprovider/test/datasource_sdn_subnet_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//go:build acceptance || all
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDatasourceSDNSubnet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"read sdn subnet attributes",
|
||||||
|
[]resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
data "proxmox_virtual_environment_sdn_vnet" "vnet_ex" {
|
||||||
|
name = "{{ .VNetName }}"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_sdn_subnet" "subnet_ex" {
|
||||||
|
subnet = "{{ .SubnetName }}"
|
||||||
|
vnet = data.proxmox_virtual_environment_sdn_vnet.vnet_ex.id
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
ResourceAttributesSet("data.proxmox_virtual_environment_sdn_subnet.subnet_ex", []string{
|
||||||
|
"id",
|
||||||
|
"subnet",
|
||||||
|
"canonical_name",
|
||||||
|
"type",
|
||||||
|
"vnet",
|
||||||
|
"dhcp_dns_server",
|
||||||
|
"dhcp_range.#",
|
||||||
|
"gateway",
|
||||||
|
"snat",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
54
fwprovider/test/datasource_sdn_vnet_test.go
Normal file
54
fwprovider/test/datasource_sdn_vnet_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//go:build acceptance || all
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDatasourceSDNVNet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"read sdn vnet attributes",
|
||||||
|
[]resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
data "proxmox_virtual_environment_sdn_vnet" "vnet_ex" {
|
||||||
|
name = "{{ .VnetName }}"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
ResourceAttributesSet("data.proxmox_virtual_environment_sdn_vnet.vnet_ex", []string{
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"zone",
|
||||||
|
"type",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
54
fwprovider/test/datasource_sdn_zone_test.go
Normal file
54
fwprovider/test/datasource_sdn_zone_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//go:build acceptance || all
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDatasourceSDNZone(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"read sdn zone attributes",
|
||||||
|
[]resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
data "proxmox_virtual_environment_sdn_zone" "zone_ex" {
|
||||||
|
name = "{{ .ZoneName }}"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
ResourceAttributesSet("data.proxmox_virtual_environment_sdn_zone.zone_ex", []string{
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"ipam",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
157
fwprovider/test/resource_sdn_test.go
Normal file
157
fwprovider/test/resource_sdn_test.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
//go:build acceptance || all
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccResourceSDN(t *testing.T) {
|
||||||
|
te := InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"create zones, vnets and subnets", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone" "zone_simple" {
|
||||||
|
name = "zoneS"
|
||||||
|
type = "simple"
|
||||||
|
nodes = "weisshorn-proxmox"
|
||||||
|
mtu = 1496
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone" "zone_vlan" {
|
||||||
|
name = "zoneVLAN"
|
||||||
|
type = "vlan"
|
||||||
|
nodes = "weisshorn-proxmox"
|
||||||
|
mtu = 1500
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_vnet" "vnet_simple" {
|
||||||
|
name = "vnetM"
|
||||||
|
zone = proxmox_virtual_environment_sdn_zone.zone_simple.name
|
||||||
|
alias = "vnet in zoneM"
|
||||||
|
isolate_ports = "0"
|
||||||
|
vlanaware = "0"
|
||||||
|
zonetype = proxmox_virtual_environment_sdn_zone.zone_simple.type
|
||||||
|
depends_on = [proxmox_virtual_environment_sdn_zone.zone_simple]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_vnet" "vnet_vlan" {
|
||||||
|
name = "vnetVLAN"
|
||||||
|
zone = proxmox_virtual_environment_sdn_zone.zone_vlan.name
|
||||||
|
alias = "vnet in zoneVLAN"
|
||||||
|
tag = 1000
|
||||||
|
zonetype = proxmox_virtual_environment_sdn_zone.zone_vlan.type
|
||||||
|
depends_on = [proxmox_virtual_environment_sdn_zone.zone_vlan]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_simple" {
|
||||||
|
subnet = "10.10.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_simple.name
|
||||||
|
dhcp_dns_server = "10.10.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.10.0.10"
|
||||||
|
end_address = "10.10.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.10.0.1"
|
||||||
|
snat = true
|
||||||
|
depends_on = [proxmox_virtual_environment_sdn_vnet.vnet_simple]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_simple2" {
|
||||||
|
subnet = "10.40.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_simple.name
|
||||||
|
dhcp_dns_server = "10.40.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.40.0.10"
|
||||||
|
end_address = "10.40.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.40.0.1"
|
||||||
|
snat = true
|
||||||
|
depends_on = [proxmox_virtual_environment_sdn_vnet.vnet_simple]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_sdn_subnet" "subnet_vlan" {
|
||||||
|
subnet = "10.20.0.0/24"
|
||||||
|
vnet = proxmox_virtual_environment_sdn_vnet.vnet_vlan.name
|
||||||
|
dhcp_dns_server = "10.20.0.53"
|
||||||
|
dhcp_range = [
|
||||||
|
{
|
||||||
|
start_address = "10.20.0.10"
|
||||||
|
end_address = "10.20.0.100"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
gateway = "10.20.0.100"
|
||||||
|
snat = false
|
||||||
|
depends_on = [proxmox_virtual_environment_sdn_vnet.vnet_vlan]
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
// Zones
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_sdn_zone.zone_simple", map[string]string{
|
||||||
|
"name": "zoneS",
|
||||||
|
"type": "simple",
|
||||||
|
"mtu": "1496",
|
||||||
|
"nodes": "weisshorn-proxmox",
|
||||||
|
}),
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_sdn_zone.zone_vlan", map[string]string{
|
||||||
|
"name": "zoneVLAN",
|
||||||
|
"type": "vlan",
|
||||||
|
"mtu": "1500",
|
||||||
|
"bridge": "vmbr0",
|
||||||
|
}),
|
||||||
|
|
||||||
|
// VNets
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_sdn_vnet.vnet_simple", map[string]string{
|
||||||
|
"name": "vnetM",
|
||||||
|
"alias": "vnet in zoneM",
|
||||||
|
"zone": "zoneS",
|
||||||
|
"isolate_ports": "false",
|
||||||
|
"vlanaware": "false",
|
||||||
|
"zonetype": "simple",
|
||||||
|
}),
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_sdn_vnet.vnet_vlan", map[string]string{
|
||||||
|
"name": "vnetVLAN",
|
||||||
|
"alias": "vnet in zoneVLAN",
|
||||||
|
"zone": "zoneVLAN",
|
||||||
|
"tag": "1000",
|
||||||
|
"zonetype": "vlan",
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Subnet (only check one in detail to avoid too many long checks)
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_sdn_subnet.subnet_simple", map[string]string{
|
||||||
|
"subnet": "10.10.0.0/24",
|
||||||
|
"vnet": "vnetM",
|
||||||
|
"gateway": "10.10.0.1",
|
||||||
|
"dhcp_dns_server": "10.10.0.53",
|
||||||
|
"snat": "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -141,6 +141,16 @@ func InitEnvironment(t *testing.T) *Environment {
|
|||||||
nodeName = "pve"
|
nodeName = "pve"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoneName := utils.GetAnyStringEnv("PROXMOX_VE_ACC_ZONE_NAME")
|
||||||
|
if zoneName == "" {
|
||||||
|
zoneName = "ZoneEx"
|
||||||
|
}
|
||||||
|
|
||||||
|
vnetName := utils.GetAnyStringEnv("PROXMOX_VE_ACC_VNET_NAME")
|
||||||
|
if vnetName == "" {
|
||||||
|
vnetName = "VnetEx"
|
||||||
|
}
|
||||||
|
|
||||||
const datastoreID = "local"
|
const datastoreID = "local"
|
||||||
|
|
||||||
cloudImagesServer := utils.GetAnyStringEnv("PROXMOX_VE_ACC_CLOUD_IMAGES_SERVER")
|
cloudImagesServer := utils.GetAnyStringEnv("PROXMOX_VE_ACC_CLOUD_IMAGES_SERVER")
|
||||||
@ -160,6 +170,8 @@ func InitEnvironment(t *testing.T) *Environment {
|
|||||||
"DatastoreID": datastoreID,
|
"DatastoreID": datastoreID,
|
||||||
"CloudImagesServer": cloudImagesServer,
|
"CloudImagesServer": cloudImagesServer,
|
||||||
"ContainerImagesServer": containerImagesServer,
|
"ContainerImagesServer": containerImagesServer,
|
||||||
|
"ZoneName": zoneName,
|
||||||
|
"VnetName": vnetName,
|
||||||
},
|
},
|
||||||
NodeName: nodeName,
|
NodeName: nodeName,
|
||||||
DatastoreID: datastoreID,
|
DatastoreID: datastoreID,
|
||||||
|
@ -15,6 +15,9 @@ import (
|
|||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/subnets"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/vnets"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,3 +57,18 @@ func (c *Client) ACME() *acme.Client {
|
|||||||
func (c *Client) Metrics() *metrics.Client {
|
func (c *Client) Metrics() *metrics.Client {
|
||||||
return &metrics.Client{Client: c}
|
return &metrics.Client{Client: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDNZones returns a client for managing the cluster's SDN zones.
|
||||||
|
func (c *Client) SDNZones() *zones.Client {
|
||||||
|
return &zones.Client{Client: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDNVnets returns a client for managing the cluster's SDN Vnets.
|
||||||
|
func (c *Client) SDNVnets() *vnets.Client {
|
||||||
|
return &vnets.Client{Client: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDNSubnets returns a client for managing the cluster's SDN Subnets.
|
||||||
|
func (c *Client) SDNSubnets() *subnets.Client {
|
||||||
|
return &subnets.Client{Client: c}
|
||||||
|
}
|
||||||
|
230
proxmox/cluster/sdn/sdn_test.go
Normal file
230
proxmox/cluster/sdn/sdn_test.go
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/subnets"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/vnets"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testZoneID = "testzone"
|
||||||
|
testVnetID = "testvnet"
|
||||||
|
testSubnetCIDR = "10.10.0.0/24"
|
||||||
|
testSubnetCanonical = "testzone-10.10.0.0-24"
|
||||||
|
testGateway = "10.10.0.1"
|
||||||
|
testDNS = "10.10.0.53"
|
||||||
|
testDHCPStart = "10.10.0.10"
|
||||||
|
testDHCPEnd = "10.10.0.100"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testClients struct {
|
||||||
|
zone *zones.Client
|
||||||
|
vnet *vnets.Client
|
||||||
|
subnet *subnets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestClients(t *testing.T) *testClients {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
apiToken := os.Getenv("PVE_TOKEN")
|
||||||
|
|
||||||
|
url := os.Getenv("PVE_URL")
|
||||||
|
if apiToken == "" || url == "" {
|
||||||
|
t.Skip("PVE_TOKEN and PVE_URL must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := api.NewConnection(url, true, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("connection error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := api.Credentials{TokenCredentials: &api.TokenCredentials{APIToken: apiToken}}
|
||||||
|
|
||||||
|
client, err := api.NewClient(creds, conn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("client error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &testClients{
|
||||||
|
zone: &zones.Client{Client: client},
|
||||||
|
vnet: &vnets.Client{Client: client},
|
||||||
|
subnet: &subnets.Client{Client: client},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSDNLifecycle(t *testing.T) {
|
||||||
|
clients := getTestClients(t)
|
||||||
|
|
||||||
|
t.Run("Create Zone", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.zone.CreateZone(t.Context(), &zones.ZoneRequestData{
|
||||||
|
ZoneData: zones.ZoneData{
|
||||||
|
ID: testZoneID,
|
||||||
|
Type: ptr.Ptr("vlan"),
|
||||||
|
IPAM: ptr.Ptr("pve"),
|
||||||
|
Bridge: ptr.Ptr("vmbr0"),
|
||||||
|
MTU: ptr.Ptr(int64(1500)),
|
||||||
|
Nodes: ptr.Ptr("pvenode1"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateZone failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get Zone", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
zone, err := clients.zone.GetZone(t.Context(), testZoneID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetZone failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Zone: %+v", zone)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update Zone", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.zone.UpdateZone(t.Context(), &zones.ZoneRequestData{
|
||||||
|
ZoneData: zones.ZoneData{
|
||||||
|
ID: testZoneID,
|
||||||
|
Nodes: ptr.Ptr("updatednode"),
|
||||||
|
Bridge: ptr.Ptr("vmbr1"), // simulate a VLAN-related update.
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateZone failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Create VNet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.vnet.CreateVnet(t.Context(), &vnets.VnetRequestData{
|
||||||
|
VnetData: vnets.VnetData{
|
||||||
|
ID: testVnetID,
|
||||||
|
Zone: ptr.Ptr(testZoneID),
|
||||||
|
Alias: ptr.Ptr("TestVNet"),
|
||||||
|
IsolatePorts: ptr.Ptr(int64(0)),
|
||||||
|
Type: ptr.Ptr("vnet"),
|
||||||
|
Tag: ptr.Ptr(int64(100)),
|
||||||
|
VlanAware: ptr.Ptr(int64(0)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateVnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get VNet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
vnet, err := clients.vnet.GetVnet(t.Context(), testVnetID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetVnet failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("VNet: %+v", vnet)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update VNet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.vnet.UpdateVnet(t.Context(), &vnets.VnetRequestData{
|
||||||
|
VnetData: vnets.VnetData{
|
||||||
|
ID: testVnetID,
|
||||||
|
Alias: ptr.Ptr("UpdatedAlias"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateVnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Create Subnet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ptr := &subnets.SubnetData{
|
||||||
|
ID: testSubnetCIDR,
|
||||||
|
Vnet: ptr.Ptr(testVnetID),
|
||||||
|
Type: ptr.Ptr("subnet"),
|
||||||
|
Gateway: ptr.Ptr(testGateway),
|
||||||
|
DHCPDNSServer: ptr.Ptr(testDNS),
|
||||||
|
DHCPRange: subnets.DHCPRangeList{
|
||||||
|
{StartAddress: testDHCPStart, EndAddress: testDHCPEnd},
|
||||||
|
},
|
||||||
|
SNAT: ptr.Ptr(int64(1)),
|
||||||
|
}
|
||||||
|
req := &subnets.SubnetRequestData{
|
||||||
|
EncodedSubnetData: *ptr.ToEncoded(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := clients.subnet.CreateSubnet(t.Context(), testVnetID, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateSubnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get Subnet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
subnet, err := clients.subnet.GetSubnet(t.Context(), testVnetID, testSubnetCanonical)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetSubnet failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Subnet: %+v", subnet)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update Subnet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ptr := &subnets.SubnetData{
|
||||||
|
ID: testSubnetCanonical,
|
||||||
|
Vnet: ptr.Ptr(testVnetID),
|
||||||
|
Gateway: ptr.Ptr("10.10.0.254"),
|
||||||
|
}
|
||||||
|
req := &subnets.SubnetRequestData{
|
||||||
|
EncodedSubnetData: *ptr.ToEncoded(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := clients.subnet.UpdateSubnet(t.Context(), testVnetID, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateSubnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete Subnet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.subnet.DeleteSubnet(t.Context(), testVnetID, testSubnetCanonical)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeleteSubnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete VNet", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.vnet.DeleteVnet(t.Context(), testVnetID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeleteVnet failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete Zone", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := clients.zone.DeleteZone(t.Context(), testZoneID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeleteZone failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
13
proxmox/cluster/sdn/subnets/api.go
Normal file
13
proxmox/cluster/sdn/subnets/api.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package subnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
GetSubnets(ctx context.Context, vnetID string) ([]SubnetData, error)
|
||||||
|
GetSubnet(ctx context.Context, vnetID string, id string) (*SubnetData, error)
|
||||||
|
CreateSubnet(ctx context.Context, vnetID string, data *SubnetRequestData) error
|
||||||
|
UpdateSubnet(ctx context.Context, vnetID string, data *SubnetRequestData) error
|
||||||
|
DeleteSubnet(ctx context.Context, vnetID string, id string) error
|
||||||
|
}
|
17
proxmox/cluster/sdn/subnets/client.go
Normal file
17
proxmox/cluster/sdn/subnets/client.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package subnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a client for accessing the Proxmox SDN VNETs API.
|
||||||
|
type Client struct {
|
||||||
|
api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandPath returns the API path for SDN VNETS.
|
||||||
|
func (c *Client) ExpandPath(vnet_id string, path string) string {
|
||||||
|
return fmt.Sprintf("cluster/sdn/vnets/%s/subnets/%s", vnet_id, path)
|
||||||
|
}
|
71
proxmox/cluster/sdn/subnets/subnets.go
Normal file
71
proxmox/cluster/sdn/subnets/subnets.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package subnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSubnet retrieves a single Subnet by ID and containing Vnet's ID.
|
||||||
|
func (c *Client) GetSubnet(ctx context.Context, vnetID string, id string) (*SubnetData, error) {
|
||||||
|
resBody := &SubnetResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(vnetID, id), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading SDN subnet %s for Vnet %s: %w", id, vnetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubnets lists all Subnets related to a Vnet.
|
||||||
|
func (c *Client) GetSubnets(ctx context.Context, vnetID string) ([]SubnetData, error) {
|
||||||
|
resBody := &SubnetsResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(vnetID, ""), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing Subnets for Vnet %s: %w", vnetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return *resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSubnet creates a new Subnet in the defined Vnet.
|
||||||
|
func (c *Client) CreateSubnet(ctx context.Context, vnetID string, data *SubnetRequestData) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath(vnetID, ""), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating subnet %s on VNet %s: %w", data.ID, vnetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubnet updates an existing subnet inside a defined vnet.
|
||||||
|
func (c *Client) UpdateSubnet(ctx context.Context, vnetID string, data *SubnetRequestData) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath(vnetID, data.ID), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating subnet %s on VNet %s: %w", data.ID, vnetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubnet deletes an existing subnet inside a defined vnet.
|
||||||
|
func (c *Client) DeleteSubnet(ctx context.Context, vnetID string, id string) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(vnetID, id), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting subnet %s on VNet %s: %w", id, vnetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
85
proxmox/cluster/sdn/subnets/subnets_types.go
Normal file
85
proxmox/cluster/sdn/subnets/subnets_types.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package subnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
SUBNETS
|
||||||
|
|
||||||
|
This part is related to the SDN component : SubNets
|
||||||
|
Based on docs :
|
||||||
|
https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_config_subnet
|
||||||
|
https://pve.proxmox.com/pve-docs/api-viewer/index.html#/cluster/sdn/vnets/{vnet}/subnets
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
1. The Type is once again defined as an enum type in the API docs but isn't referenced
|
||||||
|
anywhere. Therefore no way to check what are allowed types. 'subnet' works
|
||||||
|
2. Currently in the API there are Delete and Digest options which are not available
|
||||||
|
in the UI so the choice was made to remove them temporary, waiting for a fix.
|
||||||
|
3. It is also not really in the terraform spirit to update elements like this.
|
||||||
|
*/
|
||||||
|
type SubnetData struct {
|
||||||
|
ID string `json:"subnet,omitempty" url:"subnet,omitempty"`
|
||||||
|
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||||
|
Vnet *string `json:"vnet,omitempty" url:"vnet,omitempty"`
|
||||||
|
DHCPDNSServer *string `json:"dhcp-dns-server,omitempty" url:"dhcp-dns-server,omitempty"`
|
||||||
|
DHCPRange DHCPRangeList `json:"dhcp-range,omitempty" url:"dhcp-range,omitempty"`
|
||||||
|
DNSZonePrefix *string `json:"dnszoneprefix,omitempty" url:"dnszoneprefix,omitempty"`
|
||||||
|
Gateway *string `json:"gateway,omitempty" url:"gateway,omitempty"`
|
||||||
|
SNAT *int64 `json:"snat,omitempty" url:"snat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubnetRequestData struct {
|
||||||
|
EncodedSubnetData
|
||||||
|
Delete []string `url:"delete,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubnetResponseBody struct {
|
||||||
|
Data *SubnetData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubnetsResponseBody struct {
|
||||||
|
Data *[]SubnetData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DHCPRangeList []DHCPRangeEntry
|
||||||
|
|
||||||
|
type DHCPRangeEntry struct {
|
||||||
|
StartAddress string `json:"start-address"`
|
||||||
|
EndAddress string `json:"end-address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure had to be defined and added after realizing a weird behavior in Proxmox's API.
|
||||||
|
When creating or updating Subnets, the dhcpRange needs to be passed as string array.
|
||||||
|
But when reading (GET), it arrives as an array of JSON structures.
|
||||||
|
*/
|
||||||
|
type EncodedSubnetData struct {
|
||||||
|
ID string `url:"subnet,omitempty"`
|
||||||
|
Type *string `url:"type,omitempty"`
|
||||||
|
Vnet *string `url:"vnet,omitempty"`
|
||||||
|
DHCPDNSServer *string `url:"dhcp-dns-server,omitempty"`
|
||||||
|
DHCPRange []string `url:"dhcp-range,omitempty"`
|
||||||
|
DNSZonePrefix *string `url:"dnszoneprefix,omitempty"`
|
||||||
|
Gateway *string `url:"gateway,omitempty"`
|
||||||
|
SNAT *int64 `url:"snat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubnetData) ToEncoded() *EncodedSubnetData {
|
||||||
|
encodedRanges := make([]string, 0, len(s.DHCPRange))
|
||||||
|
for _, r := range s.DHCPRange {
|
||||||
|
encodedRanges = append(encodedRanges, fmt.Sprintf("start-address=%s,end-address=%s", r.StartAddress, r.EndAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &EncodedSubnetData{
|
||||||
|
ID: s.ID,
|
||||||
|
Type: s.Type,
|
||||||
|
Vnet: s.Vnet,
|
||||||
|
DHCPDNSServer: s.DHCPDNSServer,
|
||||||
|
DHCPRange: encodedRanges,
|
||||||
|
DNSZonePrefix: s.DNSZonePrefix,
|
||||||
|
Gateway: s.Gateway,
|
||||||
|
SNAT: s.SNAT,
|
||||||
|
}
|
||||||
|
}
|
16
proxmox/cluster/sdn/vnets/api.go
Normal file
16
proxmox/cluster/sdn/vnets/api.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package vnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
GetVnets(ctx context.Context) ([]VnetData, error)
|
||||||
|
GetVnet(ctx context.Context, id string) (*VnetData, error)
|
||||||
|
CreateVnet(ctx context.Context, req *VnetRequestData) error
|
||||||
|
UpdateVnet(ctx context.Context, req *VnetRequestData) error
|
||||||
|
DeleteVnet(ctx context.Context, id string) error
|
||||||
|
GetParentZone(ctx context.Context, zoneId string) (*zones.ZoneData, error)
|
||||||
|
}
|
21
proxmox/cluster/sdn/vnets/client.go
Normal file
21
proxmox/cluster/sdn/vnets/client.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package vnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a client for accessing the Proxmox SDN VNETs API.
|
||||||
|
type Client struct {
|
||||||
|
api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandPath returns the API path for SDN VNETS.
|
||||||
|
func (c *Client) ExpandPath(path string) string {
|
||||||
|
return fmt.Sprintf("cluster/sdn/vnets/%s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ParentPath(parentId string) string {
|
||||||
|
return fmt.Sprintf("cluster/sdn/zones/%s", parentId)
|
||||||
|
}
|
83
proxmox/cluster/sdn/vnets/vnets.go
Normal file
83
proxmox/cluster/sdn/vnets/vnets.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package vnets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetVnet retrieves a single SDN Vnet by ID.
|
||||||
|
func (c *Client) GetVnet(ctx context.Context, id string) (*VnetData, error) {
|
||||||
|
resBody := &VnetResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(id), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading SDN Vnet %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVnets lists all SDN Vnets.
|
||||||
|
func (c *Client) GetVnets(ctx context.Context) ([]VnetData, error) {
|
||||||
|
resBody := &VnetsResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(""), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing SDN Vnets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return *resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVnet creates a new SDN VNET.
|
||||||
|
func (c *Client) CreateVnet(ctx context.Context, data *VnetRequestData) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath(""), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating SDN VNET: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVnet Updates an existing VNet.
|
||||||
|
func (c *Client) UpdateVnet(ctx context.Context, data *VnetRequestData) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath(data.ID), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating SDN VNET: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVnet deletes an SDN VNET by ID.
|
||||||
|
func (c *Client) DeleteVnet(ctx context.Context, id string) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(id), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting SDN VNET: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetParentZone(ctx context.Context, zoneId string) (*zones.ZoneData, error) {
|
||||||
|
parentZone := zones.ZoneResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ParentPath(zoneId), nil, parentZone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching vnet's parent zone %s: %w", zoneId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentZone.Data, nil
|
||||||
|
}
|
45
proxmox/cluster/sdn/vnets/vnets_types.go
Normal file
45
proxmox/cluster/sdn/vnets/vnets_types.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package vnets
|
||||||
|
|
||||||
|
/*
|
||||||
|
VNETS
|
||||||
|
|
||||||
|
This part is related to the SDN component : VNETS
|
||||||
|
Based on docs :
|
||||||
|
https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_config_vnet
|
||||||
|
https://pve.proxmox.com/pve-docs/api-viewer/index.html#/cluster/sdn/vnets
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. IsolatePorts is a boolean in the docs but needs to be passed as 0 or 1
|
||||||
|
and is therefore defined as int.
|
||||||
|
|
||||||
|
2. Type field can be 'vnet' but other values are unknown
|
||||||
|
|
||||||
|
3. Tag cannot be set on Vnets created in simple Zones, might actually be
|
||||||
|
only usable on vlan or vxlan zones as it sets the vlan or vxlan id.
|
||||||
|
|
||||||
|
4. Currently in the API there are Delete and Digest options which are not available
|
||||||
|
in the UI so the choice was made to remove them temporary, waiting for a fix.
|
||||||
|
*/
|
||||||
|
type VnetData struct {
|
||||||
|
ID string `json:"vnet,omitempty" url:"vnet,omitempty"`
|
||||||
|
Zone *string `json:"zone,omitempty" url:"zone,omitempty"`
|
||||||
|
Alias *string `json:"alias,omitempty" url:"alias,omitempty"`
|
||||||
|
IsolatePorts *int64 `json:"isolate-ports,omitempty" url:"isolate-ports,omitempty"`
|
||||||
|
Tag *int64 `json:"tag,omitempty" url:"tag,omitempty"`
|
||||||
|
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||||
|
VlanAware *int64 `json:"vlanaware,omitempty" url:"vlanaware,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VnetRequestData struct {
|
||||||
|
VnetData
|
||||||
|
Delete []string `url:"delete,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VnetResponseBody struct {
|
||||||
|
Data *VnetData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VnetsResponseBody struct {
|
||||||
|
Data *[]VnetData `json:"data"`
|
||||||
|
}
|
13
proxmox/cluster/sdn/zones/api.go
Normal file
13
proxmox/cluster/sdn/zones/api.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
GetZones(ctx context.Context) ([]ZoneData, error)
|
||||||
|
GetZone(ctx context.Context, id string) (*ZoneData, error)
|
||||||
|
CreateZone(ctx context.Context, req *ZoneRequestData) error
|
||||||
|
UpdateZone(ctx context.Context, req *ZoneRequestData) error
|
||||||
|
DeleteZone(ctx context.Context, id string) error
|
||||||
|
}
|
17
proxmox/cluster/sdn/zones/client.go
Normal file
17
proxmox/cluster/sdn/zones/client.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a client for accessing the Proxmox SDN Zones API.
|
||||||
|
type Client struct {
|
||||||
|
api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandPath returns the API path for SDN zones.
|
||||||
|
func (c *Client) ExpandPath(path string) string {
|
||||||
|
return fmt.Sprintf("cluster/sdn/zones/%s", path)
|
||||||
|
}
|
76
proxmox/cluster/sdn/zones/zones.go
Normal file
76
proxmox/cluster/sdn/zones/zones.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetZone retrieves a single SDN zone by ID.
|
||||||
|
func (c *Client) GetZone(ctx context.Context, id string) (*ZoneData, error) {
|
||||||
|
resBody := &ZoneResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(id), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading SDN zone %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetZones lists all SDN zones.
|
||||||
|
func (c *Client) GetZones(ctx context.Context) ([]ZoneData, error) {
|
||||||
|
resBody := &ZonesResponseBody{}
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(""), nil, resBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing SDN zones: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return nil, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return *resBody.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateZone creates a new SDN zone.
|
||||||
|
func (c *Client) CreateZone(ctx context.Context, data *ZoneRequestData) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath(""), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating SDN zone: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateZone updates an existing SDN zone.
|
||||||
|
func (c *Client) UpdateZone(ctx context.Context, data *ZoneRequestData) error {
|
||||||
|
/* PVE API does not allow to pass "type" in PUT requests, this doesn't makes any sense
|
||||||
|
since other required params like port, server must still be there
|
||||||
|
while we could spawn another struct, let's just fix it silently */
|
||||||
|
data.Type = nil
|
||||||
|
|
||||||
|
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath(data.ID), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating SDN zone: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteZone deletes an SDN zone by ID.
|
||||||
|
func (c *Client) DeleteZone(ctx context.Context, id string) error {
|
||||||
|
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(id), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting SDN zone: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
56
proxmox/cluster/sdn/zones/zones_types.go
Normal file
56
proxmox/cluster/sdn/zones/zones_types.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
/*
|
||||||
|
ZONES
|
||||||
|
|
||||||
|
This part is related to the first SDN component : Zones
|
||||||
|
Based on docs :
|
||||||
|
https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_config_zone
|
||||||
|
https://pve.proxmox.com/pve-docs/api-viewer/index.html#/cluster/sdn/zones
|
||||||
|
*/
|
||||||
|
type ZoneData struct {
|
||||||
|
ID string `json:"zone,omitempty" url:"zone,omitempty"`
|
||||||
|
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||||
|
IPAM *string `json:"ipam,omitempty" url:"ipam,omitempty"`
|
||||||
|
DNS *string `json:"dns,omitempty" url:"dns,omitempty"`
|
||||||
|
ReverseDNS *string `json:"reversedns,omitempty" url:"reversedns,omitempty"`
|
||||||
|
DNSZone *string `json:"dnszone,omitempty" url:"dnszone,omitempty"`
|
||||||
|
Nodes *string `json:"nodes,omitempty" url:"nodes,omitempty"`
|
||||||
|
MTU *int64 `json:"mtu,omitempty" url:"mtu,omitempty"`
|
||||||
|
|
||||||
|
// VLAN.
|
||||||
|
Bridge *string `json:"bridge,omitempty" url:"bridge,omitempty"`
|
||||||
|
|
||||||
|
// QinQ.
|
||||||
|
ServiceVLAN *int64 `json:"tag,omitempty" url:"tag,omitempty"`
|
||||||
|
ServiceVLANProtocol *string `json:"vlan-protocol,omitempty" url:"vlan-protocol,omitempty"`
|
||||||
|
|
||||||
|
// VXLAN.
|
||||||
|
Peers *string `json:"peers,omitempty" url:"peers,omitempty"`
|
||||||
|
|
||||||
|
// EVPN.
|
||||||
|
Controller *string `json:"controller,omitempty" url:"controller,omitempty"`
|
||||||
|
VRFVXLANID *int64 `json:"vrf-vxlan,omitempty" url:"vrf-vxlan,omitempty"`
|
||||||
|
ExitNodes *string `json:"exitnodes,omitempty" url:"exitnodes,omitempty"`
|
||||||
|
PrimaryExitNode *string `json:"exitnodes-primary,omitempty" url:"exitnodes-primary,omitempty"`
|
||||||
|
ExitNodesLocalRouting *int64 `json:"exitnodes-local-routing,omitempty" url:"exitnodes-local-routing,omitempty"`
|
||||||
|
AdvertiseSubnets *int64 `json:"advertise-subnets,omitempty" url:"advertise-subnets,omitempty"`
|
||||||
|
DisableARPNDSuppression *int64 `json:"disable-arp-nd-suppression,omitempty" url:"disable-arp-nd-suppression,omitempty"`
|
||||||
|
RouteTargetImport *string `json:"rt-import,omitempty" url:"rt-import,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneRequestData wraps a ZoneData struct with optional delete instructions.
|
||||||
|
type ZoneRequestData struct {
|
||||||
|
ZoneData
|
||||||
|
Delete []string `url:"delete,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneResponseBody represents the response for a single zone.
|
||||||
|
type ZoneResponseBody struct {
|
||||||
|
Data *ZoneData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZonesResponseBody represents the response for a list of zones.
|
||||||
|
type ZonesResponseBody struct {
|
||||||
|
Data *[]ZoneData `json:"data"`
|
||||||
|
}
|
@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
package ptr
|
package ptr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
// Ptr creates a ptr from a value to use it inline.
|
// Ptr creates a ptr from a value to use it inline.
|
||||||
func Ptr[T any](val T) *T {
|
func Ptr[T any](val T) *T {
|
||||||
return &val
|
return &val
|
||||||
@ -43,3 +49,20 @@ func UpdateIfChanged[T comparable](dst **T, src *T) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PtrOrNil safely gets a value of any type from schema.ResourceData.
|
||||||
|
// If the key is missing, returns nil. For strings, also returns nil if empty or whitespace.
|
||||||
|
func PtrOrNil[T any](d *schema.ResourceData, key string) *T {
|
||||||
|
if v, ok := d.GetOk(key); ok {
|
||||||
|
val := v.(T)
|
||||||
|
|
||||||
|
// Special case: skip empty/whitespace-only strings
|
||||||
|
if s, ok := any(val).(string); ok && strings.TrimSpace(s) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
34
proxmoxtf/resource/cluster/sdn/subnets.go
Normal file
34
proxmoxtf/resource/cluster/sdn/subnets.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package sdn
|
||||||
|
|
||||||
|
import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
|
const (
|
||||||
|
mkSubnetID = "subnet"
|
||||||
|
mkSubnetType = "type"
|
||||||
|
mkSubnetVnet = "vnet"
|
||||||
|
mkSubnetDhcpDnsServer = "DhcpDnsServer"
|
||||||
|
mkSubnetDhcpRange = "DhcpRange"
|
||||||
|
mkSubnetDnsZonePrefix = "DnsZonePrefix"
|
||||||
|
mkSubnetGateway = "gateway"
|
||||||
|
mkSubnetSnat = "snat"
|
||||||
|
mkSubnetDeleteSettings = "deleteSettings"
|
||||||
|
mkSubnetDigest = "digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Subnet() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
mkSubnetID: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Description: "Subnet value",
|
||||||
|
},
|
||||||
|
mkSubnetType: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Subnet type",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user