From ce66bfdc61bd5faf6f95cfd25cb09dc1447567ec Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:18:32 -0400 Subject: [PATCH 1/6] fix(examples): update ubuntu LXC image ref (#2070) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- .../resource_virtual_environment_download_file.tf | 6 +++--- example/variables.tf | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/example/resource_virtual_environment_download_file.tf b/example/resource_virtual_environment_download_file.tf index 2ddeba6b..99405d11 100644 --- a/example/resource_virtual_environment_download_file.tf +++ b/example/resource_virtual_environment_download_file.tf @@ -1,11 +1,11 @@ ## Debian and ubuntu image download -resource "proxmox_virtual_environment_download_file" "release_20250610_ubuntu_24_noble_lxc_img" { +resource "proxmox_virtual_environment_download_file" "release_20250701_ubuntu_24_10_lxc_img" { content_type = "vztmpl" datastore_id = "local" node_name = var.virtual_environment_node_name - url = var.release_20250610_ubuntu_24_noble_lxc_img_url - checksum = var.release_20250610_ubuntu_24_noble_lxc_img_checksum + url = var.release_20250701_ubuntu_24_10_lxc_img_url + checksum = var.release_20250701_ubuntu_24_10_lxc_img_checksum checksum_algorithm = "sha256" upload_timeout = 4444 overwrite_unmanaged = true diff --git a/example/variables.tf b/example/variables.tf index 04f3aa61..331411db 100644 --- a/example/variables.tf +++ b/example/variables.tf @@ -31,14 +31,14 @@ variable "latest_debian_12_bookworm_qcow2_img_url" { default = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2" } -variable "release_20250610_ubuntu_24_noble_lxc_img_url" { +variable "release_20250701_ubuntu_24_10_lxc_img_url" { type = string - description = "The URL for the Ubuntu 24.04 LXC image" - default = "https://mirrors.servercentral.com/ubuntu-cloud-images/releases/24.04/release-20250610/ubuntu-24.04-server-cloudimg-amd64-root.tar.xz" + description = "The URL for the Ubuntu 24.10 LXC image" + default = "https://mirrors.servercentral.com/ubuntu-cloud-images/releases/24.10/release-20250701/ubuntu-24.10-server-cloudimg-amd64-root.tar.xz" } -variable "release_20250610_ubuntu_24_noble_lxc_img_checksum" { +variable "release_20250701_ubuntu_24_10_lxc_img_checksum" { type = string - description = "The checksum for the Ubuntu 24.04 LXC image" - default = "ae1fc4b5f020e6f1f2048beb5a7635f7bce4d72723239b7dea86af062cc1ab79" + description = "The checksum for the Ubuntu 24.10 LXC image" + default = "6caa4e90e4c2ae33d3fff0526c75cfc3d221e0c1ccd49d01229a44776af126d1" } From 757b191376405b5cea41b229cda165ac70b3a490 Mon Sep 17 00:00:00 2001 From: "Shawn M. Hanes" Date: Wed, 30 Jul 2025 20:48:32 -0400 Subject: [PATCH 2/6] chore(example): apply consistency in example/*.tf node_name declarations (#2051) * chore(example): apply consistency in example/*.tf node_name declarations Signed-off-by: Shawn M. Hanes * chore: reformat .tf files Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --------- Signed-off-by: Shawn M. Hanes Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- example/main.tf | 6 +-- .../resource_virtual_environment_container.tf | 4 +- ...ource_virtual_environment_download_file.tf | 4 +- example/variables.tf | 37 +++++++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/example/main.tf b/example/main.tf index 4265ee33..b95b4f83 100644 --- a/example/main.tf +++ b/example/main.tf @@ -1,9 +1,9 @@ provider "proxmox" { - endpoint = var.virtual_environment_endpoint + endpoint = var.virtual_environment_endpoint api_token = var.virtual_environment_api_token - insecure = true + insecure = true ssh { - agent = true + agent = true username = var.virtual_environment_ssh_username } } diff --git a/example/resource_virtual_environment_container.tf b/example/resource_virtual_environment_container.tf index c244f306..3ebb9d93 100644 --- a/example/resource_virtual_environment_container.tf +++ b/example/resource_virtual_environment_container.tf @@ -42,7 +42,7 @@ resource "proxmox_virtual_environment_container" "example_template" { node_name = data.proxmox_virtual_environment_nodes.example.names[0] operating_system { - template_file_id = proxmox_virtual_environment_download_file.release_20250610_ubuntu_24_noble_lxc_img.id + template_file_id = proxmox_virtual_environment_download_file.release_20250701_ubuntu_24_10_lxc_img.id type = "ubuntu" } @@ -87,7 +87,7 @@ resource "proxmox_virtual_environment_container" "example" { pool_id = proxmox_virtual_environment_pool.example.id # Set the protection flag to prevent the deletion/update operations for the container and its disks. # protection = true - vm_id = 2043 + vm_id = 2043 } output "resource_proxmox_virtual_environment_container_example_id" { diff --git a/example/resource_virtual_environment_download_file.tf b/example/resource_virtual_environment_download_file.tf index 99405d11..ae5ec760 100644 --- a/example/resource_virtual_environment_download_file.tf +++ b/example/resource_virtual_environment_download_file.tf @@ -3,7 +3,7 @@ resource "proxmox_virtual_environment_download_file" "release_20250701_ubuntu_24_10_lxc_img" { content_type = "vztmpl" datastore_id = "local" - node_name = var.virtual_environment_node_name + node_name = data.proxmox_virtual_environment_nodes.example.names[0] url = var.release_20250701_ubuntu_24_10_lxc_img_url checksum = var.release_20250701_ubuntu_24_10_lxc_img_checksum checksum_algorithm = "sha256" @@ -15,7 +15,7 @@ resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_ content_type = "import" datastore_id = "local" file_name = "debian-12-generic-amd64.qcow2" - node_name = var.virtual_environment_node_name + node_name = data.proxmox_virtual_environment_nodes.example.names[0] url = var.latest_debian_12_bookworm_qcow2_img_url overwrite = true overwrite_unmanaged = true diff --git a/example/variables.tf b/example/variables.tf index 331411db..254f7933 100644 --- a/example/variables.tf +++ b/example/variables.tf @@ -1,44 +1,65 @@ variable "virtual_environment_endpoint" { - type = string description = "The endpoint for the Proxmox Virtual Environment API (example: https://host:port)" + type = string +} + +variable "virtual_environment_username" { + description = "Proxmox User for API Access" + type = string + default = "root@pam" +} + +variable "virtual_environment_password" { + description = "Password for Proxmox API User" + type = string + sensitive = true + default = "don not use default passwords!" } variable "virtual_environment_api_token" { - type = string description = "The API token for the Proxmox Virtual Environment API" + type = string + sensitive = true } variable "virtual_environment_ssh_username" { - type = string description = "The username for the Proxmox Virtual Environment API" + type = string + default = "root" } variable "virtual_environment_node_name" { description = "Name of the Proxmox node" type = string - default = "pve" + default = "pve" +} + +variable "virtual_environment_insecure" { + description = "Self Signed Certificates Used" + type = bool + default = true } variable "virtual_environment_storage" { description = "Name of the Proxmox storage" type = string - default = "local-lvm" + default = "local-lvm" } variable "latest_debian_12_bookworm_qcow2_img_url" { - type = string description = "The URL for the latest Debian 12 Bookworm qcow2 image" + type = string default = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2" } variable "release_20250701_ubuntu_24_10_lxc_img_url" { - type = string description = "The URL for the Ubuntu 24.10 LXC image" + type = string default = "https://mirrors.servercentral.com/ubuntu-cloud-images/releases/24.10/release-20250701/ubuntu-24.10-server-cloudimg-amd64-root.tar.xz" } variable "release_20250701_ubuntu_24_10_lxc_img_checksum" { - type = string description = "The checksum for the Ubuntu 24.10 LXC image" + type = string default = "6caa4e90e4c2ae33d3fff0526c75cfc3d221e0c1ccd49d01229a44776af126d1" } From 6b73d822a5a6d55b7761647ad963be2b88491bbd Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:30:18 -0400 Subject: [PATCH 3/6] feat(sdn): add support for zone resources and datasources (#2046) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- .golangci.yml | 2 + .../virtual_environment_sdn_zone_evpn.md | 64 ++++ .../virtual_environment_sdn_zone_qinq.md | 54 ++++ .../virtual_environment_sdn_zone_simple.md | 48 +++ .../virtual_environment_sdn_zone_vlan.md | 50 +++ .../virtual_environment_sdn_zone_vxlan.md | 50 +++ .../virtual_environment_sdn_zones.md | 79 +++++ .../virtual_environment_sdn_zone_evpn.md | 72 +++++ .../virtual_environment_sdn_zone_qinq.md | 60 ++++ .../virtual_environment_sdn_zone_simple.md | 54 ++++ .../virtual_environment_sdn_zone_vlan.md | 56 ++++ .../virtual_environment_sdn_zone_vxlan.md | 56 ++++ .../data-source.tf | 23 ++ .../data-source.tf | 18 ++ .../data-source.tf | 15 + .../data-source.tf | 16 + .../data-source.tf | 16 + .../data-source.tf | 25 ++ .../import.sh | 3 + .../resource.tf | 21 ++ .../import.sh | 3 + .../resource.tf | 14 + .../import.sh | 3 + .../resource.tf | 11 + .../import.sh | 3 + .../resource.tf | 12 + .../import.sh | 3 + .../resource.tf | 12 + .../cluster/sdn/zone/datasource_evpn.go | 99 ++++++ .../cluster/sdn/zone/datasource_generic.go | 166 ++++++++++ .../cluster/sdn/zone/datasource_qinq.go | 74 +++++ .../cluster/sdn/zone/datasource_simple.go | 57 ++++ .../cluster/sdn/zone/datasource_vlan.go | 64 ++++ .../cluster/sdn/zone/datasource_vxlan.go | 75 +++++ .../cluster/sdn/zone/datasource_zones.go | 282 +++++++++++++++++ .../cluster/sdn/zone/datasource_zones_test.go | 259 ++++++++++++++++ fwprovider/cluster/sdn/zone/resource_evpn.go | 158 ++++++++++ .../cluster/sdn/zone/resource_generic.go | 292 ++++++++++++++++++ fwprovider/cluster/sdn/zone/resource_qinq.go | 124 ++++++++ .../cluster/sdn/zone/resource_simple.go | 77 +++++ fwprovider/cluster/sdn/zone/resource_vlan.go | 102 ++++++ fwprovider/cluster/sdn/zone/resource_vxlan.go | 104 +++++++ .../cluster/sdn/zone/resource_zones_test.go | 195 ++++++++++++ .../nodes/network/resource_linux_bridge.go | 1 - .../nodes/network/resource_linux_vlan.go | 1 - fwprovider/provider.go | 12 + fwprovider/types/stringset/attribute.go | 29 +- main.go | 11 + proxmox/api/client.go | 4 +- proxmox/cluster/client.go | 16 + proxmox/cluster/sdn/zones/api.go | 19 ++ proxmox/cluster/sdn/zones/client.go | 23 ++ proxmox/cluster/sdn/zones/doc.go | 11 + proxmox/cluster/sdn/zones/zones.go | 82 +++++ proxmox/cluster/sdn/zones/zones_types.go | 65 ++++ .../data-source.tf | 23 ++ .../data-source.tf | 18 ++ .../data-source.tf | 15 + .../data-source.tf | 16 + .../data-source.tf | 16 + .../data-source.tf | 25 ++ testacc | 20 +- 62 files changed, 3370 insertions(+), 8 deletions(-) create mode 100644 docs/data-sources/virtual_environment_sdn_zone_evpn.md create mode 100644 docs/data-sources/virtual_environment_sdn_zone_qinq.md create mode 100644 docs/data-sources/virtual_environment_sdn_zone_simple.md create mode 100644 docs/data-sources/virtual_environment_sdn_zone_vlan.md create mode 100644 docs/data-sources/virtual_environment_sdn_zone_vxlan.md create mode 100644 docs/data-sources/virtual_environment_sdn_zones.md create mode 100644 docs/resources/virtual_environment_sdn_zone_evpn.md create mode 100644 docs/resources/virtual_environment_sdn_zone_qinq.md create mode 100644 docs/resources/virtual_environment_sdn_zone_simple.md create mode 100644 docs/resources/virtual_environment_sdn_zone_vlan.md create mode 100644 docs/resources/virtual_environment_sdn_zone_vxlan.md create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zone_simple/data-source.tf create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf create mode 100644 examples/data-sources/proxmox_virtual_environment_sdn_zones/data-source.tf create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_evpn/import.sh create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_evpn/resource.tf create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_qinq/import.sh create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_qinq/resource.tf create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_simple/import.sh create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_simple/resource.tf create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_vlan/import.sh create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_vlan/resource.tf create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/import.sh create mode 100644 examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/resource.tf create mode 100644 fwprovider/cluster/sdn/zone/datasource_evpn.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_generic.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_qinq.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_simple.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_vlan.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_vxlan.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_zones.go create mode 100644 fwprovider/cluster/sdn/zone/datasource_zones_test.go create mode 100644 fwprovider/cluster/sdn/zone/resource_evpn.go create mode 100644 fwprovider/cluster/sdn/zone/resource_generic.go create mode 100644 fwprovider/cluster/sdn/zone/resource_qinq.go create mode 100644 fwprovider/cluster/sdn/zone/resource_simple.go create mode 100644 fwprovider/cluster/sdn/zone/resource_vlan.go create mode 100644 fwprovider/cluster/sdn/zone/resource_vxlan.go create mode 100644 fwprovider/cluster/sdn/zone/resource_zones_test.go create mode 100644 proxmox/cluster/sdn/zones/api.go create mode 100644 proxmox/cluster/sdn/zones/client.go create mode 100644 proxmox/cluster/sdn/zones/doc.go create mode 100644 proxmox/cluster/sdn/zones/zones.go create mode 100644 proxmox/cluster/sdn/zones/zones_types.go create mode 100644 proxmox_virtual_environment_sdn_zone_evpn/data-source.tf create mode 100644 proxmox_virtual_environment_sdn_zone_qinq/data-source.tf create mode 100644 proxmox_virtual_environment_sdn_zone_simple/data-source.tf create mode 100644 proxmox_virtual_environment_sdn_zone_vlan/data-source.tf create mode 100644 proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf create mode 100644 proxmox_virtual_environment_sdn_zones/data-source.tf diff --git a/.golangci.yml b/.golangci.yml index 3c6d3ae1..b91d3142 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -44,6 +44,8 @@ linters: gosec: excludes: - G115 + lll: + line-length: 150 revive: rules: - name: "package-comments" diff --git a/docs/data-sources/virtual_environment_sdn_zone_evpn.md b/docs/data-sources/virtual_environment_sdn_zone_evpn.md new file mode 100644 index 00000000..14ed2a7a --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zone_evpn.md @@ -0,0 +1,64 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_evpn +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about an EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of spanning across multiple clusters. +--- + +# Data Source: proxmox_virtual_environment_sdn_zone_evpn + +Retrieves information about an EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of spanning across multiple clusters. + +## Example Usage + +```terraform +data "proxmox_virtual_environment_sdn_zone_evpn" "example" { + id = "evpn1" +} + +output "data_proxmox_virtual_environment_sdn_zone_evpn" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_evpn.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.nodes + controller = data.proxmox_virtual_environment_sdn_zone_evpn.example.controller + vrf_vxlan = data.proxmox_virtual_environment_sdn_zone_evpn.example.vrf_vxlan + advertise_subnets = data.proxmox_virtual_environment_sdn_zone_evpn.example.advertise_subnets + disable_arp_nd_suppression = data.proxmox_virtual_environment_sdn_zone_evpn.example.disable_arp_nd_suppression + exit_nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes + exit_nodes_local_routing = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes_local_routing + primary_exit_node = data.proxmox_virtual_environment_sdn_zone_evpn.example.primary_exit_node + rt_import = data.proxmox_virtual_environment_sdn_zone_evpn.example.rt_import + mtu = data.proxmox_virtual_environment_sdn_zone_evpn.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_evpn.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.reverse_dns + } +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. + +### Read-Only + +- `advertise_subnets` (Boolean) Enable subnet advertisement for EVPN. +- `controller` (String) EVPN controller address. +- `disable_arp_nd_suppression` (Boolean) Disable ARP/ND suppression for EVPN. +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `exit_nodes` (Set of String) 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` (Set of String) The Proxmox nodes which the zone and associated VNets are deployed on +- `primary_exit_node` (String) Primary exit node for EVPN. +- `reverse_dns` (String) Reverse DNS API server address. +- `rt_import` (String) Route target import for EVPN. Must be in the format ':' (e.g., '65000:65000'). +- `vrf_vxlan` (Number) VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different than the VXLAN-ID of the VNets. diff --git a/docs/data-sources/virtual_environment_sdn_zone_qinq.md b/docs/data-sources/virtual_environment_sdn_zone_qinq.md new file mode 100644 index 00000000..b3ad6eba --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zone_qinq.md @@ -0,0 +1,54 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_qinq +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about a QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500. +--- + +# Data Source: proxmox_virtual_environment_sdn_zone_qinq + +Retrieves information about a QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500. + +## Example Usage + +```terraform +data "proxmox_virtual_environment_sdn_zone_qinq" "example" { + id = "qinq1" +} + +output "data_proxmox_virtual_environment_sdn_zone_qinq" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_qinq.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_qinq.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_qinq.example.bridge + service_vlan = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan + service_vlan_protocol = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan_protocol + mtu = data.proxmox_virtual_environment_sdn_zone_qinq.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_qinq.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.reverse_dns + } +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. + +### Read-Only + +- `bridge` (String) A local, VLAN-aware bridge that is already configured on each local node +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets are deployed on +- `reverse_dns` (String) Reverse DNS API server address. +- `service_vlan` (Number) Service VLAN tag for QinQ. The tag must be between `1` and `4094`. +- `service_vlan_protocol` (String) Service VLAN protocol for QinQ. The protocol must be `802.1ad` or `802.1q`. diff --git a/docs/data-sources/virtual_environment_sdn_zone_simple.md b/docs/data-sources/virtual_environment_sdn_zone_simple.md new file mode 100644 index 00000000..79382c0e --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zone_simple.md @@ -0,0 +1,48 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_simple +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about a Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. This bridge is not linked to a physical interface, and VM traffic is only local on each the node. It can be used in NAT or routed setups. +--- + +# Data Source: proxmox_virtual_environment_sdn_zone_simple + +Retrieves information about a Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. This bridge is not linked to a physical interface, and VM traffic is only local on each the node. It can be used in NAT or routed setups. + +## Example Usage + +```terraform +data "proxmox_virtual_environment_sdn_zone_simple" "example" { + id = "simple1" +} + +output "data_proxmox_virtual_environment_sdn_zone_simple" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_simple.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_simple.example.nodes + mtu = data.proxmox_virtual_environment_sdn_zone_simple.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_simple.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_simple.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_simple.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_simple.example.reverse_dns + } +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. + +### Read-Only + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets are deployed on +- `reverse_dns` (String) Reverse DNS API server address. diff --git a/docs/data-sources/virtual_environment_sdn_zone_vlan.md b/docs/data-sources/virtual_environment_sdn_zone_vlan.md new file mode 100644 index 00000000..83377df2 --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zone_vlan.md @@ -0,0 +1,50 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_vlan +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about a VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. This allows connectivity of VMs between different nodes. +--- + +# Data Source: proxmox_virtual_environment_sdn_zone_vlan + +Retrieves information about a VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. This allows connectivity of VMs between different nodes. + +## Example Usage + +```terraform +data "proxmox_virtual_environment_sdn_zone_vlan" "example" { + id = "vlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vlan.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_vlan.example.bridge + mtu = data.proxmox_virtual_environment_sdn_zone_vlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.reverse_dns + } +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. + +### Read-Only + +- `bridge` (String) The local bridge or OVS switch, already configured on _each_ node that allows node-to-node connection. +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets are deployed on +- `reverse_dns` (String) Reverse DNS API server address. diff --git a/docs/data-sources/virtual_environment_sdn_zone_vxlan.md b/docs/data-sources/virtual_environment_sdn_zone_vxlan.md new file mode 100644 index 00000000..a4346333 --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zone_vxlan.md @@ -0,0 +1,50 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_vxlan +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about a VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network (underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the outgoing physical interface. +--- + +# Data Source: proxmox_virtual_environment_sdn_zone_vxlan + +Retrieves information about a VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network (underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the outgoing physical interface. + +## Example Usage + +```terraform +data "proxmox_virtual_environment_sdn_zone_vxlan" "example" { + id = "vxlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vxlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vxlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vxlan.example.nodes + peers = data.proxmox_virtual_environment_sdn_zone_vxlan.example.peers + mtu = data.proxmox_virtual_environment_sdn_zone_vxlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vxlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.reverse_dns + } +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. + +### Read-Only + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets are deployed on +- `peers` (Set of String) A list of IP addresses of each node in the VXLAN zone. This can be external nodes reachable at this IP address. All nodes in the cluster need to be mentioned here +- `reverse_dns` (String) Reverse DNS API server address. diff --git a/docs/data-sources/virtual_environment_sdn_zones.md b/docs/data-sources/virtual_environment_sdn_zones.md new file mode 100644 index 00000000..2997d01b --- /dev/null +++ b/docs/data-sources/virtual_environment_sdn_zones.md @@ -0,0 +1,79 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zones +parent: Data Sources +subcategory: Virtual Environment +description: |- + Retrieves information about all SDN Zones in Proxmox. This data source can optionally filter zones by type. +--- + +# Data Source: proxmox_virtual_environment_sdn_zones + +Retrieves information about all SDN Zones in Proxmox. This data source can optionally filter zones by type. + +## Example Usage + +```terraform +# List all SDN zones +data "proxmox_virtual_environment_sdn_zones" "all" {} + +# List only EVPN zones +data "proxmox_virtual_environment_sdn_zones" "evpn_only" { + type = "evpn" +} + +# List only Simple zones +data "proxmox_virtual_environment_sdn_zones" "simple_only" { + type = "simple" +} + +output "data_proxmox_virtual_environment_sdn_zones_all" { + value = { + zones = data.proxmox_virtual_environment_sdn_zones.all.zones + } +} + +output "data_proxmox_virtual_environment_sdn_zones_filtered" { + value = { + evpn_zones = data.proxmox_virtual_environment_sdn_zones.evpn_only.zones + simple_zones = data.proxmox_virtual_environment_sdn_zones.simple_only.zones + } +} +``` + + +## Schema + +### Optional + +- `type` (String) Filter zones by type (simple, vlan, qinq, vxlan, evpn). + +### Read-Only + +- `zones` (List of Object) List of SDN zones. (see [below for nested schema](#nestedatt--zones)) + + +### Nested Schema for `zones` + +Read-Only: + +- `advertise_subnets` (Boolean) +- `bridge` (String) +- `controller` (String) +- `disable_arp_nd_suppression` (Boolean) +- `dns` (String) +- `dns_zone` (String) +- `exit_nodes` (Set of String) +- `exit_nodes_local_routing` (Boolean) +- `id` (String) +- `ipam` (String) +- `mtu` (Number) +- `nodes` (Set of String) +- `peers` (Set of String) +- `primary_exit_node` (String) +- `reverse_dns` (String) +- `rt_import` (String) +- `service_vlan` (Number) +- `service_vlan_protocol` (String) +- `type` (String) +- `vrf_vxlan` (Number) diff --git a/docs/resources/virtual_environment_sdn_zone_evpn.md b/docs/resources/virtual_environment_sdn_zone_evpn.md new file mode 100644 index 00000000..1b94d637 --- /dev/null +++ b/docs/resources/virtual_environment_sdn_zone_evpn.md @@ -0,0 +1,72 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_evpn +parent: Resources +subcategory: Virtual Environment +description: |- + EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of spanning across multiple clusters. +--- + +# Resource: proxmox_virtual_environment_sdn_zone_evpn + +EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of spanning across multiple clusters. + +## Example Usage + +```terraform +resource "proxmox_virtual_environment_sdn_zone_evpn" "example" { + id = "evpn1" + nodes = ["pve"] + controller = "evpn-controller1" + vrf_vxlan = 4000 + + # Optional attributes + advertise_subnets = true + disable_arp_nd_suppression = false + exit_nodes = ["pve-exit1", "pve-exit2"] + exit_nodes_local_routing = true + primary_exit_node = "pve-exit1" + rt_import = "65000:65000" + mtu = 1450 + + # Generic optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} +``` + + +## Schema + +### Required + +- `controller` (String) EVPN controller address. +- `id` (String) The unique identifier of the SDN zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets should be deployed on +- `vrf_vxlan` (Number) VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different than the VXLAN-ID of the VNets. + +### Optional + +- `advertise_subnets` (Boolean) Enable subnet advertisement for EVPN. +- `disable_arp_nd_suppression` (Boolean) Disable ARP/ND suppression for EVPN. +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `exit_nodes` (Set of String) 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. +- `primary_exit_node` (String) Primary exit node for EVPN. +- `reverse_dns` (String) Reverse DNS API server address. +- `rt_import` (String) Route target import for EVPN. + +## Import + +Import is supported using the following syntax: + +```shell +#!/usr/bin/env sh +# EVPN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_evpn.example evpn1 +``` diff --git a/docs/resources/virtual_environment_sdn_zone_qinq.md b/docs/resources/virtual_environment_sdn_zone_qinq.md new file mode 100644 index 00000000..7b72b785 --- /dev/null +++ b/docs/resources/virtual_environment_sdn_zone_qinq.md @@ -0,0 +1,60 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_qinq +parent: Resources +subcategory: Virtual Environment +description: |- + QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500. +--- + +# Resource: proxmox_virtual_environment_sdn_zone_qinq + +QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500. + +## Example Usage + +```terraform +resource "proxmox_virtual_environment_sdn_zone_qinq" "example" { + id = "qinq1" + nodes = ["pve"] + bridge = "vmbr0" + service_vlan = 100 + service_vlan_protocol = "802.1ad" + mtu = 1496 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} +``` + + +## Schema + +### Required + +- `bridge` (String) A local, VLAN-aware bridge that is already configured on each local node +- `id` (String) The unique identifier of the SDN zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets should be deployed on +- `service_vlan` (Number) Service VLAN tag for QinQ. The tag must be between `1` and `4094`. + +### Optional + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `reverse_dns` (String) Reverse DNS API server address. +- `service_vlan_protocol` (String) Service VLAN protocol for QinQ. The protocol must be `802.1ad` or `802.1q`. + +## Import + +Import is supported using the following syntax: + +```shell +#!/usr/bin/env sh +# QinQ SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_qinq.example qinq1 +``` diff --git a/docs/resources/virtual_environment_sdn_zone_simple.md b/docs/resources/virtual_environment_sdn_zone_simple.md new file mode 100644 index 00000000..92600448 --- /dev/null +++ b/docs/resources/virtual_environment_sdn_zone_simple.md @@ -0,0 +1,54 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_simple +parent: Resources +subcategory: Virtual Environment +description: |- + Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. This bridge is not linked to a physical interface, and VM traffic is only local on each the node. It can be used in NAT or routed setups. +--- + +# Resource: proxmox_virtual_environment_sdn_zone_simple + +Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. This bridge is not linked to a physical interface, and VM traffic is only local on each the node. It can be used in NAT or routed setups. + +## Example Usage + +```terraform +resource "proxmox_virtual_environment_sdn_zone_simple" "example" { + id = "simple1" + nodes = ["pve"] + mtu = 1500 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets should be deployed on + +### Optional + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `reverse_dns` (String) Reverse DNS API server address. + +## Import + +Import is supported using the following syntax: + +```shell +#!/usr/bin/env sh +# Simple SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_simple.example simple1 +``` diff --git a/docs/resources/virtual_environment_sdn_zone_vlan.md b/docs/resources/virtual_environment_sdn_zone_vlan.md new file mode 100644 index 00000000..3cb0b5eb --- /dev/null +++ b/docs/resources/virtual_environment_sdn_zone_vlan.md @@ -0,0 +1,56 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_vlan +parent: Resources +subcategory: Virtual Environment +description: |- + VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. This allows connectivity of VMs between different nodes. +--- + +# Resource: proxmox_virtual_environment_sdn_zone_vlan + +VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. This allows connectivity of VMs between different nodes. + +## Example Usage + +```terraform +resource "proxmox_virtual_environment_sdn_zone_vlan" "example" { + id = "vlan1" + nodes = ["pve"] + bridge = "vmbr0" + mtu = 1500 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} +``` + + +## Schema + +### Required + +- `bridge` (String) The local bridge or OVS switch, already configured on _each_ node that allows node-to-node connection. +- `id` (String) The unique identifier of the SDN zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets should be deployed on + +### Optional + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `reverse_dns` (String) Reverse DNS API server address. + +## Import + +Import is supported using the following syntax: + +```shell +#!/usr/bin/env sh +# VLAN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_vlan.example vlan1 +``` diff --git a/docs/resources/virtual_environment_sdn_zone_vxlan.md b/docs/resources/virtual_environment_sdn_zone_vxlan.md new file mode 100644 index 00000000..630f2a3a --- /dev/null +++ b/docs/resources/virtual_environment_sdn_zone_vxlan.md @@ -0,0 +1,56 @@ +--- +layout: page +title: proxmox_virtual_environment_sdn_zone_vxlan +parent: Resources +subcategory: Virtual Environment +description: |- + VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network (underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the outgoing physical interface. +--- + +# Resource: proxmox_virtual_environment_sdn_zone_vxlan + +VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network (underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the outgoing physical interface. + +## Example Usage + +```terraform +resource "proxmox_virtual_environment_sdn_zone_vxlan" "example" { + id = "vxlan1" + nodes = ["pve"] + peers = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] + mtu = 1450 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} +``` + + +## Schema + +### Required + +- `id` (String) The unique identifier of the SDN zone. +- `nodes` (Set of String) The Proxmox nodes which the zone and associated VNets should be deployed on +- `peers` (Set of String) A list of IP addresses of each node in the VXLAN zone. This can be external nodes reachable at this IP address. All nodes in the cluster need to be mentioned here + +### Optional + +- `dns` (String) DNS API server address. +- `dns_zone` (String) DNS domain name. Used to register hostnames, such as `.`. The DNS zone must already exist on the DNS server. +- `ipam` (String) IP Address Management system. +- `mtu` (Number) MTU value for the zone. +- `reverse_dns` (String) Reverse DNS API server address. + +## Import + +Import is supported using the following syntax: + +```shell +#!/usr/bin/env sh +# VXLAN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_vxlan.example vxlan1 +``` diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf new file mode 100644 index 00000000..2b70ab68 --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf @@ -0,0 +1,23 @@ +data "proxmox_virtual_environment_sdn_zone_evpn" "example" { + id = "evpn1" +} + +output "data_proxmox_virtual_environment_sdn_zone_evpn" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_evpn.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.nodes + controller = data.proxmox_virtual_environment_sdn_zone_evpn.example.controller + vrf_vxlan = data.proxmox_virtual_environment_sdn_zone_evpn.example.vrf_vxlan + advertise_subnets = data.proxmox_virtual_environment_sdn_zone_evpn.example.advertise_subnets + disable_arp_nd_suppression = data.proxmox_virtual_environment_sdn_zone_evpn.example.disable_arp_nd_suppression + exit_nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes + exit_nodes_local_routing = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes_local_routing + primary_exit_node = data.proxmox_virtual_environment_sdn_zone_evpn.example.primary_exit_node + rt_import = data.proxmox_virtual_environment_sdn_zone_evpn.example.rt_import + mtu = data.proxmox_virtual_environment_sdn_zone_evpn.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_evpn.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.reverse_dns + } +} diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf new file mode 100644 index 00000000..26f448c1 --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf @@ -0,0 +1,18 @@ +data "proxmox_virtual_environment_sdn_zone_qinq" "example" { + id = "qinq1" +} + +output "data_proxmox_virtual_environment_sdn_zone_qinq" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_qinq.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_qinq.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_qinq.example.bridge + service_vlan = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan + service_vlan_protocol = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan_protocol + mtu = data.proxmox_virtual_environment_sdn_zone_qinq.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_qinq.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.reverse_dns + } +} diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zone_simple/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zone_simple/data-source.tf new file mode 100644 index 00000000..452cb88f --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zone_simple/data-source.tf @@ -0,0 +1,15 @@ +data "proxmox_virtual_environment_sdn_zone_simple" "example" { + id = "simple1" +} + +output "data_proxmox_virtual_environment_sdn_zone_simple" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_simple.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_simple.example.nodes + mtu = data.proxmox_virtual_environment_sdn_zone_simple.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_simple.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_simple.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_simple.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_simple.example.reverse_dns + } +} diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf new file mode 100644 index 00000000..a1f0f9e4 --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf @@ -0,0 +1,16 @@ +data "proxmox_virtual_environment_sdn_zone_vlan" "example" { + id = "vlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vlan.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_vlan.example.bridge + mtu = data.proxmox_virtual_environment_sdn_zone_vlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.reverse_dns + } +} diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf new file mode 100644 index 00000000..2ca035e4 --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf @@ -0,0 +1,16 @@ +data "proxmox_virtual_environment_sdn_zone_vxlan" "example" { + id = "vxlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vxlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vxlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vxlan.example.nodes + peers = data.proxmox_virtual_environment_sdn_zone_vxlan.example.peers + mtu = data.proxmox_virtual_environment_sdn_zone_vxlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vxlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.reverse_dns + } +} diff --git a/examples/data-sources/proxmox_virtual_environment_sdn_zones/data-source.tf b/examples/data-sources/proxmox_virtual_environment_sdn_zones/data-source.tf new file mode 100644 index 00000000..a84abd4c --- /dev/null +++ b/examples/data-sources/proxmox_virtual_environment_sdn_zones/data-source.tf @@ -0,0 +1,25 @@ +# List all SDN zones +data "proxmox_virtual_environment_sdn_zones" "all" {} + +# List only EVPN zones +data "proxmox_virtual_environment_sdn_zones" "evpn_only" { + type = "evpn" +} + +# List only Simple zones +data "proxmox_virtual_environment_sdn_zones" "simple_only" { + type = "simple" +} + +output "data_proxmox_virtual_environment_sdn_zones_all" { + value = { + zones = data.proxmox_virtual_environment_sdn_zones.all.zones + } +} + +output "data_proxmox_virtual_environment_sdn_zones_filtered" { + value = { + evpn_zones = data.proxmox_virtual_environment_sdn_zones.evpn_only.zones + simple_zones = data.proxmox_virtual_environment_sdn_zones.simple_only.zones + } +} diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/import.sh b/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/import.sh new file mode 100644 index 00000000..77ec54de --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/import.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# EVPN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_evpn.example evpn1 diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/resource.tf b/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/resource.tf new file mode 100644 index 00000000..1993be2b --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_evpn/resource.tf @@ -0,0 +1,21 @@ +resource "proxmox_virtual_environment_sdn_zone_evpn" "example" { + id = "evpn1" + nodes = ["pve"] + controller = "evpn-controller1" + vrf_vxlan = 4000 + + # Optional attributes + advertise_subnets = true + disable_arp_nd_suppression = false + exit_nodes = ["pve-exit1", "pve-exit2"] + exit_nodes_local_routing = true + primary_exit_node = "pve-exit1" + rt_import = "65000:65000" + mtu = 1450 + + # Generic optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/import.sh b/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/import.sh new file mode 100644 index 00000000..ee146ff5 --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/import.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# QinQ SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_qinq.example qinq1 diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/resource.tf b/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/resource.tf new file mode 100644 index 00000000..3b5f1998 --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_qinq/resource.tf @@ -0,0 +1,14 @@ +resource "proxmox_virtual_environment_sdn_zone_qinq" "example" { + id = "qinq1" + nodes = ["pve"] + bridge = "vmbr0" + service_vlan = 100 + service_vlan_protocol = "802.1ad" + mtu = 1496 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_simple/import.sh b/examples/resources/proxmox_virtual_environment_sdn_zone_simple/import.sh new file mode 100644 index 00000000..72b735c0 --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_simple/import.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# Simple SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_simple.example simple1 diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_simple/resource.tf b/examples/resources/proxmox_virtual_environment_sdn_zone_simple/resource.tf new file mode 100644 index 00000000..ebe019e2 --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_simple/resource.tf @@ -0,0 +1,11 @@ +resource "proxmox_virtual_environment_sdn_zone_simple" "example" { + id = "simple1" + nodes = ["pve"] + mtu = 1500 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/import.sh b/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/import.sh new file mode 100644 index 00000000..72dd74cd --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/import.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# VLAN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_vlan.example vlan1 diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/resource.tf b/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/resource.tf new file mode 100644 index 00000000..1cd733da --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_vlan/resource.tf @@ -0,0 +1,12 @@ +resource "proxmox_virtual_environment_sdn_zone_vlan" "example" { + id = "vlan1" + nodes = ["pve"] + bridge = "vmbr0" + mtu = 1500 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/import.sh b/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/import.sh new file mode 100644 index 00000000..fb9cad68 --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/import.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# VXLAN SDN zone can be imported using its unique identifier (zone ID) +terraform import proxmox_virtual_environment_sdn_zone_vxlan.example vxlan1 diff --git a/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/resource.tf b/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/resource.tf new file mode 100644 index 00000000..833397bb --- /dev/null +++ b/examples/resources/proxmox_virtual_environment_sdn_zone_vxlan/resource.tf @@ -0,0 +1,12 @@ +resource "proxmox_virtual_environment_sdn_zone_vxlan" "example" { + id = "vxlan1" + nodes = ["pve"] + peers = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] + mtu = 1450 + + # Optional attributes + dns = "1.1.1.1" + dns_zone = "example.com" + ipam = "pve" + reverse_dns = "1.1.1.1" +} diff --git a/fwprovider/cluster/sdn/zone/datasource_evpn.go b/fwprovider/cluster/sdn/zone/datasource_evpn.go new file mode 100644 index 00000000..c504360a --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_evpn.go @@ -0,0 +1,99 @@ +/* + * 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 zone + +import ( + "context" + + "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/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ datasource.DataSource = &EVPNDataSource{} + _ datasource.DataSourceWithConfigure = &EVPNDataSource{} +) + +type EVPNDataSource struct { + generic *genericZoneDataSource +} + +func NewEVPNDataSource() datasource.DataSource { + return &EVPNDataSource{ + generic: newGenericZoneDataSource(zoneDataSourceConfig{ + typeNameSuffix: "_sdn_zone_evpn", + zoneType: zones.TypeEVPN, + modelFunc: func() zoneModel { return &evpnModel{} }, + }), + } +} + +func (d *EVPNDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about an EVPN Zone in Proxmox SDN.", + MarkdownDescription: "Retrieves information about an EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of " + + "spanning across multiple clusters.", + Attributes: genericDataSourceAttributesWith(map[string]schema.Attribute{ + "advertise_subnets": schema.BoolAttribute{ + Description: "Enable subnet advertisement for EVPN.", + Computed: true, + }, + "controller": schema.StringAttribute{ + Description: "EVPN controller address.", + Computed: true, + }, + "disable_arp_nd_suppression": schema.BoolAttribute{ + Description: "Disable ARP/ND suppression for EVPN.", + Computed: true, + }, + "exit_nodes": schema.SetAttribute{ + CustomType: stringset.Type{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + Description: "List of exit nodes for EVPN.", + ElementType: types.StringType, + Computed: true, + }, + "exit_nodes_local_routing": schema.BoolAttribute{ + Description: "Enable local routing for EVPN exit nodes.", + Computed: true, + }, + "primary_exit_node": schema.StringAttribute{ + Description: "Primary exit node for EVPN.", + Computed: true, + }, + "rt_import": schema.StringAttribute{ + Description: "Route target import for EVPN.", + MarkdownDescription: "Route target import for EVPN. Must be in the format ':' (e.g., '65000:65000').", + Computed: true, + }, + "vrf_vxlan": schema.Int64Attribute{ + Description: "VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different " + + "than the VXLAN-ID of the VNets.", + Computed: true, + }, + }), + } +} + +func (d *EVPNDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + d.generic.Metadata(ctx, req, resp) +} + +func (d *EVPNDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.generic.Configure(ctx, req, resp) +} + +func (d *EVPNDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + d.generic.Read(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_generic.go b/fwprovider/cluster/sdn/zone/datasource_generic.go new file mode 100644 index 00000000..0406030f --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_generic.go @@ -0,0 +1,166 @@ +/* + * 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 zone + +import ( + "context" + "errors" + "fmt" + "maps" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/config" + "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/api" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +type zoneDataSourceConfig struct { + typeNameSuffix string + zoneType string + modelFunc func() zoneModel +} + +type genericZoneDataSource struct { + client *zones.Client + config zoneDataSourceConfig +} + +func newGenericZoneDataSource(cfg zoneDataSourceConfig) *genericZoneDataSource { + return &genericZoneDataSource{config: cfg} +} + +func (d *genericZoneDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + d.config.typeNameSuffix +} + +func (d *genericZoneDataSource) 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 DataSource Configure Type", + fmt.Sprintf( + "Expected config.DataSource, got: %T", + req.ProviderData, + ), + ) + + return + } + + d.client = cfg.Client.Cluster().SDNZones() +} + +func genericDataSourceAttributesWith(extraAttributes map[string]schema.Attribute) map[string]schema.Attribute { + // Start with generic attributes as the base + result := map[string]schema.Attribute{ + "dns": schema.StringAttribute{ + Computed: true, + Description: "DNS API server address.", + }, + "dns_zone": schema.StringAttribute{ + Computed: true, + Description: "DNS domain name. The DNS zone must already exist on the DNS server.", + MarkdownDescription: "DNS domain name. Used to register hostnames, such as `.`. " + + "The DNS zone must already exist on the DNS server.", + }, + "id": schema.StringAttribute{ + Description: "The unique identifier of the SDN zone.", + Required: true, + }, + "ipam": schema.StringAttribute{ + Computed: true, + Description: "IP Address Management system.", + }, + "mtu": schema.Int64Attribute{ + Computed: true, + Description: "MTU value for the zone.", + }, + "nodes": schema.SetAttribute{ + CustomType: stringset.Type{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + Description: "The Proxmox nodes which the zone and associated VNets are deployed on", + ElementType: types.StringType, + Computed: true, + }, + "reverse_dns": schema.StringAttribute{ + Computed: true, + Description: "Reverse DNS API server address.", + }, + } + + // Add extra attributes, allowing them to override generic ones if needed + if extraAttributes != nil { + maps.Copy(result, extraAttributes) + } + + return result +} + +func (d *genericZoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + state := d.config.modelFunc() + resp.Diagnostics.Append(req.Config.Get(ctx, state)...) + + if resp.Diagnostics.HasError() { + return + } + + zone, err := d.client.GetZone(ctx, state.getID()) + if err != nil { + if errors.Is(err, api.ErrResourceDoesNotExist) { + resp.Diagnostics.AddError( + "SDN Zone Not Found", + fmt.Sprintf("SDN zone with ID '%s' was not found", state.getID()), + ) + + return + } + + resp.Diagnostics.AddError( + "Unable to Read SDN Zone", + err.Error(), + ) + + return + } + + // Verify the zone type matches what this datasource expects + if zone.Type != nil && *zone.Type != d.config.zoneType { + resp.Diagnostics.AddError( + "SDN Zone Type Mismatch", + fmt.Sprintf( + "Expected zone type '%s' but found '%s' for zone '%s'", + d.config.zoneType, + *zone.Type, + zone.ID, + ), + ) + + return + } + + readModel := d.config.modelFunc() + diags := &diag.Diagnostics{} + readModel.importFromAPI(zone.ID, zone, diags) + resp.Diagnostics.Append(*diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_qinq.go b/fwprovider/cluster/sdn/zone/datasource_qinq.go new file mode 100644 index 00000000..2060e573 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_qinq.go @@ -0,0 +1,74 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ datasource.DataSource = &QinQDataSource{} + _ datasource.DataSourceWithConfigure = &QinQDataSource{} +) + +type QinQDataSource struct { + generic *genericZoneDataSource +} + +func NewQinQDataSource() datasource.DataSource { + return &QinQDataSource{ + generic: newGenericZoneDataSource(zoneDataSourceConfig{ + typeNameSuffix: "_sdn_zone_qinq", + zoneType: zones.TypeQinQ, + modelFunc: func() zoneModel { return &qinqModel{} }, + }), + } +} + +func (d *QinQDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a QinQ Zone in Proxmox SDN.", + MarkdownDescription: "Retrieves information about a QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of " + + "VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner " + + "VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this " + + "configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. " + + "For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500.", + Attributes: genericDataSourceAttributesWith(map[string]schema.Attribute{ + "bridge": schema.StringAttribute{ + Description: "A local, VLAN-aware bridge that is already configured on each local node", + Computed: true, + }, + "service_vlan": schema.Int64Attribute{ + Description: "Service VLAN tag for QinQ.", + MarkdownDescription: "Service VLAN tag for QinQ. The tag must be between `1` and `4094`.", + Computed: true, + }, + "service_vlan_protocol": schema.StringAttribute{ + Description: "Service VLAN protocol for QinQ.", + MarkdownDescription: "Service VLAN protocol for QinQ. The protocol must be `802.1ad` or `802.1q`.", + Computed: true, + }, + }), + } +} + +func (d *QinQDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + d.generic.Metadata(ctx, req, resp) +} + +func (d *QinQDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.generic.Configure(ctx, req, resp) +} + +func (d *QinQDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + d.generic.Read(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_simple.go b/fwprovider/cluster/sdn/zone/datasource_simple.go new file mode 100644 index 00000000..04a718c3 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_simple.go @@ -0,0 +1,57 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ datasource.DataSource = &SimpleDataSource{} + _ datasource.DataSourceWithConfigure = &SimpleDataSource{} +) + +type SimpleDataSource struct { + generic *genericZoneDataSource +} + +func NewSimpleDataSource() datasource.DataSource { + return &SimpleDataSource{ + generic: newGenericZoneDataSource(zoneDataSourceConfig{ + typeNameSuffix: "_sdn_zone_simple", + zoneType: zones.TypeSimple, + modelFunc: func() zoneModel { return &simpleModel{} }, + }), + } +} + +func (d *SimpleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a Simple Zone in Proxmox SDN.", + MarkdownDescription: "Retrieves information about a Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. " + + "This bridge is not linked to a physical interface, and VM traffic is only local on each the node. " + + "It can be used in NAT or routed setups.", + Attributes: genericDataSourceAttributesWith(nil), + } +} + +func (d *SimpleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + d.generic.Metadata(ctx, req, resp) +} + +func (d *SimpleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.generic.Configure(ctx, req, resp) +} + +func (d *SimpleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + d.generic.Read(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_vlan.go b/fwprovider/cluster/sdn/zone/datasource_vlan.go new file mode 100644 index 00000000..7cb466f9 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_vlan.go @@ -0,0 +1,64 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ datasource.DataSource = &VLANDataSource{} + _ datasource.DataSourceWithConfigure = &VLANDataSource{} +) + +type VLANDataSource struct { + generic *genericZoneDataSource +} + +func NewVLANDataSource() datasource.DataSource { + return &VLANDataSource{ + generic: newGenericZoneDataSource(zoneDataSourceConfig{ + typeNameSuffix: "_sdn_zone_vlan", + zoneType: zones.TypeVLAN, + modelFunc: func() zoneModel { return &vlanModel{} }, + }), + } +} + +func (d *VLANDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a VLAN Zone in Proxmox SDN.", + MarkdownDescription: "Retrieves information about a VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the " + + "node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. " + + "This allows connectivity of VMs between different nodes.", + Attributes: genericDataSourceAttributesWith(map[string]schema.Attribute{ + "bridge": schema.StringAttribute{ + Description: "Bridge interface for VLAN.", + MarkdownDescription: "The local bridge or OVS switch, already configured on _each_ node that allows " + + "node-to-node connection.", + Computed: true, + }, + }), + } +} + +func (d *VLANDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + d.generic.Metadata(ctx, req, resp) +} + +func (d *VLANDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.generic.Configure(ctx, req, resp) +} + +func (d *VLANDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + d.generic.Read(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_vxlan.go b/fwprovider/cluster/sdn/zone/datasource_vxlan.go new file mode 100644 index 00000000..5c8d6b3c --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_vxlan.go @@ -0,0 +1,75 @@ +/* + * 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 zone + +import ( + "context" + + "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/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ datasource.DataSource = &VXLANDataSource{} + _ datasource.DataSourceWithConfigure = &VXLANDataSource{} +) + +type VXLANDataSource struct { + generic *genericZoneDataSource +} + +func NewVXLANDataSource() datasource.DataSource { + return &VXLANDataSource{ + generic: newGenericZoneDataSource(zoneDataSourceConfig{ + typeNameSuffix: "_sdn_zone_vxlan", + zoneType: zones.TypeVXLAN, + modelFunc: func() zoneModel { return &vxlanModel{} }, + }), + } +} + +func (d *VXLANDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a VXLAN Zone in Proxmox SDN.", + MarkdownDescription: "Retrieves information about a VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network " + + "(underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default " + + "destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity " + + "between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the " + + "outgoing physical interface.", + Attributes: genericDataSourceAttributesWith(map[string]schema.Attribute{ + "peers": schema.SetAttribute{ + CustomType: stringset.Type{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + Description: "A list of IP addresses of each node in the VXLAN zone.", + MarkdownDescription: "A list of IP addresses of each node in the VXLAN zone. " + + "This can be external nodes reachable at this IP address. All nodes in the cluster need to be " + + "mentioned here", + ElementType: types.StringType, + Computed: true, + }, + }), + } +} + +func (d *VXLANDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + d.generic.Metadata(ctx, req, resp) +} + +func (d *VXLANDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.generic.Configure(ctx, req, resp) +} + +func (d *VXLANDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + d.generic.Read(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/datasource_zones.go b/fwprovider/cluster/sdn/zone/datasource_zones.go new file mode 100644 index 00000000..f412c50f --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_zones.go @@ -0,0 +1,282 @@ +/* + * 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 zone + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/config" + "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +// Ensure the implementation satisfies the required interfaces. +var ( + _ datasource.DataSource = &zonesDataSource{} + _ datasource.DataSourceWithConfigure = &zonesDataSource{} +) + +// zonesDataSource is the data source implementation for SDN zones. +type zonesDataSource struct { + client *zones.Client +} + +// zonesDataSourceModel represents the data source model for listing zones. +type zonesDataSourceModel struct { + Type types.String `tfsdk:"type"` + Zones types.List `tfsdk:"zones"` +} + +// zoneDataModel represents individual zone data in the list. +type zoneDataModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + IPAM types.String `tfsdk:"ipam"` + DNS types.String `tfsdk:"dns"` + ReverseDNS types.String `tfsdk:"reverse_dns"` + DNSZone types.String `tfsdk:"dns_zone"` + Nodes stringset.Value `tfsdk:"nodes"` + MTU types.Int64 `tfsdk:"mtu"` + Bridge types.String `tfsdk:"bridge"` + ServiceVLAN types.Int64 `tfsdk:"service_vlan"` + ServiceVLANProtocol types.String `tfsdk:"service_vlan_protocol"` + Peers stringset.Value `tfsdk:"peers"` + AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"` + Controller types.String `tfsdk:"controller"` + DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"` + ExitNodes stringset.Value `tfsdk:"exit_nodes"` + ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"` + PrimaryExitNode types.String `tfsdk:"primary_exit_node"` + RouteTargetImport types.String `tfsdk:"rt_import"` + VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"` +} + +// Configure adds the provider-configured client to the data source. +func (d *zonesDataSource) 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 DataSource Configure Type", + fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData), + ) + + return + } + + d.client = cfg.Client.Cluster().SDNZones() +} + +// Metadata returns the data source type name. +func (d *zonesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_sdn_zones" +} + +// Schema defines the schema for the data source. +func (d *zonesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about all SDN Zones in Proxmox.", + MarkdownDescription: "Retrieves information about all SDN Zones in Proxmox. " + + "This data source can optionally filter zones by type.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: "Filter zones by type (simple, vlan, qinq, vxlan, evpn).", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("simple", "vlan", "qinq", "vxlan", "evpn"), + }, + }, + "zones": schema.ListAttribute{ + Description: "List of SDN zones.", + Computed: true, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "type": types.StringType, + "ipam": types.StringType, + "dns": types.StringType, + "reverse_dns": types.StringType, + "dns_zone": types.StringType, + "nodes": types.SetType{ + ElemType: types.StringType, + }, + "mtu": types.Int64Type, + "bridge": types.StringType, + "service_vlan": types.Int64Type, + "service_vlan_protocol": types.StringType, + "peers": types.SetType{ + ElemType: types.StringType, + }, + "advertise_subnets": types.BoolType, + "controller": types.StringType, + "disable_arp_nd_suppression": types.BoolType, + "exit_nodes": types.SetType{ + ElemType: types.StringType, + }, + "exit_nodes_local_routing": types.BoolType, + "primary_exit_node": types.StringType, + "rt_import": types.StringType, + "vrf_vxlan": types.Int64Type, + }, + }, + }, + }, + } +} + +// Read fetches all SDN zones from the Proxmox VE API. +func (d *zonesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data zonesDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + zonesList, err := d.client.GetZones(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Read SDN Zones", + err.Error(), + ) + + return + } + + filteredZones := zonesList + + if !data.Type.IsNull() && !data.Type.IsUnknown() { + filterType := data.Type.ValueString() + filteredZones = make([]zones.ZoneData, 0) + + for _, zone := range zonesList { + if zone.Type != nil && *zone.Type == filterType { + filteredZones = append(filteredZones, zone) + } + } + } + + // Convert zones to list elements + zoneElements := make([]attr.Value, len(filteredZones)) + for i, zone := range filteredZones { + diags := &resp.Diagnostics + + zoneData := zoneDataModel{ + ID: types.StringValue(zone.ID), + Type: types.StringPointerValue(zone.Type), + IPAM: types.StringPointerValue(zone.IPAM), + DNS: types.StringPointerValue(zone.DNS), + ReverseDNS: types.StringPointerValue(zone.ReverseDNS), + DNSZone: types.StringPointerValue(zone.DNSZone), + Nodes: stringset.NewValueString(zone.Nodes, diags, stringset.WithSeparator(",")), + MTU: types.Int64PointerValue(zone.MTU), + Bridge: types.StringPointerValue(zone.Bridge), + ServiceVLAN: types.Int64PointerValue(zone.ServiceVLAN), + ServiceVLANProtocol: types.StringPointerValue(zone.ServiceVLANProtocol), + Peers: stringset.NewValueString(zone.Peers, diags, stringset.WithSeparator(",")), + AdvertiseSubnets: types.BoolPointerValue(zone.AdvertiseSubnets.PointerBool()), + Controller: types.StringPointerValue(zone.Controller), + DisableARPNDSuppression: types.BoolPointerValue(zone.DisableARPNDSuppression.PointerBool()), + ExitNodes: stringset.NewValueString(zone.ExitNodes, diags, stringset.WithSeparator(",")), + ExitNodesLocalRouting: types.BoolPointerValue(zone.ExitNodesLocalRouting.PointerBool()), + PrimaryExitNode: types.StringPointerValue(zone.ExitNodesPrimary), + RouteTargetImport: types.StringPointerValue(zone.RouteTargetImport), + VRFVXLANID: types.Int64PointerValue(zone.VRFVXLANID), + } + + objValue, objDiag := types.ObjectValueFrom(ctx, map[string]attr.Type{ + "id": types.StringType, + "type": types.StringType, + "ipam": types.StringType, + "dns": types.StringType, + "reverse_dns": types.StringType, + "dns_zone": types.StringType, + "nodes": types.SetType{ + ElemType: types.StringType, + }, + "mtu": types.Int64Type, + "bridge": types.StringType, + "service_vlan": types.Int64Type, + "service_vlan_protocol": types.StringType, + "peers": types.SetType{ + ElemType: types.StringType, + }, + "advertise_subnets": types.BoolType, + "controller": types.StringType, + "disable_arp_nd_suppression": types.BoolType, + "exit_nodes": types.SetType{ + ElemType: types.StringType, + }, + "exit_nodes_local_routing": types.BoolType, + "primary_exit_node": types.StringType, + "rt_import": types.StringType, + "vrf_vxlan": types.Int64Type, + }, zoneData) + resp.Diagnostics.Append(objDiag...) + + if resp.Diagnostics.HasError() { + return + } + + zoneElements[i] = objValue + } + + listValue, listDiag := types.ListValue(types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "type": types.StringType, + "ipam": types.StringType, + "dns": types.StringType, + "reverse_dns": types.StringType, + "dns_zone": types.StringType, + "nodes": types.SetType{ + ElemType: types.StringType, + }, + "mtu": types.Int64Type, + "bridge": types.StringType, + "service_vlan": types.Int64Type, + "service_vlan_protocol": types.StringType, + "peers": types.SetType{ + ElemType: types.StringType, + }, + "advertise_subnets": types.BoolType, + "controller": types.StringType, + "disable_arp_nd_suppression": types.BoolType, + "exit_nodes": types.SetType{ + ElemType: types.StringType, + }, + "exit_nodes_local_routing": types.BoolType, + "primary_exit_node": types.StringType, + "rt_import": types.StringType, + "vrf_vxlan": types.Int64Type, + }, + }, zoneElements) + resp.Diagnostics.Append(listDiag...) + + if resp.Diagnostics.HasError() { + return + } + + data.Zones = listValue + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// NewZonesDataSource returns a new data source for SDN zones. +func NewZonesDataSource() datasource.DataSource { + return &zonesDataSource{} +} diff --git a/fwprovider/cluster/sdn/zone/datasource_zones_test.go b/fwprovider/cluster/sdn/zone/datasource_zones_test.go new file mode 100644 index 00000000..6c2140fb --- /dev/null +++ b/fwprovider/cluster/sdn/zone/datasource_zones_test.go @@ -0,0 +1,259 @@ +//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 zone_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccDataSourceSDNZoneSimple(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create simple zone and read with datasource", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_simple" "test" { + id = "dstest1" + nodes = ["pve"] + mtu = 1500 + } + + data "proxmox_virtual_environment_sdn_zone_simple" "test" { + id = proxmox_virtual_environment_sdn_zone_simple.test.id + } + `), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_sdn_zone_simple.test", map[string]string{ + "id": "dstest1", + "mtu": "1500", + }), + test.ResourceAttributes("data.proxmox_virtual_environment_sdn_zone_simple.test", map[string]string{ + "nodes.#": "1", + "nodes.0": "pve", + }), + ), + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccDataSourceSDNZoneVLAN(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create VLAN zone and read with datasource", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vlan" "test" { + id = "dstest2" + nodes = ["pve"] + bridge = "vmbr0" + mtu = 1496 + } + + data "proxmox_virtual_environment_sdn_zone_vlan" "test" { + id = proxmox_virtual_environment_sdn_zone_vlan.test.id + } + `), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_sdn_zone_vlan.test", map[string]string{ + "id": "dstest2", + "bridge": "vmbr0", + "mtu": "1496", + "nodes.#": "1", + "nodes.0": "pve", + }), + ), + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccDataSourceSDNZoneQinQ(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create QinQ zone and read with datasource", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_qinq" "test" { + id = "dstest3" + nodes = ["pve"] + bridge = "vmbr0" + service_vlan = 100 + service_vlan_protocol = "802.1ad" + mtu = 1492 + } + + data "proxmox_virtual_environment_sdn_zone_qinq" "test" { + id = proxmox_virtual_environment_sdn_zone_qinq.test.id + } + `), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_sdn_zone_qinq.test", map[string]string{ + "id": "dstest3", + "bridge": "vmbr0", + "service_vlan": "100", + "service_vlan_protocol": "802.1ad", + "mtu": "1492", + "nodes.#": "1", + "nodes.0": "pve", + }), + ), + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccDataSourceSDNZoneVXLAN(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create VXLAN zone and read with datasource", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vxlan" "test" { + id = "dstest4" + nodes = ["pve"] + peers = ["10.0.0.1", "10.0.0.2"] + mtu = 1450 + } + + data "proxmox_virtual_environment_sdn_zone_vxlan" "test" { + id = proxmox_virtual_environment_sdn_zone_vxlan.test.id + } + `), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_sdn_zone_vxlan.test", map[string]string{ + "id": "dstest4", + "mtu": "1450", + "nodes.#": "1", + "nodes.0": "pve", + "peers.#": "2", + "peers.0": "10.0.0.1", + "peers.1": "10.0.0.2", + }), + ), + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccDataSourceSDNZones(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create multiple zones and read with zones datasource", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_simple" "test1" { + id = "dstest6" + nodes = ["pve"] + mtu = 1500 + } + + resource "proxmox_virtual_environment_sdn_zone_vlan" "test2" { + id = "dstest7" + nodes = ["pve"] + bridge = "vmbr0" + mtu = 1496 + } + + data "proxmox_virtual_environment_sdn_zones" "all" { + depends_on = [ + proxmox_virtual_environment_sdn_zone_simple.test1, + proxmox_virtual_environment_sdn_zone_vlan.test2 + ] + } + + data "proxmox_virtual_environment_sdn_zones" "simple_only" { + type = "simple" + depends_on = [ + proxmox_virtual_environment_sdn_zone_simple.test1, + proxmox_virtual_environment_sdn_zone_vlan.test2 + ] + } + `), + Check: resource.ComposeTestCheckFunc( + // Check that all zones datasource returns multiple zones + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_sdn_zones.all", "zones.#"), + + // Check that filtered datasource works + resource.TestCheckResourceAttr("data.proxmox_virtual_environment_sdn_zones.simple_only", "type", "simple"), + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_sdn_zones.simple_only", "zones.#"), + ), + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} diff --git a/fwprovider/cluster/sdn/zone/resource_evpn.go b/fwprovider/cluster/sdn/zone/resource_evpn.go new file mode 100644 index 00000000..fff5f8e9 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_evpn.go @@ -0,0 +1,158 @@ +/* + * 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 zone + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" + + proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" +) + +var ( + _ resource.ResourceWithConfigure = &EVPNResource{} + _ resource.ResourceWithImportState = &EVPNResource{} +) + +type evpnModel struct { + genericModel + + AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"` + Controller types.String `tfsdk:"controller"` + DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"` + ExitNodes stringset.Value `tfsdk:"exit_nodes"` + ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"` + PrimaryExitNode types.String `tfsdk:"primary_exit_node"` + RouteTargetImport types.String `tfsdk:"rt_import"` + VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"` +} + +func (m *evpnModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) { + m.genericModel.importFromAPI(name, data, diags) + + m.AdvertiseSubnets = types.BoolPointerValue(data.AdvertiseSubnets.PointerBool()) + m.Controller = types.StringPointerValue(data.Controller) + m.DisableARPNDSuppression = types.BoolPointerValue(data.DisableARPNDSuppression.PointerBool()) + m.ExitNodes = stringset.NewValueString(data.ExitNodes, diags, stringset.WithSeparator(",")) + m.ExitNodesLocalRouting = types.BoolPointerValue(data.ExitNodesLocalRouting.PointerBool()) + m.PrimaryExitNode = types.StringPointerValue(data.ExitNodesPrimary) + m.RouteTargetImport = types.StringPointerValue(data.RouteTargetImport) + m.VRFVXLANID = types.Int64PointerValue(data.VRFVXLANID) +} + +func (m *evpnModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData { + data := m.genericModel.toAPIRequestBody(ctx, diags) + + data.AdvertiseSubnets = proxmoxtypes.CustomBoolPtr(m.AdvertiseSubnets.ValueBoolPointer()) + data.Controller = m.Controller.ValueStringPointer() + data.DisableARPNDSuppression = proxmoxtypes.CustomBoolPtr(m.DisableARPNDSuppression.ValueBoolPointer()) + data.ExitNodes = m.ExitNodes.ValueStringPointer(ctx, diags, stringset.WithSeparator(",")) + data.ExitNodesLocalRouting = proxmoxtypes.CustomBoolPtr(m.ExitNodesLocalRouting.ValueBoolPointer()) + data.ExitNodesPrimary = m.PrimaryExitNode.ValueStringPointer() + data.RouteTargetImport = m.RouteTargetImport.ValueStringPointer() + data.VRFVXLANID = m.VRFVXLANID.ValueInt64Pointer() + + return data +} + +type EVPNResource struct { + generic *genericZoneResource +} + +func NewEVPNResource() resource.Resource { + return &EVPNResource{ + generic: newGenericZoneResource(zoneResourceConfig{ + typeNameSuffix: "_sdn_zone_evpn", + zoneType: zones.TypeEVPN, + modelFunc: func() zoneModel { return &evpnModel{} }, + }).(*genericZoneResource), + } +} + +func (r *EVPNResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "EVPN Zone in Proxmox SDN.", + MarkdownDescription: "EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of " + + "spanning across multiple clusters.", + Attributes: genericAttributesWith(map[string]schema.Attribute{ + "advertise_subnets": schema.BoolAttribute{ + Description: "Enable subnet advertisement for EVPN.", + Optional: true, + }, + "controller": schema.StringAttribute{ + Description: "EVPN controller address.", + Required: true, + }, + "disable_arp_nd_suppression": schema.BoolAttribute{ + Description: "Disable ARP/ND suppression for EVPN.", + Optional: true, + }, + "exit_nodes": stringset.ResourceAttribute("List of exit nodes for EVPN.", ""), + "exit_nodes_local_routing": schema.BoolAttribute{ + Description: "Enable local routing for EVPN exit nodes.", + Optional: true, + }, + "primary_exit_node": schema.StringAttribute{ + Description: "Primary exit node for EVPN.", + Optional: true, + }, + "rt_import": schema.StringAttribute{ + Description: "Route target import for EVPN.", + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^(\d+):(\d+)$`), + "must be in the format ':' (e.g., '65000:65000')", + ), + }, + Optional: true, + }, + "vrf_vxlan": schema.Int64Attribute{ + Description: "VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different " + + "than the VXLAN-ID of the VNets.", + Required: true, + }, + }), + } +} + +func (r *EVPNResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + r.generic.Metadata(ctx, req, resp) +} + +func (r *EVPNResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.generic.Configure(ctx, req, resp) +} + +func (r *EVPNResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + r.generic.Create(ctx, req, resp) +} + +func (r *EVPNResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + r.generic.Read(ctx, req, resp) +} + +func (r *EVPNResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + r.generic.Update(ctx, req, resp) +} + +func (r *EVPNResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + r.generic.Delete(ctx, req, resp) +} + +func (r *EVPNResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + r.generic.ImportState(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/resource_generic.go b/fwprovider/cluster/sdn/zone/resource_generic.go new file mode 100644 index 00000000..3ade630a --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_generic.go @@ -0,0 +1,292 @@ +/* + * 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 zone + +import ( + "context" + "errors" + "fmt" + "maps" + "regexp" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/config" + "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/api" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" + "github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type genericModel struct { + ID types.String `tfsdk:"id"` + IPAM types.String `tfsdk:"ipam"` + DNS types.String `tfsdk:"dns"` + ReverseDNS types.String `tfsdk:"reverse_dns"` + DNSZone types.String `tfsdk:"dns_zone"` + Nodes stringset.Value `tfsdk:"nodes"` + MTU types.Int64 `tfsdk:"mtu"` +} + +func (m *genericModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) { + m.ID = types.StringValue(name) + + m.DNS = types.StringPointerValue(data.DNS) + m.DNSZone = types.StringPointerValue(data.DNSZone) + m.IPAM = types.StringPointerValue(data.IPAM) + m.MTU = types.Int64PointerValue(data.MTU) + m.Nodes = stringset.NewValueString(data.Nodes, diags, stringset.WithSeparator(",")) + m.ReverseDNS = types.StringPointerValue(data.ReverseDNS) +} + +func (m *genericModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData { + data := &zones.ZoneRequestData{} + + data.ID = m.ID.ValueString() + + 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(ctx, diags, stringset.WithSeparator(",")) + data.MTU = m.MTU.ValueInt64Pointer() + + return data +} + +func (m *genericModel) getID() string { + return m.ID.ValueString() +} + +func genericAttributesWith(extraAttributes map[string]schema.Attribute) map[string]schema.Attribute { + // Start with generic attributes as the base + result := map[string]schema.Attribute{ + "dns": schema.StringAttribute{ + Optional: true, + Description: "DNS API server address.", + }, + "dns_zone": schema.StringAttribute{ + Optional: true, + Description: "DNS domain name. The DNS zone must already exist on the DNS server.", + MarkdownDescription: "DNS domain name. Used to register hostnames, such as `.`. " + + "The DNS zone must already exist on the DNS server.", + }, + "id": schema.StringAttribute{ + Description: "The unique identifier of the SDN zone.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + // https://github.com/proxmox/pve-network/blob/faaf96a8378a3e41065018562c09c3de0aa434f5/src/PVE/Network/SDN/Zones/Plugin.pm#L34 + stringvalidator.RegexMatches( + regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*[A-Za-z0-9]$`), + "must be a valid zone identifier", + ), + stringvalidator.LengthAtMost(8), + }, + }, + "ipam": schema.StringAttribute{ + Optional: true, + Description: "IP Address Management system.", + }, + "mtu": schema.Int64Attribute{ + Optional: true, + Description: "MTU value for the zone.", + }, + "nodes": stringset.ResourceAttribute("The Proxmox nodes which the zone and associated VNets should be deployed on", "", stringset.WithRequired()), + "reverse_dns": schema.StringAttribute{ + Optional: true, + Description: "Reverse DNS API server address.", + }, + } + + // Add extra attributes, allowing them to override generic ones if needed + if extraAttributes != nil { + maps.Copy(result, extraAttributes) + } + + return result +} + +type zoneModel interface { + importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) + toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData + getID() string +} + +type zoneResourceConfig struct { + typeNameSuffix string + zoneType string + modelFunc func() zoneModel +} + +type genericZoneResource struct { + client *zones.Client + config zoneResourceConfig +} + +func newGenericZoneResource(cfg zoneResourceConfig) resource.Resource { + return &genericZoneResource{config: cfg} +} + +func (r *genericZoneResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + r.config.typeNameSuffix +} + +func (r *genericZoneResource) 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 *genericZoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + plan := r.config.modelFunc() + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + + if resp.Diagnostics.HasError() { + return + } + + diags := &diag.Diagnostics{} + reqData := plan.toAPIRequestBody(ctx, diags) + resp.Diagnostics.Append(*diags...) + + reqData.Type = ptr.Ptr(r.config.zoneType) + + if err := r.client.CreateZone(ctx, reqData); err != nil { + resp.Diagnostics.AddError( + "Unable to Create SDN Zone", + err.Error(), + ) + + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *genericZoneResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + state := r.config.modelFunc() + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + + if resp.Diagnostics.HasError() { + return + } + + zone, err := r.client.GetZone(ctx, state.getID()) + 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 := r.config.modelFunc() + diags := &diag.Diagnostics{} + readModel.importFromAPI(zone.ID, zone, diags) + resp.Diagnostics.Append(*diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...) +} + +func (r *genericZoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + plan := r.config.modelFunc() + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + + if resp.Diagnostics.HasError() { + return + } + + diags := &diag.Diagnostics{} + reqData := plan.toAPIRequestBody(ctx, diags) + resp.Diagnostics.Append(*diags...) + + if err := r.client.UpdateZone(ctx, reqData); err != nil { + resp.Diagnostics.AddError( + "Unable to Update SDN Zone", + err.Error(), + ) + + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *genericZoneResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + state := r.config.modelFunc() + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + + if resp.Diagnostics.HasError() { + return + } + + if err := r.client.DeleteZone(ctx, state.getID()); err != nil && + !errors.Is(err, api.ErrResourceDoesNotExist) { + resp.Diagnostics.AddError( + "Unable to Delete SDN Zone", + err.Error(), + ) + } +} + +func (r *genericZoneResource) 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(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error()) + return + } + + resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN Zone %s", req.ID), err.Error()) + + return + } + + readModel := r.config.modelFunc() + diags := &diag.Diagnostics{} + readModel.importFromAPI(zone.ID, zone, diags) + resp.Diagnostics.Append(*diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...) +} + +// Schema is required to satisfy the resource.Resource interface. It should be implemented by the specific resource. +func (r *genericZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, _ *resource.SchemaResponse) { + // Intentionally left blank. Should be set by the specific resource. +} diff --git a/fwprovider/cluster/sdn/zone/resource_qinq.go b/fwprovider/cluster/sdn/zone/resource_qinq.go new file mode 100644 index 00000000..ca89398d --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_qinq.go @@ -0,0 +1,124 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ resource.ResourceWithConfigure = &QinQResource{} + _ resource.ResourceWithImportState = &QinQResource{} +) + +type qinqModel struct { + vlanModel + + ServiceVLAN types.Int64 `tfsdk:"service_vlan"` + ServiceVLANProtocol types.String `tfsdk:"service_vlan_protocol"` +} + +func (m *qinqModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) { + m.vlanModel.importFromAPI(name, data, diags) + + m.ServiceVLAN = types.Int64PointerValue(data.ServiceVLAN) + m.ServiceVLANProtocol = types.StringPointerValue(data.ServiceVLANProtocol) +} + +func (m *qinqModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData { + data := m.vlanModel.toAPIRequestBody(ctx, diags) + + data.ServiceVLAN = m.ServiceVLAN.ValueInt64Pointer() + data.ServiceVLANProtocol = m.ServiceVLANProtocol.ValueStringPointer() + + return data +} + +type QinQResource struct { + generic *genericZoneResource +} + +func NewQinQResource() resource.Resource { + return &QinQResource{ + generic: newGenericZoneResource(zoneResourceConfig{ + typeNameSuffix: "_sdn_zone_qinq", + zoneType: zones.TypeQinQ, + modelFunc: func() zoneModel { return &qinqModel{} }, + }).(*genericZoneResource), + } +} + +func (r *QinQResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "QinQ Zone in Proxmox SDN.", + MarkdownDescription: "QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of " + + "VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner " + + "VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this " + + "configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. " + + "For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500.", + Attributes: genericAttributesWith(map[string]schema.Attribute{ + "bridge": schema.StringAttribute{ + Description: "A local, VLAN-aware bridge that is already configured on each local node", + Required: true, + }, + "service_vlan": schema.Int64Attribute{ + Description: "Service VLAN tag for QinQ.", + MarkdownDescription: "Service VLAN tag for QinQ. The tag must be between `1` and `4094`.", + Validators: []validator.Int64{ + int64validator.Between(int64(1), int64(4094)), + }, + Required: true, + }, + "service_vlan_protocol": schema.StringAttribute{ + Description: "Service VLAN protocol for QinQ.", + MarkdownDescription: "Service VLAN protocol for QinQ. The protocol must be `802.1ad` or `802.1q`.", + Validators: []validator.String{ + stringvalidator.OneOf("802.1ad", "802.1q"), + }, + Optional: true, + }, + }), + } +} + +func (r *QinQResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + r.generic.Metadata(ctx, req, resp) +} + +func (r *QinQResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.generic.Configure(ctx, req, resp) +} + +func (r *QinQResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + r.generic.Create(ctx, req, resp) +} + +func (r *QinQResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + r.generic.Read(ctx, req, resp) +} + +func (r *QinQResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + r.generic.Update(ctx, req, resp) +} + +func (r *QinQResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + r.generic.Delete(ctx, req, resp) +} + +func (r *QinQResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + r.generic.ImportState(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/resource_simple.go b/fwprovider/cluster/sdn/zone/resource_simple.go new file mode 100644 index 00000000..71866b7d --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_simple.go @@ -0,0 +1,77 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ resource.ResourceWithConfigure = &SimpleResource{} + _ resource.ResourceWithImportState = &SimpleResource{} +) + +type simpleModel struct { + genericModel +} + +type SimpleResource struct { + generic *genericZoneResource +} + +func NewSimpleResource() resource.Resource { + return &SimpleResource{ + generic: newGenericZoneResource(zoneResourceConfig{ + typeNameSuffix: "_sdn_zone_simple", + zoneType: zones.TypeSimple, + modelFunc: func() zoneModel { return &simpleModel{} }, + }).(*genericZoneResource), + } +} + +func (r *SimpleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Simple Zone in Proxmox SDN.", + MarkdownDescription: "Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. " + + "This bridge is not linked to a physical interface, and VM traffic is only local on each the node. " + + "It can be used in NAT or routed setups.", + Attributes: genericAttributesWith(nil), + } +} + +func (r *SimpleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + r.generic.Metadata(ctx, req, resp) +} + +func (r *SimpleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.generic.Configure(ctx, req, resp) +} + +func (r *SimpleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + r.generic.Create(ctx, req, resp) +} + +func (r *SimpleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + r.generic.Read(ctx, req, resp) +} + +func (r *SimpleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + r.generic.Update(ctx, req, resp) +} + +func (r *SimpleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + r.generic.Delete(ctx, req, resp) +} + +func (r *SimpleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + r.generic.ImportState(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/resource_vlan.go b/fwprovider/cluster/sdn/zone/resource_vlan.go new file mode 100644 index 00000000..c3f61312 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_vlan.go @@ -0,0 +1,102 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ resource.ResourceWithConfigure = &VLANResource{} + _ resource.ResourceWithImportState = &VLANResource{} +) + +type vlanModel struct { + genericModel + + Bridge types.String `tfsdk:"bridge"` +} + +func (m *vlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) { + m.genericModel.importFromAPI(name, data, diags) + + m.Bridge = types.StringPointerValue(data.Bridge) +} + +func (m *vlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData { + data := m.genericModel.toAPIRequestBody(ctx, diags) + + data.Bridge = m.Bridge.ValueStringPointer() + + return data +} + +type VLANResource struct { + generic *genericZoneResource +} + +func NewVLANResource() resource.Resource { + return &VLANResource{ + generic: newGenericZoneResource(zoneResourceConfig{ + typeNameSuffix: "_sdn_zone_vlan", + zoneType: zones.TypeVLAN, + modelFunc: func() zoneModel { return &vlanModel{} }, + }).(*genericZoneResource), + } +} + +func (r *VLANResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "VLAN Zone in Proxmox SDN.", + MarkdownDescription: "VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the " + + "node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. " + + "This allows connectivity of VMs between different nodes.", + Attributes: genericAttributesWith(map[string]schema.Attribute{ + "bridge": schema.StringAttribute{ + Description: "Bridge interface for VLAN.", + MarkdownDescription: "The local bridge or OVS switch, already configured on _each_ node that allows " + + "node-to-node connection.", + Required: true, + }, + }), + } +} + +func (r *VLANResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + r.generic.Metadata(ctx, req, resp) +} + +func (r *VLANResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.generic.Configure(ctx, req, resp) +} + +func (r *VLANResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + r.generic.Create(ctx, req, resp) +} + +func (r *VLANResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + r.generic.Read(ctx, req, resp) +} + +func (r *VLANResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + r.generic.Update(ctx, req, resp) +} + +func (r *VLANResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + r.generic.Delete(ctx, req, resp) +} + +func (r *VLANResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + r.generic.ImportState(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/resource_vxlan.go b/fwprovider/cluster/sdn/zone/resource_vxlan.go new file mode 100644 index 00000000..824d4383 --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_vxlan.go @@ -0,0 +1,104 @@ +/* + * 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 zone + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" +) + +var ( + _ resource.ResourceWithConfigure = &VXLANResource{} + _ resource.ResourceWithImportState = &VXLANResource{} +) + +type vxlanModel struct { + genericModel + + Peers stringset.Value `tfsdk:"peers"` +} + +func (m *vxlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) { + m.genericModel.importFromAPI(name, data, diags) + m.Peers = stringset.NewValueString(data.Peers, diags, stringset.WithSeparator(",")) +} + +func (m *vxlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData { + data := m.genericModel.toAPIRequestBody(ctx, diags) + + data.Peers = m.Peers.ValueStringPointer(ctx, diags, stringset.WithSeparator(",")) + + return data +} + +type VXLANResource struct { + generic *genericZoneResource +} + +func NewVXLANResource() resource.Resource { + return &VXLANResource{ + generic: newGenericZoneResource(zoneResourceConfig{ + typeNameSuffix: "_sdn_zone_vxlan", + zoneType: zones.TypeVXLAN, + modelFunc: func() zoneModel { return &vxlanModel{} }, + }).(*genericZoneResource), + } +} + +func (r *VXLANResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "VXLAN Zone in Proxmox SDN.", + MarkdownDescription: "VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network " + + "(underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default " + + "destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity " + + "between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the " + + "outgoing physical interface.", + Attributes: genericAttributesWith(map[string]schema.Attribute{ + "peers": stringset.ResourceAttribute( + "A list of IP addresses of each node in the VXLAN zone.", + "A list of IP addresses of each node in the VXLAN zone. "+ + "This can be external nodes reachable at this IP address. All nodes in the cluster need to be "+ + "mentioned here", + stringset.WithRequired(), + ), + }), + } +} + +func (r *VXLANResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + r.generic.Metadata(ctx, req, resp) +} + +func (r *VXLANResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.generic.Configure(ctx, req, resp) +} + +func (r *VXLANResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + r.generic.Create(ctx, req, resp) +} + +func (r *VXLANResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + r.generic.Read(ctx, req, resp) +} + +func (r *VXLANResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + r.generic.Update(ctx, req, resp) +} + +func (r *VXLANResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + r.generic.Delete(ctx, req, resp) +} + +func (r *VXLANResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + r.generic.ImportState(ctx, req, resp) +} diff --git a/fwprovider/cluster/sdn/zone/resource_zones_test.go b/fwprovider/cluster/sdn/zone/resource_zones_test.go new file mode 100644 index 00000000..3caaa64b --- /dev/null +++ b/fwprovider/cluster/sdn/zone/resource_zones_test.go @@ -0,0 +1,195 @@ +//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 zone_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccResourceSDNZoneSimple(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create and update zones", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" { + id = "zoneS" + nodes = ["pve"] + mtu = 1496 + } + `), + }, { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" { + id = "zoneS" + nodes = ["pve"] + mtu = 1495 + } + `), + ResourceName: "proxmox_virtual_environment_sdn_zone_simple.zone_simple", + ImportStateId: "zoneS", + ImportState: true, + ImportStateVerify: true, + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccResourceSDNZoneVLAN(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create and update VLAN zone", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" { + id = "zoneV" + nodes = ["pve"] + mtu = 1496 + bridge = "vmbr0" + } + `), + }, { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" { + id = "zoneV" + nodes = ["pve"] + mtu = 1495 + bridge = "vmbr0" + } + `), + ResourceName: "proxmox_virtual_environment_sdn_zone_vlan.zone_vlan", + ImportStateId: "zoneV", + ImportState: true, + ImportStateVerify: true, + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccResourceSDNZoneQinQ(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create and update QinQ zone", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" { + id = "zoneQ" + nodes = ["pve"] + mtu = 1496 + bridge = "vmbr0" + service_vlan = 100 + service_vlan_protocol = "802.1ad" + } + `), + }, { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" { + id = "zoneQ" + nodes = ["pve"] + mtu = 1495 + bridge = "vmbr0" + service_vlan = 200 + service_vlan_protocol = "802.1q" + } + `), + ResourceName: "proxmox_virtual_environment_sdn_zone_qinq.zone_qinq", + ImportStateId: "zoneQ", + ImportState: true, + ImportStateVerify: true, + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} + +func TestAccResourceSDNZoneVXLAN(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + + tests := []struct { + name string + steps []resource.TestStep + }{ + {"create and update VXLAN zone", []resource.TestStep{{ + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" { + id = "zoneX" + nodes = ["pve"] + mtu = 1450 + peers = ["10.0.0.1", "10.0.0.2"] + } + `), + }, { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" { + id = "zoneX" + nodes = ["pve"] + mtu = 1440 + peers = ["10.0.0.3", "10.0.0.4"] + } + `), + ResourceName: "proxmox_virtual_environment_sdn_zone_vxlan.zone_vxlan", + ImportStateId: "zoneX", + ImportState: true, + ImportStateVerify: true, + }}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.steps, + }) + }) + } +} diff --git a/fwprovider/nodes/network/resource_linux_bridge.go b/fwprovider/nodes/network/resource_linux_bridge.go index b6334e67..19c63209 100644 --- a/fwprovider/nodes/network/resource_linux_bridge.go +++ b/fwprovider/nodes/network/resource_linux_bridge.go @@ -56,7 +56,6 @@ type linuxBridgeResourceModel struct { VLANAware types.Bool `tfsdk:"vlan_aware"` } -//nolint:lll func (m *linuxBridgeResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody { body := &nodes.NetworkInterfaceCreateUpdateRequestBody{ Iface: m.Name.ValueString(), diff --git a/fwprovider/nodes/network/resource_linux_vlan.go b/fwprovider/nodes/network/resource_linux_vlan.go index f39426bc..442fbdec 100644 --- a/fwprovider/nodes/network/resource_linux_vlan.go +++ b/fwprovider/nodes/network/resource_linux_vlan.go @@ -54,7 +54,6 @@ type linuxVLANResourceModel struct { VLAN types.Int64 `tfsdk:"vlan"` } -//nolint:lll func (m *linuxVLANResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody { body := &nodes.NetworkInterfaceCreateUpdateRequestBody{ Iface: m.Name.ValueString(), diff --git a/fwprovider/provider.go b/fwprovider/provider.go index c578e2e8..98faa4eb 100644 --- a/fwprovider/provider.go +++ b/fwprovider/provider.go @@ -30,6 +30,7 @@ import ( "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/options" + sdnzone "github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/sdn/zone" "github.com/bpg/terraform-provider-proxmox/fwprovider/config" "github.com/bpg/terraform-provider-proxmox/fwprovider/nodes" "github.com/bpg/terraform-provider-proxmox/fwprovider/nodes/apt" @@ -527,6 +528,11 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc nodes.NewDownloadFileResource, options.NewClusterOptionsResource, vm.NewResource, + sdnzone.NewSimpleResource, + sdnzone.NewVLANResource, + sdnzone.NewQinQResource, + sdnzone.NewVXLANResource, + sdnzone.NewEVPNResource, } } @@ -549,6 +555,12 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat hardwaremapping.NewPCIDataSource, hardwaremapping.NewUSBDataSource, metrics.NewMetricsServerDatasource, + sdnzone.NewSimpleDataSource, + sdnzone.NewVLANDataSource, + sdnzone.NewQinQDataSource, + sdnzone.NewVXLANDataSource, + sdnzone.NewEVPNDataSource, + sdnzone.NewZonesDataSource, vm.NewDataSource, } } diff --git a/fwprovider/types/stringset/attribute.go b/fwprovider/types/stringset/attribute.go index ca7f89d7..2b5a2ba6 100644 --- a/fwprovider/types/stringset/attribute.go +++ b/fwprovider/types/stringset/attribute.go @@ -16,9 +16,27 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +type ResourceAttributeOption func(*schema.SetAttribute) + +func WithRequired() ResourceAttributeOption { + return func(attribute *schema.SetAttribute) { + attribute.Required = true + attribute.Optional = false + attribute.Computed = false + } +} + +func WithOptional() ResourceAttributeOption { + return func(attribute *schema.SetAttribute) { + attribute.Optional = true + attribute.Required = false + attribute.Computed = true + } +} + // ResourceAttribute returns a resource schema attribute for string set. -func ResourceAttribute(desc, markdownDesc string) schema.SetAttribute { - return schema.SetAttribute{ +func ResourceAttribute(desc, markdownDesc string, options ...ResourceAttributeOption) schema.SetAttribute { + attribute := schema.SetAttribute{ CustomType: Type{ SetType: types.SetType{ ElemType: types.StringType, @@ -30,7 +48,6 @@ func ResourceAttribute(desc, markdownDesc string) schema.SetAttribute { Computed: true, ElementType: types.StringType, Validators: []validator.Set{ - // NOTE: we allow empty list to remove all previously set values setvalidator.ValueStringsAre( stringvalidator.RegexMatches( regexp.MustCompile(`(.|\s)*\S(.|\s)*`), @@ -40,6 +57,12 @@ func ResourceAttribute(desc, markdownDesc string) schema.SetAttribute { ), }, } + + for _, option := range options { + option(&attribute) + } + + return attribute } // DataSourceAttribute returns a data source schema attribute for string set. diff --git a/main.go b/main.go index 0395120e..7ab7d54a 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,12 @@ import ( //go:generate cp ./build/docs-gen/data-sources/virtual_environment_hardware_mappings.md ./docs/data-sources/ //go:generate cp ./build/docs-gen/data-sources/virtual_environment_haresource.md ./docs/data-sources/ //go:generate cp ./build/docs-gen/data-sources/virtual_environment_haresources.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zones.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zone_simple.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zone_vlan.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zone_qinq.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zone_vxlan.md ./docs/data-sources/ +//go:generate cp ./build/docs-gen/data-sources/virtual_environment_sdn_zone_evpn.md ./docs/data-sources/ //go:generate cp ./build/docs-gen/data-sources/virtual_environment_version.md ./docs/data-sources/ //go:generate cp ./build/docs-gen/data-sources/virtual_environment_vm2.md ./docs/data-sources/ //go:generate cp ./build/docs-gen/data-sources/virtual_environment_metrics_server.md ./docs/data-sources/ @@ -65,6 +71,11 @@ import ( //go:generate cp ./build/docs-gen/resources/virtual_environment_haresource.md ./docs/resources/ //go:generate cp ./build/docs-gen/resources/virtual_environment_network_linux_bridge.md ./docs/resources/ //go:generate cp ./build/docs-gen/resources/virtual_environment_network_linux_vlan.md ./docs/resources/ +//go:generate cp ./build/docs-gen/resources/virtual_environment_sdn_zone_simple.md ./docs/resources/ +//go:generate cp ./build/docs-gen/resources/virtual_environment_sdn_zone_vlan.md ./docs/resources/ +//go:generate cp ./build/docs-gen/resources/virtual_environment_sdn_zone_qinq.md ./docs/resources/ +//go:generate cp ./build/docs-gen/resources/virtual_environment_sdn_zone_vxlan.md ./docs/resources/ +//go:generate cp ./build/docs-gen/resources/virtual_environment_sdn_zone_evpn.md ./docs/resources/ //go:generate cp ./build/docs-gen/resources/virtual_environment_user_token.md ./docs/resources/ //go:generate cp ./build/docs-gen/resources/virtual_environment_vm2.md ./docs/resources/ //go:generate cp ./build/docs-gen/resources/virtual_environment_metrics_server.md ./docs/resources/ diff --git a/proxmox/api/client.go b/proxmox/api/client.go index 663dc9c9..8cc89d5f 100644 --- a/proxmox/api/client.go +++ b/proxmox/api/client.go @@ -352,7 +352,9 @@ func validateResponseCode(res *http.Response) error { errList = append(errList, split...) } - msg = fmt.Sprintf("%s (%s)", msg, strings.Join(errList, " - ")) + if len(errList) > 0 { + msg = fmt.Sprintf("%s (%s)", msg, strings.Join(errList, " - ")) + } } httpError := &HTTPError{ diff --git a/proxmox/cluster/client.go b/proxmox/cluster/client.go index e4f2314a..f0accdf1 100644 --- a/proxmox/cluster/client.go +++ b/proxmox/cluster/client.go @@ -15,6 +15,7 @@ import ( "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/metrics" + "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones" "github.com/bpg/terraform-provider-proxmox/proxmox/firewall" ) @@ -54,3 +55,18 @@ func (c *Client) ACME() *acme.Client { func (c *Client) Metrics() *metrics.Client { 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} +// } diff --git a/proxmox/cluster/sdn/zones/api.go b/proxmox/cluster/sdn/zones/api.go new file mode 100644 index 00000000..b61a318a --- /dev/null +++ b/proxmox/cluster/sdn/zones/api.go @@ -0,0 +1,19 @@ +/* + * 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 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 +} diff --git a/proxmox/cluster/sdn/zones/client.go b/proxmox/cluster/sdn/zones/client.go new file mode 100644 index 00000000..1adb4c0f --- /dev/null +++ b/proxmox/cluster/sdn/zones/client.go @@ -0,0 +1,23 @@ +/* + * 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 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) +} diff --git a/proxmox/cluster/sdn/zones/doc.go b/proxmox/cluster/sdn/zones/doc.go new file mode 100644 index 00000000..4afeea94 --- /dev/null +++ b/proxmox/cluster/sdn/zones/doc.go @@ -0,0 +1,11 @@ +/* + * 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 `zones` provides types and structures for managing Proxmox VE Software Defined Network (SDN) zones. +// SDN zones are logical network segments that can be configured with different network types including +// VLAN, QinQ, VXLAN, and EVPN. This package contains the data structures used for creating, updating, +// and managing SDN zones through the Proxmox API. +package zones diff --git a/proxmox/cluster/sdn/zones/zones.go b/proxmox/cluster/sdn/zones/zones.go new file mode 100644 index 00000000..23fd19cc --- /dev/null +++ b/proxmox/cluster/sdn/zones/zones.go @@ -0,0 +1,82 @@ +/* + * 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 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 +} diff --git a/proxmox/cluster/sdn/zones/zones_types.go b/proxmox/cluster/sdn/zones/zones_types.go new file mode 100644 index 00000000..6b40695d --- /dev/null +++ b/proxmox/cluster/sdn/zones/zones_types.go @@ -0,0 +1,65 @@ +/* + * 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 zones + +import "github.com/bpg/terraform-provider-proxmox/proxmox/types" + +const ( + TypeSimple = "simple" + TypeVLAN = "vlan" + TypeQinQ = "qinq" + TypeVXLAN = "vxlan" + TypeEVPN = "evpn" +) + +type ZoneData struct { + ID string `json:"zone" url:"zone"` + 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"` + ExitNodesPrimary *string `json:"exitnodes-primary,omitempty" url:"exitnodes-primary,omitempty"` + ExitNodesLocalRouting *types.CustomBool `json:"exitnodes-local-routing,omitempty" url:"exitnodes-local-routing,omitempty,int"` + AdvertiseSubnets *types.CustomBool `json:"advertise-subnets,omitempty" url:"advertise-subnets,omitempty,int"` + DisableARPNDSuppression *types.CustomBool `json:"disable-arp-nd-suppression,omitempty" url:"disable-arp-nd-suppression,omitempty,int"` + 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"` +} diff --git a/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf b/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf new file mode 100644 index 00000000..2b70ab68 --- /dev/null +++ b/proxmox_virtual_environment_sdn_zone_evpn/data-source.tf @@ -0,0 +1,23 @@ +data "proxmox_virtual_environment_sdn_zone_evpn" "example" { + id = "evpn1" +} + +output "data_proxmox_virtual_environment_sdn_zone_evpn" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_evpn.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.nodes + controller = data.proxmox_virtual_environment_sdn_zone_evpn.example.controller + vrf_vxlan = data.proxmox_virtual_environment_sdn_zone_evpn.example.vrf_vxlan + advertise_subnets = data.proxmox_virtual_environment_sdn_zone_evpn.example.advertise_subnets + disable_arp_nd_suppression = data.proxmox_virtual_environment_sdn_zone_evpn.example.disable_arp_nd_suppression + exit_nodes = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes + exit_nodes_local_routing = data.proxmox_virtual_environment_sdn_zone_evpn.example.exit_nodes_local_routing + primary_exit_node = data.proxmox_virtual_environment_sdn_zone_evpn.example.primary_exit_node + rt_import = data.proxmox_virtual_environment_sdn_zone_evpn.example.rt_import + mtu = data.proxmox_virtual_environment_sdn_zone_evpn.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_evpn.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_evpn.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_evpn.example.reverse_dns + } +} diff --git a/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf b/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf new file mode 100644 index 00000000..26f448c1 --- /dev/null +++ b/proxmox_virtual_environment_sdn_zone_qinq/data-source.tf @@ -0,0 +1,18 @@ +data "proxmox_virtual_environment_sdn_zone_qinq" "example" { + id = "qinq1" +} + +output "data_proxmox_virtual_environment_sdn_zone_qinq" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_qinq.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_qinq.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_qinq.example.bridge + service_vlan = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan + service_vlan_protocol = data.proxmox_virtual_environment_sdn_zone_qinq.example.service_vlan_protocol + mtu = data.proxmox_virtual_environment_sdn_zone_qinq.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_qinq.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_qinq.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_qinq.example.reverse_dns + } +} diff --git a/proxmox_virtual_environment_sdn_zone_simple/data-source.tf b/proxmox_virtual_environment_sdn_zone_simple/data-source.tf new file mode 100644 index 00000000..452cb88f --- /dev/null +++ b/proxmox_virtual_environment_sdn_zone_simple/data-source.tf @@ -0,0 +1,15 @@ +data "proxmox_virtual_environment_sdn_zone_simple" "example" { + id = "simple1" +} + +output "data_proxmox_virtual_environment_sdn_zone_simple" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_simple.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_simple.example.nodes + mtu = data.proxmox_virtual_environment_sdn_zone_simple.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_simple.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_simple.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_simple.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_simple.example.reverse_dns + } +} diff --git a/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf b/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf new file mode 100644 index 00000000..a1f0f9e4 --- /dev/null +++ b/proxmox_virtual_environment_sdn_zone_vlan/data-source.tf @@ -0,0 +1,16 @@ +data "proxmox_virtual_environment_sdn_zone_vlan" "example" { + id = "vlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vlan.example.nodes + bridge = data.proxmox_virtual_environment_sdn_zone_vlan.example.bridge + mtu = data.proxmox_virtual_environment_sdn_zone_vlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vlan.example.reverse_dns + } +} diff --git a/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf b/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf new file mode 100644 index 00000000..2ca035e4 --- /dev/null +++ b/proxmox_virtual_environment_sdn_zone_vxlan/data-source.tf @@ -0,0 +1,16 @@ +data "proxmox_virtual_environment_sdn_zone_vxlan" "example" { + id = "vxlan1" +} + +output "data_proxmox_virtual_environment_sdn_zone_vxlan" { + value = { + id = data.proxmox_virtual_environment_sdn_zone_vxlan.example.id + nodes = data.proxmox_virtual_environment_sdn_zone_vxlan.example.nodes + peers = data.proxmox_virtual_environment_sdn_zone_vxlan.example.peers + mtu = data.proxmox_virtual_environment_sdn_zone_vxlan.example.mtu + dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns + dns_zone = data.proxmox_virtual_environment_sdn_zone_vxlan.example.dns_zone + ipam = data.proxmox_virtual_environment_sdn_zone_vxlan.example.ipam + reverse_dns = data.proxmox_virtual_environment_sdn_zone_vxlan.example.reverse_dns + } +} diff --git a/proxmox_virtual_environment_sdn_zones/data-source.tf b/proxmox_virtual_environment_sdn_zones/data-source.tf new file mode 100644 index 00000000..a84abd4c --- /dev/null +++ b/proxmox_virtual_environment_sdn_zones/data-source.tf @@ -0,0 +1,25 @@ +# List all SDN zones +data "proxmox_virtual_environment_sdn_zones" "all" {} + +# List only EVPN zones +data "proxmox_virtual_environment_sdn_zones" "evpn_only" { + type = "evpn" +} + +# List only Simple zones +data "proxmox_virtual_environment_sdn_zones" "simple_only" { + type = "simple" +} + +output "data_proxmox_virtual_environment_sdn_zones_all" { + value = { + zones = data.proxmox_virtual_environment_sdn_zones.all.zones + } +} + +output "data_proxmox_virtual_environment_sdn_zones_filtered" { + value = { + evpn_zones = data.proxmox_virtual_environment_sdn_zones.evpn_only.zones + simple_zones = data.proxmox_virtual_environment_sdn_zones.simple_only.zones + } +} diff --git a/testacc b/testacc index b022b53b..0959e631 100755 --- a/testacc +++ b/testacc @@ -6,5 +6,21 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. # -# shellcheck disable=SC2046 -TF_ACC=1 env $(xargs < testacc.env) go test -count 1 --tags=acceptance -timeout 360s -run "$1" github.com/bpg/terraform-provider-proxmox/fwprovider/... $2 +BASE_PKG="github.com/bpg/terraform-provider-proxmox" + +find_test_package() { + test_name="$1" + [ -z "$test_name" ] && echo "${BASE_PKG}/fwprovider/..." && return + + test_file=$(find . -name "*.go" -type f -exec grep -l "func ${test_name}(" {} \; | head -1) + [ -z "$test_file" ] && echo "${BASE_PKG}/fwprovider/..." && return + + package_dir=$(dirname "$test_file") + package_path=$(echo "$package_dir" | sed 's|^\./||') + echo "${BASE_PKG}/${package_path}" +} + +PACKAGE_PATH=$(find_test_package "$1") + +# shellcheck disable=SC2046,SC2086 +TF_ACC=1 env $(xargs < testacc.env) go test -count 1 --tags=acceptance -timeout 360s -run "$1" "$PACKAGE_PATH" $2 From 3f5b795a840efeec989ea9aa38d70df3a0098800 Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:06:07 -0400 Subject: [PATCH 4/6] fix(acme): fix ACME account management & add acceptance tests (#2072) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- .../acme/datasource_acme_account_test.go | 63 +++++++ .../acme/datasource_acme_accounts_test.go | 64 +++++++ .../acme/datasource_acme_plugin_test.go | 63 +++++++ .../acme/datasource_acme_plugins_test.go | 96 +++++++++++ .../cluster/acme/resource_acme_account.go | 24 +++ .../acme/resource_acme_account_test.go | 131 ++++++++++++++ .../acme/resource_acme_dns_plugin_test.go | 160 ++++++++++++++++++ 7 files changed, 601 insertions(+) create mode 100644 fwprovider/cluster/acme/datasource_acme_account_test.go create mode 100644 fwprovider/cluster/acme/datasource_acme_accounts_test.go create mode 100644 fwprovider/cluster/acme/datasource_acme_plugin_test.go create mode 100644 fwprovider/cluster/acme/datasource_acme_plugins_test.go create mode 100644 fwprovider/cluster/acme/resource_acme_account_test.go create mode 100644 fwprovider/cluster/acme/resource_acme_dns_plugin_test.go diff --git a/fwprovider/cluster/acme/datasource_acme_account_test.go b/fwprovider/cluster/acme/datasource_acme_account_test.go new file mode 100644 index 00000000..7db3f8dd --- /dev/null +++ b/fwprovider/cluster/acme/datasource_acme_account_test.go @@ -0,0 +1,63 @@ +//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 acme_test + +import ( + "fmt" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccDatasourceACMEAccount(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + accountName := fmt.Sprintf("test-ds-account-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "AccountName": accountName, + }) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account" { + name = "{{.AccountName}}" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + } + + data "proxmox_virtual_environment_acme_account" "test" { + depends_on = [proxmox_virtual_environment_acme_account.test_account] + name = "{{.AccountName}}" + } + `, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_acme_account.test", map[string]string{ + "name": accountName, + }), + test.ResourceAttributesSet("data.proxmox_virtual_environment_acme_account.test", []string{ + "account.created_at", + "account.status", + "directory", + "location", + "tos", + }), + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_acme_account.test", "account.contact.#"), + ), + }, + }, + }) +} diff --git a/fwprovider/cluster/acme/datasource_acme_accounts_test.go b/fwprovider/cluster/acme/datasource_acme_accounts_test.go new file mode 100644 index 00000000..be030854 --- /dev/null +++ b/fwprovider/cluster/acme/datasource_acme_accounts_test.go @@ -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 acme_test + +import ( + "fmt" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccDatasourceACMEAccounts(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + accountName1 := fmt.Sprintf("test-ds-accounts1-%s", gofakeit.Word()) + accountName2 := fmt.Sprintf("test-ds-accounts2-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "AccountName1": accountName1, + "AccountName2": accountName2, + }) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account1" { + name = "{{.AccountName1}}" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + } + + resource "proxmox_virtual_environment_acme_account" "test_account2" { + name = "{{.AccountName2}}" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + } + + data "proxmox_virtual_environment_acme_accounts" "test" { + depends_on = [ + proxmox_virtual_environment_acme_account.test_account1, + proxmox_virtual_environment_acme_account.test_account2 + ] + } + `, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_acme_accounts.test", "accounts.#"), + ), + }, + }, + }) +} diff --git a/fwprovider/cluster/acme/datasource_acme_plugin_test.go b/fwprovider/cluster/acme/datasource_acme_plugin_test.go new file mode 100644 index 00000000..299b046c --- /dev/null +++ b/fwprovider/cluster/acme/datasource_acme_plugin_test.go @@ -0,0 +1,63 @@ +//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 acme_test + +import ( + "fmt" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccDatasourceACMEPlugin(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + pluginName := fmt.Sprintf("test-ds-plugin-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "PluginName": pluginName, + }) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin" { + plugin = "{{.PluginName}}" + api = "cf" + data = { + "CF_API_EMAIL" = "le.ge9ro@passmail.net" + "CF_API_KEY" = "test-api-key" + } + } + + data "proxmox_virtual_environment_acme_plugin" "test" { + depends_on = [proxmox_virtual_environment_acme_dns_plugin.test_plugin] + plugin = "{{.PluginName}}" + } + `), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("data.proxmox_virtual_environment_acme_plugin.test", map[string]string{ + "plugin": pluginName, + }), + test.ResourceAttributesSet("data.proxmox_virtual_environment_acme_plugin.test", []string{ + "api", + "digest", + "validation_delay", + }), + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_acme_plugin.test", "data.%"), + ), + }, + }, + }) +} diff --git a/fwprovider/cluster/acme/datasource_acme_plugins_test.go b/fwprovider/cluster/acme/datasource_acme_plugins_test.go new file mode 100644 index 00000000..a05b877f --- /dev/null +++ b/fwprovider/cluster/acme/datasource_acme_plugins_test.go @@ -0,0 +1,96 @@ +//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 acme_test + +import ( + "fmt" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccDatasourceACMEPlugins(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + pluginName1 := fmt.Sprintf("test-ds-plugins1-%s", gofakeit.Word()) + pluginName2 := fmt.Sprintf("test-ds-plugins2-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "PluginName1": pluginName1, + "PluginName2": pluginName2, + }) + + // First create some plugins to test against + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin1" { + plugin = "{{.PluginName1}}" + api = "cf" + data = { + "CF_API_EMAIL" = "test1@example.com" + "CF_API_KEY" = "test-api-key-1" + } + } + + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin2" { + plugin = "{{.PluginName2}}" + api = "cf" + data = { + "CF_API_EMAIL" = "test2@example.com" + "CF_API_KEY" = "test-api-key-2" + } + } + `), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin1" { + plugin = "{{.PluginName1}}" + api = "cf" + data = { + "CF_API_EMAIL" = "le.ge9ro@passmail.net" + "CF_API_KEY" = "test-api-key-1" + } + } + + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin2" { + plugin = "{{.PluginName2}}" + api = "cf" + data = { + "CF_API_EMAIL" = "le.ge9ro@passmail.net" + "CF_API_KEY" = "test-api-key-2" + } + } + + data "proxmox_virtual_environment_acme_plugins" "test" { + depends_on = [ + proxmox_virtual_environment_acme_dns_plugin.test_plugin1, + proxmox_virtual_environment_acme_dns_plugin.test_plugin2 + ] + } + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.proxmox_virtual_environment_acme_plugins.test", "plugins.#"), + ), + }, + }, + }) +} diff --git a/fwprovider/cluster/acme/resource_acme_account.go b/fwprovider/cluster/acme/resource_acme_account.go index 15f5dc3a..40b28b22 100644 --- a/fwprovider/cluster/acme/resource_acme_account.go +++ b/fwprovider/cluster/acme/resource_acme_account.go @@ -4,6 +4,24 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * 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/. + */ + +/* + * 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/. + */ + +/* + * 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 acme import ( @@ -305,9 +323,15 @@ func (r *acmeAccountResource) read(ctx context.Context, data *acmeAccountModel) return false, diags } + // Preserve the original contact value if the API doesn't return it + originalContact := data.Contact.ValueString() + var contact string if len(acc.Account.Contact) > 0 { contact = strings.Replace(acc.Account.Contact[0], "mailto:", "", 1) + } else if originalContact != "" { + // If API doesn't return contact but we had one, preserve it + contact = originalContact } data.Directory = types.StringValue(acc.Directory) diff --git a/fwprovider/cluster/acme/resource_acme_account_test.go b/fwprovider/cluster/acme/resource_acme_account_test.go new file mode 100644 index 00000000..245b9413 --- /dev/null +++ b/fwprovider/cluster/acme/resource_acme_account_test.go @@ -0,0 +1,131 @@ +//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 acme_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccResourceACMEAccount(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + accountName := fmt.Sprintf("test-account-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "AccountName": accountName, + }) + + tests := []struct { + name string + step []resource.TestStep + }{ + {"basic account creation", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account" { + name = "{{.AccountName}}" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + }`, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_account.test_account", map[string]string{ + "name": accountName, + "directory": "https://acme-staging-v02.api.letsencrypt.org/directory", + "tos": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", + }), + test.ResourceAttributesSet("proxmox_virtual_environment_acme_account.test_account", []string{ + "created_at", + "location", + }), + ), + }, + }}, + {"account with EAB", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account_eab" { + name = "{{.AccountName}}-eab" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + eab_hmac_key = "test-hmac-key" + eab_kid = "test-kid" + }`, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_account.test_account_eab", map[string]string{ + "name": fmt.Sprintf("%s-eab", accountName), + "directory": "https://acme-staging-v02.api.letsencrypt.org/directory", + "tos": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", + "eab_hmac_key": "test-hmac-key", + "eab_kid": "test-kid", + }), + ), + }, + }}, + {"update account", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account_update" { + name = "{{.AccountName}}-update" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + }`, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_account.test_account_update", map[string]string{ + "name": fmt.Sprintf("%s-update", accountName), + }), + ), + }, + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account_update" { + name = "{{.AccountName}}-update" + contact = "le.ge9ro@passmail.net" + directory = "https://acme-staging-v02.api.letsencrypt.org/directory" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + }`, test.WithRootUser()), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_account.test_account_update", map[string]string{ + "name": fmt.Sprintf("%s-update", accountName), + }), + ), + }, + }}, + {"invalid directory URL", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_account" "test_account_invalid" { + name = "{{.AccountName}}-invalid" + contact = "le.ge9ro@passmail.net" + directory = "invalid-url" + tos = "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + }`, test.WithRootUser()), + ExpectError: regexp.MustCompile(`must be a valid URL`), + }, + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.step, + }) + }) + } +} diff --git a/fwprovider/cluster/acme/resource_acme_dns_plugin_test.go b/fwprovider/cluster/acme/resource_acme_dns_plugin_test.go new file mode 100644 index 00000000..5f8ee8f7 --- /dev/null +++ b/fwprovider/cluster/acme/resource_acme_dns_plugin_test.go @@ -0,0 +1,160 @@ +//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 acme_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/brianvoe/gofakeit/v7" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/bpg/terraform-provider-proxmox/fwprovider/test" +) + +func TestAccResourceACMEDNSPlugin(t *testing.T) { + t.Parallel() + + te := test.InitEnvironment(t) + pluginName := fmt.Sprintf("test-plugin-%s", gofakeit.Word()) + te.AddTemplateVars(map[string]interface{}{ + "PluginName": pluginName, + }) + + tests := []struct { + name string + step []resource.TestStep + }{ + {"basic plugin creation", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin" { + plugin = "{{.PluginName}}" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + }`), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_dns_plugin.test_plugin", map[string]string{ + "plugin": pluginName, + "api": "cf", + }), + test.ResourceAttributesSet("proxmox_virtual_environment_acme_dns_plugin.test_plugin", []string{ + "digest", + }), + ), + }, + }}, + {"plugin with validation delay", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin_delay" { + plugin = "{{.PluginName}}-delay" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + validation_delay = 60 + }`), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_dns_plugin.test_plugin_delay", map[string]string{ + "plugin": fmt.Sprintf("%s-delay", pluginName), + "api": "cf", + "validation_delay": "60", + }), + ), + }, + }}, + {"plugin with disable flag", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin_disabled" { + plugin = "{{.PluginName}}-disabled" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + disable = true + }`), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_dns_plugin.test_plugin_disabled", map[string]string{ + "plugin": fmt.Sprintf("%s-disabled", pluginName), + "api": "cf", + "disable": "true", + }), + ), + }, + }}, + {"update plugin", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin_update" { + plugin = "{{.PluginName}}-update" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + validation_delay = 30 + }`), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_dns_plugin.test_plugin_update", map[string]string{ + "validation_delay": "30", + }), + ), + }, + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin_update" { + plugin = "{{.PluginName}}-update" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + validation_delay = 120 + }`), + Check: resource.ComposeTestCheckFunc( + test.ResourceAttributes("proxmox_virtual_environment_acme_dns_plugin.test_plugin_update", map[string]string{ + "validation_delay": "120", + }), + ), + }, + }}, + {"invalid validation delay", []resource.TestStep{ + { + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_acme_dns_plugin" "test_plugin_invalid" { + plugin = "{{.PluginName}}-invalid" + api = "cf" + data = { + "CF_API_EMAIL" = "test@example.com" + "CF_API_KEY" = "test-api-key" + } + validation_delay = 200000 + }`), + ExpectError: regexp.MustCompile(`Attribute validation_delay value must be between 0 and 172800`), + }, + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: te.AccProviders, + Steps: tt.step, + }) + }) + } +} From 4be9e932fab68363d8dddbe15bac90ac82a663e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:10:46 -0400 Subject: [PATCH 5/6] =?UTF-8?q?chore(deps):=20update=20module=20github.com?= =?UTF-8?q?/hashicorp/terraform-plugin-framework=20(v1.15.0=20=E2=86=92=20?= =?UTF-8?q?v1.15.1)=20(#2073)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ---------- | ----------------------------------------------- | ------- | ------- | | go | github.com/hashicorp/terraform-plugin-framework | v1.15.0 | v1.15.1 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 401f4287..3b646dd2 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/go-version v1.7.0 - github.com/hashicorp/terraform-plugin-framework v1.15.0 + github.com/hashicorp/terraform-plugin-framework v1.15.1 github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 github.com/hashicorp/terraform-plugin-go v0.28.0 diff --git a/go.sum b/go.sum index 99a3ce14..82da126d 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGo github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc= github.com/hashicorp/terraform-plugin-docs v0.22.0 h1:fwIDStbFel1PPNkM+mDPnpB4efHZBdGoMz/zt5FbTDw= github.com/hashicorp/terraform-plugin-docs v0.22.0/go.mod h1:55DJVyZ7BNK4t/lANcQ1YpemRuS6KsvIO1BbGA+xzGE= -github.com/hashicorp/terraform-plugin-framework v1.15.0 h1:LQ2rsOfmDLxcn5EeIwdXFtr03FVsNktbbBci8cOKdb4= -github.com/hashicorp/terraform-plugin-framework v1.15.0/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= +github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8= +github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0/go.mod h1:t339KhmxnaF4SzdpxmqW8HnQBHVGYazwtfxU0qCs4eE= github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw= From 891066821bf7993a5006b12a44c5b36dbdb852d8 Mon Sep 17 00:00:00 2001 From: "bpg-autobot[bot]" <155200059+bpg-autobot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:15:34 -0400 Subject: [PATCH 6/6] chore(main): release 0.81.0 (#2053) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: bpg-autobot[bot] <155200059+bpg-autobot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 26 +++++++++++++++++++ Makefile | 2 +- examples/guides/clone-vm/provider.tf | 2 +- .../cloud-image/centos-qcow2/provider.tf | 2 +- .../debian-from-storage/provider.tf | 2 +- .../guides/cloud-image/ubuntu-img/provider.tf | 2 +- examples/guides/cloud-init/custom/provider.tf | 2 +- examples/guides/cloud-init/native/provider.tf | 2 +- 9 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 503c3146..970c6fcd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.80.0" + ".": "0.81.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a79b4e..036dc2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## [0.81.0](https://github.com/bpg/terraform-provider-proxmox/compare/v0.80.0...v0.81.0) (2025-08-01) + + +### Features + +* **firewall:** adds forward type to firewall rules ([#2052](https://github.com/bpg/terraform-provider-proxmox/issues/2052)) ([e185201](https://github.com/bpg/terraform-provider-proxmox/commit/e185201840ec3140505f344b2ec5a3cac6f23ba4)) +* **firewall:** adds forward_policy to cluster firewall ([#2064](https://github.com/bpg/terraform-provider-proxmox/issues/2064)) ([209d10c](https://github.com/bpg/terraform-provider-proxmox/commit/209d10cc0bb137e9ed52ec1975646a565a613863)) +* **sdn:** add support for zone resources and datasources ([#2046](https://github.com/bpg/terraform-provider-proxmox/issues/2046)) ([6b73d82](https://github.com/bpg/terraform-provider-proxmox/commit/6b73d822a5a6d55b7761647ad963be2b88491bbd)) + + +### Bug Fixes + +* **acme:** fix ACME account management & add acceptance tests ([#2072](https://github.com/bpg/terraform-provider-proxmox/issues/2072)) ([3f5b795](https://github.com/bpg/terraform-provider-proxmox/commit/3f5b795a840efeec989ea9aa38d70df3a0098800)) +* **example:** update ubuntu LXC image ref ([#2070](https://github.com/bpg/terraform-provider-proxmox/issues/2070)) ([ce66bfd](https://github.com/bpg/terraform-provider-proxmox/commit/ce66bfdc61bd5faf6f95cfd25cb09dc1447567ec)) +* **vm,lxc:** 32bit int overflow in list nodes / containers API calls when running on i386 arch ([#2057](https://github.com/bpg/terraform-provider-proxmox/issues/2057)) ([47ff578](https://github.com/bpg/terraform-provider-proxmox/commit/47ff5787b1e150ab754abfd86e4a43a34b5cde0b)) + + +### Miscellaneous + +* **deps:** update golangci/golangci-lint (v2.2.2 → v2.3.0) ([#2056](https://github.com/bpg/terraform-provider-proxmox/issues/2056)) ([092edf2](https://github.com/bpg/terraform-provider-proxmox/commit/092edf2d08901c8e579cc6927b14ec5613547a00)) +* **deps:** update image golang (14fd8a5 → ef5b4be) ([#2061](https://github.com/bpg/terraform-provider-proxmox/issues/2061)) ([4da593d](https://github.com/bpg/terraform-provider-proxmox/commit/4da593d727f1141aeb64bc41925b9a6031a3055f)) +* **deps:** update module github.com/hashicorp/terraform-plugin-framework (v1.15.0 → v1.15.1) ([#2073](https://github.com/bpg/terraform-provider-proxmox/issues/2073)) ([4be9e93](https://github.com/bpg/terraform-provider-proxmox/commit/4be9e932fab68363d8dddbe15bac90ac82a663e1)) +* **docs:** add ceph squid to `apt_standard_repository` docs ([#2063](https://github.com/bpg/terraform-provider-proxmox/issues/2063)) ([5c33941](https://github.com/bpg/terraform-provider-proxmox/commit/5c3394121ff68efe3928be2ca957076dc4d4f2c8)) +* **example:** apply consistency in example/*.tf node_name declarations ([#2051](https://github.com/bpg/terraform-provider-proxmox/issues/2051)) ([757b191](https://github.com/bpg/terraform-provider-proxmox/commit/757b191376405b5cea41b229cda165ac70b3a490)) +* **project:** switch repo infographic to repobeats ([#2058](https://github.com/bpg/terraform-provider-proxmox/issues/2058)) ([bbd55d2](https://github.com/bpg/terraform-provider-proxmox/commit/bbd55d26184d464248c1e7dd7723ebbd2a028bbc)) + ## [0.80.0](https://github.com/bpg/terraform-provider-proxmox/compare/v0.79.0...v0.80.0) (2025-07-15) diff --git a/Makefile b/Makefile index 14c58548..a762da4d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ NAME=terraform-provider-proxmox TARGETS=darwin linux windows TERRAFORM_PLUGIN_EXTENSION= -VERSION=0.80.0# x-release-please-version +VERSION=0.81.0# x-release-please-version GOLANGCI_LINT_VERSION=2.3.0# renovate: depName=golangci/golangci-lint datasource=github-releases diff --git a/examples/guides/clone-vm/provider.tf b/examples/guides/clone-vm/provider.tf index 867f05c0..18e634b4 100644 --- a/examples/guides/clone-vm/provider.tf +++ b/examples/guides/clone-vm/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } } diff --git a/examples/guides/cloud-image/centos-qcow2/provider.tf b/examples/guides/cloud-image/centos-qcow2/provider.tf index 94e2fbd9..88b4c6f5 100644 --- a/examples/guides/cloud-image/centos-qcow2/provider.tf +++ b/examples/guides/cloud-image/centos-qcow2/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } } diff --git a/examples/guides/cloud-image/debian-from-storage/provider.tf b/examples/guides/cloud-image/debian-from-storage/provider.tf index 867f05c0..18e634b4 100644 --- a/examples/guides/cloud-image/debian-from-storage/provider.tf +++ b/examples/guides/cloud-image/debian-from-storage/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } } diff --git a/examples/guides/cloud-image/ubuntu-img/provider.tf b/examples/guides/cloud-image/ubuntu-img/provider.tf index cd683f99..ac9bc346 100644 --- a/examples/guides/cloud-image/ubuntu-img/provider.tf +++ b/examples/guides/cloud-image/ubuntu-img/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } } diff --git a/examples/guides/cloud-init/custom/provider.tf b/examples/guides/cloud-init/custom/provider.tf index 867f05c0..18e634b4 100644 --- a/examples/guides/cloud-init/custom/provider.tf +++ b/examples/guides/cloud-init/custom/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } } diff --git a/examples/guides/cloud-init/native/provider.tf b/examples/guides/cloud-init/native/provider.tf index cd683f99..ac9bc346 100644 --- a/examples/guides/cloud-init/native/provider.tf +++ b/examples/guides/cloud-init/native/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { proxmox = { source = "bpg/proxmox" - version = "0.80.0" # x-release-please-version + version = "0.81.0" # x-release-please-version } } }