0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-22 11:28:33 +00:00

feat(sdn): add support for zone resources and datasources (#2046)

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2025-07-31 20:30:18 -04:00 committed by GitHub
parent 757b191376
commit 6b73d822a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 3370 additions and 8 deletions

View File

@ -44,6 +44,8 @@ linters:
gosec:
excludes:
- G115
lll:
line-length: 150
revive:
rules:
- name: "package-comments"

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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 '<ASN>:<number>' (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.

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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`.

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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.

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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.

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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.

View File

@ -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 generated by tfplugindocs -->
## 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))
<a id="nestedatt--zones"></a>
### 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)

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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
```

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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
```

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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
```

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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
```

View File

@ -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 generated by tfplugindocs -->
## 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 `<hostname>.<domain>`. 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
```

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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 '<ASN>:<number>' (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)
}

View File

@ -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 `<hostname>.<domain>`. " +
"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)...)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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{}
}

View File

@ -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,
})
})
}
}

View File

@ -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 '<ASN>:<number>' (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)
}

View File

@ -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 `<hostname>.<domain>`. " +
"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.
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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,
})
})
}
}

View File

@ -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(),

View File

@ -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(),

View File

@ -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,
}
}

View File

@ -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.

11
main.go
View File

@ -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/

View File

@ -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{

View File

@ -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}
// }

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

20
testacc
View File

@ -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