From 71bac9602d1391d91bb4ed4c48fdec0d8b320432 Mon Sep 17 00:00:00 2001 From: Andreas Harter Date: Sat, 22 Oct 2022 18:56:56 +0200 Subject: [PATCH] Add missing IPv6 properties (#261) * Add missing WAN IPv6 properties to network resource * Rename 'ipv6_static_subnet' to 'ipv6_subnet' According to the go-unifi lib, the name was incorrect * Improve description and validation of IPv6 properties * Add missing IPv6 props to network * Add IPv6 support to firewall rules * Adjust naming * ensure unique vlan * Add vlan ID validation * fix assertion * Update docs * rename attribute * cleanup imports * test protocol_v6 * test dhcp_v6_lease * spelling * switch protocol back Co-authored-by: Paul Tyng --- docs/data-sources/network.md | 20 +- docs/resources/firewall_rule.md | 6 +- docs/resources/network.md | 22 +- internal/provider/data_network.go | 93 ++++++- internal/provider/data_port_profile.go | 2 +- internal/provider/data_radius_profile.go | 2 +- internal/provider/data_user_group.go | 2 +- internal/provider/data_user_test.go | 2 +- internal/provider/resource_dynamic_dns.go | 2 +- internal/provider/resource_firewall_group.go | 2 +- internal/provider/resource_firewall_rule.go | 40 ++- .../provider/resource_firewall_rule_test.go | 78 +++++- internal/provider/resource_network.go | 231 +++++++++++++++--- internal/provider/resource_network_test.go | 98 +++++++- internal/provider/resource_port_forward.go | 2 +- internal/provider/resource_port_profile.go | 2 +- internal/provider/resource_radius_profile.go | 3 +- .../provider/resource_radius_profile_test.go | 3 +- internal/provider/resource_setting_radius.go | 1 + internal/provider/resource_static_route.go | 2 +- internal/provider/resource_user.go | 1 - internal/provider/resource_user_group.go | 2 +- 22 files changed, 547 insertions(+), 69 deletions(-) diff --git a/docs/data-sources/network.md b/docs/data-sources/network.md index d44c3ae75..213be4bbc 100644 --- a/docs/data-sources/network.md +++ b/docs/data-sources/network.md @@ -43,27 +43,43 @@ data "unifi_network" "my_network" { - `dhcp_lease` (Number) lease time for DHCP addresses. - `dhcp_start` (String) The IPv4 address where the DHCP range of addresses starts. - `dhcp_stop` (String) The IPv4 address where the DHCP range of addresses stops. +- `dhcp_v6_dns` (List of String) Specifies the IPv6 addresses for the DNS server to be returned from the DHCP server. Used if `dhcp_v6_dns_auto` is set to `false`. +- `dhcp_v6_dns_auto` (Boolean) Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise +- `dhcp_v6_enabled` (Boolean) Enable stateful DHCPv6 for static configuration. +- `dhcp_v6_lease` (Number) Specifies the lease time for DHCPv6 addresses. +- `dhcp_v6_start` (String) Start address of the DHCPv6 range. Used in static DHCPv6 configuration. +- `dhcp_v6_stop` (String) End address of the DHCPv6 range. Used in static DHCPv6 configuration. - `dhcpd_boot_enabled` (Boolean) Toggles on the DHCP boot options. will be set to true if you have dhcpd_boot_filename, and dhcpd_boot_server set. - `dhcpd_boot_filename` (String) the file to PXE boot from on the dhcpd_boot_server. - `dhcpd_boot_server` (String) IPv4 address of a TFTP server to network boot from. - `domain_name` (String) The domain name of this network. - `igmp_snooping` (Boolean) Specifies whether IGMP snooping is enabled or not. -- `ipv6_interface_type` (String) Specifies which type of IPv6 connection to use. -- `ipv6_pd_interface` (String) Specifies which WAN interface is used for IPv6 Prefix Delegation. +- `ipv6_interface_type` (String) Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`. +- `ipv6_pd_interface` (String) Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`. - `ipv6_pd_prefixid` (String) Specifies the IPv6 Prefix ID. +- `ipv6_pd_start` (String) Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`. +- `ipv6_pd_stop` (String) End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`. - `ipv6_ra_enable` (Boolean) Specifies whether to enable router advertisements or not. +- `ipv6_ra_preferred_lifetime` (Number) Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime` +- `ipv6_ra_priority` (String) IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low` +- `ipv6_ra_valid_lifetime` (Number) Total lifetime in which the address can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`. - `ipv6_static_subnet` (String) Specifies the static IPv6 subnet (when ipv6_interface_type is 'static'). - `network_group` (String) The group of the network. - `purpose` (String) The purpose of the network. One of `corporate`, `guest`, `wan`, or `vlan-only`. - `subnet` (String) The subnet of the network (CIDR address). - `vlan_id` (Number) The VLAN ID of the network. +- `wan_dhcp_v6_pd_size` (Number) Specifies the IPv6 prefix size to request from ISP. Must be a number between 48 and 64. - `wan_dns` (List of String) DNS servers IPs of the WAN. - `wan_egress_qos` (Number) Specifies the WAN egress quality of service. - `wan_gateway` (String) The IPv4 gateway of the WAN. +- `wan_gateway_v6` (String) The IPv6 gateway of the WAN. - `wan_ip` (String) The IPv4 address of the WAN. +- `wan_ipv6` (String) The IPv6 address of the WAN. - `wan_netmask` (String) The IPv4 netmask of the WAN. - `wan_networkgroup` (String) Specifies the WAN network group. One of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`. +- `wan_prefixlen` (Number) The IPv6 prefix length of the WAN. Must be between 1 and 128. - `wan_type` (String) Specifies the IPV4 WAN connection type. One of either `disabled`, `static`, `dhcp`, or `pppoe`. +- `wan_type_v6` (String) Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`. - `wan_username` (String) Specifies the IPV4 WAN username. - `x_wan_password` (String) Specifies the IPV4 WAN password. diff --git a/docs/resources/firewall_rule.md b/docs/resources/firewall_rule.md index 7c2e812ce..457bbe81b 100644 --- a/docs/resources/firewall_rule.md +++ b/docs/resources/firewall_rule.md @@ -37,22 +37,26 @@ resource "unifi_firewall_rule" "drop_all" { - `action` (String) The action of the firewall rule. Must be one of `drop`, `accept`, or `reject`. - `name` (String) The name of the firewall rule. -- `protocol` (String) The protocol of the rule. - `rule_index` (Number) The index of the rule. Must be >= 2000 < 3000 or >= 4000 < 5000. - `ruleset` (String) The ruleset for the rule. This is from the perspective of the security gateway. Must be one of `WAN_IN`, `WAN_OUT`, `WAN_LOCAL`, `LAN_IN`, `LAN_OUT`, `LAN_LOCAL`, `GUEST_IN`, `GUEST_OUT`, `GUEST_LOCAL`, `WANv6_IN`, `WANv6_OUT`, `WANv6_LOCAL`, `LANv6_IN`, `LANv6_OUT`, `LANv6_LOCAL`, `GUESTv6_IN`, `GUESTv6_OUT`, or `GUESTv6_LOCAL`. ### Optional - `dst_address` (String) The destination address of the firewall rule. +- `dst_address_ipv6` (String) The IPv6 destination address of the firewall rule. - `dst_firewall_group_ids` (Set of String) The destination firewall group IDs of the firewall rule. - `dst_network_id` (String) The destination network ID of the firewall rule. - `dst_network_type` (String) The destination network type of the firewall rule. Can be one of `ADDRv4` or `NETv4`. Defaults to `NETv4`. - `dst_port` (String) The destination port of the firewall rule. - `icmp_typename` (String) ICMP type name. +- `icmp_v6_typename` (String) ICMPv6 type name. - `ip_sec` (String) Specify whether the rule matches on IPsec packets. Can be one of `match-ipset` or `match-none`. - `logging` (Boolean) Enable logging for the firewall rule. +- `protocol` (String) The protocol of the rule. +- `protocol_v6` (String) The IPv6 protocol of the rule. - `site` (String) The name of the site to associate the firewall rule with. - `src_address` (String) The source address for the firewall rule. +- `src_address_ipv6` (String) The IPv6 source address for the firewall rule. - `src_firewall_group_ids` (Set of String) The source firewall group IDs for the firewall rule. - `src_mac` (String) The source MAC address of the firewall rule. - `src_network_id` (String) The source network ID for the firewall rule. diff --git a/docs/resources/network.md b/docs/resources/network.md index fda93876f..2fe391732 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -57,6 +57,12 @@ resource "unifi_network" "wan" { - `dhcp_relay_enabled` (Boolean) Specifies whether DHCP relay is enabled or not on this network. - `dhcp_start` (String) The IPv4 address where the DHCP range of addresses starts. - `dhcp_stop` (String) The IPv4 address where the DHCP range of addresses stops. +- `dhcp_v6_dns` (List of String) Specifies the IPv6 addresses for the DNS server to be returned from the DHCP server. Used if `dhcp_v6_dns_auto` is set to `false`. +- `dhcp_v6_dns_auto` (Boolean) Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise Defaults to `true`. +- `dhcp_v6_enabled` (Boolean) Enable stateful DHCPv6 for static configuration. +- `dhcp_v6_lease` (Number) Specifies the lease time for DHCPv6 addresses. Defaults to `86400`. +- `dhcp_v6_start` (String) Start address of the DHCPv6 range. Used in static DHCPv6 configuration. +- `dhcp_v6_stop` (String) End address of the DHCPv6 range. Used in static DHCPv6 configuration. - `dhcpd_boot_enabled` (Boolean) Toggles on the DHCP boot options. Should be set to true when you want to have dhcpd_boot_filename, and dhcpd_boot_server to take effect. - `dhcpd_boot_filename` (String) Specifies the file to PXE boot from on the dhcpd_boot_server. - `dhcpd_boot_server` (String) Specifies the IPv4 address of a TFTP server to network boot from. @@ -64,22 +70,32 @@ resource "unifi_network" "wan" { - `igmp_snooping` (Boolean) Specifies whether IGMP snooping is enabled or not. - `internet_access_enabled` (Boolean) Specifies whether this network should be allowed to access the internet or not. Defaults to `true`. - `intra_network_access_enabled` (Boolean) Specifies whether this network should be allowed to access other local networks or not. Defaults to `true`. -- `ipv6_interface_type` (String) Specifies which type of IPv6 connection to use. Defaults to `none`. -- `ipv6_pd_interface` (String) Specifies which WAN interface to use for IPv6 PD. +- `ipv6_interface_type` (String) Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`. Defaults to `none`. +- `ipv6_pd_interface` (String) Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`. - `ipv6_pd_prefixid` (String) Specifies the IPv6 Prefix ID. +- `ipv6_pd_start` (String) Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`. +- `ipv6_pd_stop` (String) End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`. - `ipv6_ra_enable` (Boolean) Specifies whether to enable router advertisements or not. -- `ipv6_static_subnet` (String) Specifies the static IPv6 subnet when ipv6_interface_type is 'static'. +- `ipv6_ra_preferred_lifetime` (Number) Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime` Defaults to `14400`. +- `ipv6_ra_priority` (String) IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low` +- `ipv6_ra_valid_lifetime` (Number) Total lifetime in which the adress can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`. Defaults to `86400`. +- `ipv6_static_subnet` (String) Specifies the static IPv6 subnet when `ipv6_interface_type` is 'static'. - `network_group` (String) The group of the network. Defaults to `LAN`. - `site` (String) The name of the site to associate the network with. - `subnet` (String) The subnet of the network. Must be a valid CIDR address. - `vlan_id` (Number) The VLAN ID of the network. +- `wan_dhcp_v6_pd_size` (Number) Specifies the IPv6 prefix size to request from ISP. Must be between 48 and 64. - `wan_dns` (List of String) DNS servers IPs of the WAN. - `wan_egress_qos` (Number) Specifies the WAN egress quality of service. Defaults to `0`. - `wan_gateway` (String) The IPv4 gateway of the WAN. +- `wan_gateway_v6` (String) The IPv6 gateway of the WAN. - `wan_ip` (String) The IPv4 address of the WAN. +- `wan_ipv6` (String) The IPv6 address of the WAN. - `wan_netmask` (String) The IPv4 netmask of the WAN. - `wan_networkgroup` (String) Specifies the WAN network group. Must be one of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`. +- `wan_prefixlen` (Number) The IPv6 prefix length of the WAN. Must be between 1 and 128. - `wan_type` (String) Specifies the IPV4 WAN connection type. Must be one of either `disabled`, `static`, `dhcp`, or `pppoe`. +- `wan_type_v6` (String) Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`. - `wan_username` (String) Specifies the IPV4 WAN username. - `x_wan_password` (String) Specifies the IPV4 WAN password. diff --git a/internal/provider/data_network.go b/internal/provider/data_network.go index d3c49bce2..b949672e7 100644 --- a/internal/provider/data_network.go +++ b/internal/provider/data_network.go @@ -102,6 +102,40 @@ func dataNetwork() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "dhcp_v6_dns": { + Description: "Specifies the IPv6 addresses for the DNS server to be returned from the DHCP " + + "server. Used if `dhcp_v6_dns_auto` is set to `false`.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "dhcp_v6_dns_auto": { + Description: "Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise", + Type: schema.TypeBool, + Computed: true, + }, + "dhcp_v6_enabled": { + Description: "Enable stateful DHCPv6 for static configuration.", + Type: schema.TypeBool, + Computed: true, + }, + "dhcp_v6_lease": { + Description: "Specifies the lease time for DHCPv6 addresses.", + Type: schema.TypeInt, + Computed: true, + }, + "dhcp_v6_start": { + Description: "Start address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Type: schema.TypeString, + Computed: true, + }, + "dhcp_v6_stop": { + Description: "End address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Type: schema.TypeString, + Computed: true, + }, "domain_name": { Description: "The domain name of this network.", Type: schema.TypeString, @@ -113,7 +147,7 @@ func dataNetwork() *schema.Resource { Computed: true, }, "ipv6_interface_type": { - Description: "Specifies which type of IPv6 connection to use.", + Description: "Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`.", Type: schema.TypeString, Computed: true, }, @@ -123,7 +157,7 @@ func dataNetwork() *schema.Resource { Computed: true, }, "ipv6_pd_interface": { - Description: "Specifies which WAN interface is used for IPv6 Prefix Delegation.", + Description: "Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`.", Type: schema.TypeString, Computed: true, }, @@ -132,11 +166,36 @@ func dataNetwork() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "ipv6_pd_start": { + Description: "Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Type: schema.TypeString, + Computed: true, + }, + "ipv6_pd_stop": { + Description: "End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Type: schema.TypeString, + Computed: true, + }, "ipv6_ra_enable": { Description: "Specifies whether to enable router advertisements or not.", Type: schema.TypeBool, Computed: true, }, + "ipv6_ra_preferred_lifetime": { + Description: "Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime`", + Type: schema.TypeInt, + Computed: true, + }, + "ipv6_ra_priority": { + Description: "IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low`", + Type: schema.TypeString, + Computed: true, + }, + "ipv6_ra_valid_lifetime": { + Description: "Total lifetime in which the address can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`.", + Type: schema.TypeInt, + Computed: true, + }, "wan_ip": { Description: "The IPv4 address of the WAN.", Type: schema.TypeString, @@ -185,6 +244,31 @@ func dataNetwork() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "wan_type_v6": { + Description: "Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`.", + Type: schema.TypeString, + Computed: true, + }, + "wan_dhcp_v6_pd_size": { + Description: "Specifies the IPv6 prefix size to request from ISP. Must be a number between 48 and 64.", + Type: schema.TypeInt, + Computed: true, + }, + "wan_ipv6": { + Description: "The IPv6 address of the WAN.", + Type: schema.TypeString, + Computed: true, + }, + "wan_gateway_v6": { + Description: "The IPv6 gateway of the WAN.", + Type: schema.TypeString, + Computed: true, + }, + "wan_prefixlen": { + Description: "The IPv6 prefix length of the WAN. Must be between 1 and 128.", + Type: schema.TypeInt, + Computed: true, + }, }, } } @@ -264,6 +348,11 @@ func dataNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface d.Set("wan_egress_qos", n.WANEgressQOS) d.Set("wan_username", n.WANUsername) d.Set("x_wan_password", n.XWANPassword) + d.Set("wan_type_v6", n.WANTypeV6) + d.Set("wan_dhcp_v6_pd_size", n.WANDHCPv6PDSize) + d.Set("wan_ipv6", n.WANIPV6) + d.Set("wan_gateway_v6", n.WANGatewayV6) + d.Set("wan_prefixlen", n.WANPrefixlen) return nil } diff --git a/internal/provider/data_port_profile.go b/internal/provider/data_port_profile.go index 266e99b59..45702bfe9 100644 --- a/internal/provider/data_port_profile.go +++ b/internal/provider/data_port_profile.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/provider/data_radius_profile.go b/internal/provider/data_radius_profile.go index 9cdb1b0e2..de1377ee0 100644 --- a/internal/provider/data_radius_profile.go +++ b/internal/provider/data_radius_profile.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/provider/data_user_group.go b/internal/provider/data_user_group.go index 84942a1d5..4de44c424 100644 --- a/internal/provider/data_user_group.go +++ b/internal/provider/data_user_group.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/provider/data_user_test.go b/internal/provider/data_user_test.go index 6d6d78639..802706b2d 100644 --- a/internal/provider/data_user_test.go +++ b/internal/provider/data_user_test.go @@ -3,10 +3,10 @@ package provider import ( "context" "fmt" - "github.com/paultyng/go-unifi/unifi" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/paultyng/go-unifi/unifi" ) func TestAccDataUser_default(t *testing.T) { diff --git a/internal/provider/resource_dynamic_dns.go b/internal/provider/resource_dynamic_dns.go index e248b2377..c0e850b34 100644 --- a/internal/provider/resource_dynamic_dns.go +++ b/internal/provider/resource_dynamic_dns.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/paultyng/go-unifi/unifi" ) diff --git a/internal/provider/resource_firewall_group.go b/internal/provider/resource_firewall_group.go index b6454c333..4cb54c70b 100644 --- a/internal/provider/resource_firewall_group.go +++ b/internal/provider/resource_firewall_group.go @@ -3,8 +3,8 @@ package provider import ( "context" "errors" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" diff --git a/internal/provider/resource_firewall_rule.go b/internal/provider/resource_firewall_rule.go index d6dffc247..5ee8517d4 100644 --- a/internal/provider/resource_firewall_rule.go +++ b/internal/provider/resource_firewall_rule.go @@ -3,15 +3,17 @@ package provider import ( "context" "errors" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "regexp" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" ) var firewallRuleProtocolRegexp = regexp.MustCompile("^$|all|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|tcp_udp|ah|ax.25|dccp|ddp|egp|eigrp|encap|esp|etherip|fc|ggp|gre|hip|hmp|icmp|idpr-cmtp|idrp|igmp|igp|ip|ipcomp|ipencap|ipip|ipv6|ipv6-frag|ipv6-icmp|ipv6-nonxt|ipv6-opts|ipv6-route|isis|iso-tp4|l2tp|manet|mobility-header|mpls-in-ip|ospf|pim|pup|rdp|rohc|rspf|rsvp|sctp|shim6|skip|st|tcp|udp|udplite|vmtp|vrrp|wesp|xns-idp|xtp") +var firewallRuleProtocolV6Regexp = regexp.MustCompile("^$|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|ah|all|dccp|eigrp|esp|gre|icmpv6|ipcomp|ipv6|ipv6-frag|ipv6-icmp|ipv6-nonxt|ipv6-opts|ipv6-route|isis|l2tp|manet|mobility-header|mpls-in-ip|ospf|pim|rsvp|sctp|shim6|tcp|tcp_udp|udp|vrrp") +var firewallRuleICMPv6TypenameRegexp = regexp.MustCompile("^$|address-unreachable|bad-header|beyond-scope|communication-prohibited|destination-unreachable|echo-reply|echo-request|failed-policy|neighbor-advertisement|neighbor-solicitation|no-route|packet-too-big|parameter-problem|port-unreachable|redirect|reject-route|router-advertisement|router-solicitation|time-exceeded|ttl-zero-during-reassembly|ttl-zero-during-transit|unknown-header-type|unknown-option") func resourceFirewallRule() *schema.Resource { return &schema.Resource{ @@ -67,14 +69,26 @@ func resourceFirewallRule() *schema.Resource { "protocol": { Description: "The protocol of the rule.", Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringMatch(firewallRuleProtocolRegexp, "must be a valid protocol"), + Optional: true, + ValidateFunc: validation.StringMatch(firewallRuleProtocolRegexp, "must be a valid IPv4 protocol"), + }, + "protocol_v6": { + Description: "The IPv6 protocol of the rule.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(firewallRuleProtocolV6Regexp, "must be a valid IPv6 protocol"), }, "icmp_typename": { Description: "ICMP type name.", Type: schema.TypeString, Optional: true, }, + "icmp_v6_typename": { + Description: "ICMPv6 type name.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(firewallRuleICMPv6TypenameRegexp, "must be a ICMPv6 type"), + }, // sources "src_network_id": { @@ -100,6 +114,11 @@ func resourceFirewallRule() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "src_address_ipv6": { + Description: "The IPv6 source address for the firewall rule.", + Type: schema.TypeString, + Optional: true, + }, "src_mac": { Description: "The source MAC address of the firewall rule.", Type: schema.TypeString, @@ -130,6 +149,11 @@ func resourceFirewallRule() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "dst_address_ipv6": { + Description: "The IPv6 destination address of the firewall rule.", + Type: schema.TypeString, + Optional: true, + }, "dst_port": { Description: "The destination port of the firewall rule.", Type: schema.TypeString, @@ -219,7 +243,9 @@ func resourceFirewallRuleGetResourceData(d *schema.ResourceData) (*unifi.Firewal Ruleset: d.Get("ruleset").(string), RuleIndex: d.Get("rule_index").(int), Protocol: d.Get("protocol").(string), + ProtocolV6: d.Get("protocol_v6").(string), ICMPTypename: d.Get("icmp_typename").(string), + ICMPv6Typename: d.Get("icmp_v6_typename").(string), Logging: d.Get("logging").(bool), IPSec: d.Get("ip_sec").(string), StateEstablished: d.Get("state_established").(bool), @@ -230,11 +256,13 @@ func resourceFirewallRuleGetResourceData(d *schema.ResourceData) (*unifi.Firewal SrcNetworkType: d.Get("src_network_type").(string), SrcMACAddress: d.Get("src_mac").(string), SrcAddress: d.Get("src_address").(string), + SrcAddressIPV6: d.Get("src_address_ipv6").(string), SrcNetworkID: d.Get("src_network_id").(string), SrcFirewallGroupIDs: srcFirewallGroupIDs, DstNetworkType: d.Get("dst_network_type").(string), DstAddress: d.Get("dst_address").(string), + DstAddressIPV6: d.Get("dst_address_ipv6").(string), DstPort: d.Get("dst_port").(string), DstNetworkID: d.Get("dst_network_id").(string), DstFirewallGroupIDs: dstFirewallGroupIDs, @@ -248,7 +276,9 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res d.Set("ruleset", resp.Ruleset) d.Set("rule_index", resp.RuleIndex) d.Set("protocol", resp.Protocol) + d.Set("protocol_v6", resp.ProtocolV6) d.Set("icmp_typename", resp.ICMPTypename) + d.Set("icmp_v6_typename", resp.ICMPv6Typename) d.Set("logging", resp.Logging) d.Set("ip_sec", resp.IPSec) d.Set("state_established", resp.StateEstablished) @@ -256,17 +286,17 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res d.Set("state_new", resp.StateNew) d.Set("state_related", resp.StateRelated) - // TODO: handle IPv6 d.Set("src_network_type", resp.SrcNetworkType) d.Set("src_firewall_group_ids", stringSliceToSet(resp.SrcFirewallGroupIDs)) d.Set("src_mac", resp.SrcMACAddress) d.Set("src_address", resp.SrcAddress) + d.Set("src_address_ipv6", resp.SrcAddressIPV6) d.Set("src_network_id", resp.SrcNetworkID) - // TODO: handle IPv6 d.Set("dst_network_type", resp.DstNetworkType) d.Set("dst_firewall_group_ids", stringSliceToSet(resp.DstFirewallGroupIDs)) d.Set("dst_address", resp.DstAddress) + d.Set("dst_address_ipv6", resp.DstAddressIPV6) d.Set("dst_network_id", resp.DstNetworkID) d.Set("dst_port", resp.DstPort) diff --git a/internal/provider/resource_firewall_rule_test.go b/internal/provider/resource_firewall_rule_test.go index d34b8d534..688cdb85d 100644 --- a/internal/provider/resource_firewall_rule_test.go +++ b/internal/provider/resource_firewall_rule_test.go @@ -95,6 +95,33 @@ func TestAccFirewallRule_address_and_port_group(t *testing.T) { }) } +func TestAccFirewallRule_IPv6_basic(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + ProviderFactories: providerFactories, + // TODO: CheckDestroy: , + Steps: []resource.TestStep{ + { + Config: testAccFirewallRuleConfigIPv6, + }, + importStep("unifi_firewall_rule.test"), + }, + }) +} + +func TestAccFirewallRule_IPv6_dst_port(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccFirewallRuleConfigIPv6WithPort, + }, + importStep("unifi_firewall_rule.test"), + }, + }) +} + // func TestAccFirewallRule_firewall_group(t *testing.T) { // func TestAccFirewallRule_network(t *testing.T) { @@ -249,15 +276,48 @@ resource "unifi_firewall_rule" "test" { } ` -// resource "unifi_firewall_rule" "can_print_drop" { -// name = "[tf] can-print (drop all)" -// action = "drop" -// ruleset = "LAN_IN" +const testAccFirewallRuleConfigIPv6 = ` +resource "unifi_firewall_group" "test_a" { + name = "tf acc rule IPv6 group a" + type = "ipv6-address-group" -// rule_index = 2011 + members = ["fd6a:37be:e364::/64", "fd6a:37be:e365::/64",] +} + +resource "unifi_firewall_group" "test_b" { + name = "tf acc rule IPv6 group b" + type = "ipv6-address-group" + + members = ["2001:4860:4860::8888", "2001:4860:4860::8844"] +} + +resource "unifi_firewall_rule" "test" { + name = "tf acc" + action = "drop" + ruleset = "LANv6_IN" + + rule_index = 2510 -// protocol = "all" + protocol_v6 = "all" -// dst_address = "192.168.1.1" -// } -// ` + src_firewall_group_ids = [unifi_firewall_group.test_a.id] + + dst_firewall_group_ids = [unifi_firewall_group.test_b.id] +} +` + +const testAccFirewallRuleConfigIPv6WithPort = ` +resource "unifi_firewall_rule" "test" { + name = "tf acc" + action = "accept" + ruleset = "LANv6_IN" + + rule_index = 2511 + + protocol = "tcp" + + src_address_ipv6 = "fd6a:37be:e364::1/64" + dst_address_ipv6 = "fd6a:37be:e364::2/64" + dst_port = 53 +} +` diff --git a/internal/provider/resource_network.go b/internal/provider/resource_network.go index 850935793..ae7e2dc16 100644 --- a/internal/provider/resource_network.go +++ b/internal/provider/resource_network.go @@ -19,11 +19,23 @@ var ( wanTypeRegexp = regexp.MustCompile("disabled|dhcp|static|pppoe") validateWANType = validation.StringMatch(wanTypeRegexp, "invalid WAN connection type") + wanTypeV6Regexp = regexp.MustCompile("disabled|dhcpv6|static") + validateWANTypeV6 = validation.StringMatch(wanTypeV6Regexp, "invalid WANv6 connection type") + wanPasswordRegexp = regexp.MustCompile("[^\"' ]+") validateWANPassword = validation.StringMatch(wanPasswordRegexp, "invalid WAN password") wanNetworkGroupRegexp = regexp.MustCompile("WAN[2]?|WAN_LTE_FAILOVER") validateWANNetworkGroup = validation.StringMatch(wanNetworkGroupRegexp, "invalid WAN network group") + + wanV6NetworkGroupRegexp = regexp.MustCompile("wan[2]?") + validateWANV6NetworkGroup = validation.StringMatch(wanV6NetworkGroupRegexp, "invalid WANv6 network group") + + ipV6InterfaceTypeRegexp = regexp.MustCompile("none|pd|static") + validateIpV6InterfaceType = validation.StringMatch(ipV6InterfaceTypeRegexp, "invalid IPv6 interface type") + + // This is a slightly larger range than the UI, it includes some reserved ones, so could be tightened up. + validateVLANID = validation.IntBetween(0, 4096) ) func resourceNetwork() *schema.Resource { @@ -64,9 +76,10 @@ func resourceNetwork() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"corporate", "guest", "wan", "vlan-only"}, false), }, "vlan_id": { - Description: "The VLAN ID of the network.", - Type: schema.TypeInt, - Optional: true, + Description: "The VLAN ID of the network.", + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validateVLANID, }, "subnet": { Description: "The subnet of the network. Must be a valid CIDR address.", @@ -99,7 +112,7 @@ func resourceNetwork() *schema.Resource { Optional: true, }, "dhcp_lease": { - Description: "Specifies the lease time for DHCP addresses.", + Description: "Specifies the lease time for DHCP addresses in seconds.", Type: schema.TypeInt, Optional: true, Default: 86400, @@ -140,6 +153,47 @@ func resourceNetwork() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "dhcp_v6_dns": { + Description: "Specifies the IPv6 addresses for the DNS server to be returned from the DHCP " + + "server. Used if `dhcp_v6_dns_auto` is set to `false`.", + Type: schema.TypeList, + Optional: true, + MaxItems: 4, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv6Address, + // TODO: should this ensure blank can't get through? + }, + }, + "dhcp_v6_dns_auto": { + Description: "Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "dhcp_v6_enabled": { + Description: "Enable stateful DHCPv6 for static configuration.", + Type: schema.TypeBool, + Optional: true, + }, + "dhcp_v6_lease": { + Description: "Specifies the lease time for DHCPv6 addresses in seconds.", + Type: schema.TypeInt, + Optional: true, + Default: 86400, + }, + "dhcp_v6_start": { + Description: "Start address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, + "dhcp_v6_stop": { + Description: "End address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, "domain_name": { Description: "The domain name of this network.", Type: schema.TypeString, @@ -151,26 +205,40 @@ func resourceNetwork() *schema.Resource { Optional: true, }, "ipv6_interface_type": { - Description: "Specifies which type of IPv6 connection to use.", - Type: schema.TypeString, - Optional: true, - Default: "none", + Description: "Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`.", + Type: schema.TypeString, + Optional: true, + Default: "none", + ValidateFunc: validateIpV6InterfaceType, }, "ipv6_static_subnet": { - Description: "Specifies the static IPv6 subnet when ipv6_interface_type is 'static'.", + Description: "Specifies the static IPv6 subnet when `ipv6_interface_type` is 'static'.", Type: schema.TypeString, Optional: true, }, "ipv6_pd_interface": { - Description: "Specifies which WAN interface to use for IPv6 PD.", - Type: schema.TypeString, - Optional: true, + Description: "Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateWANV6NetworkGroup, }, "ipv6_pd_prefixid": { Description: "Specifies the IPv6 Prefix ID.", Type: schema.TypeString, Optional: true, }, + "ipv6_pd_start": { + Description: "Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, + "ipv6_pd_stop": { + Description: "End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, "ipv6_ra_enable": { Description: "Specifies whether to enable router advertisements or not.", Type: schema.TypeBool, @@ -188,6 +256,24 @@ func resourceNetwork() *schema.Resource { Optional: true, Default: true, }, + "ipv6_ra_preferred_lifetime": { + Description: "Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime`", + Type: schema.TypeInt, + Optional: true, + Default: 14400, + }, + "ipv6_ra_priority": { + Description: "IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low`", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv4Address, + }, + "ipv6_ra_valid_lifetime": { + Description: "Total lifetime in which the address can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`.", + Type: schema.TypeInt, + Optional: true, + Default: 86400, + }, "wan_ip": { Description: "The IPv4 address of the WAN.", Type: schema.TypeString, @@ -246,6 +332,36 @@ func resourceNetwork() *schema.Resource { Optional: true, ValidateFunc: validateWANPassword, }, + "wan_type_v6": { + Description: "Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateWANTypeV6, + }, + "wan_dhcp_v6_pd_size": { + Description: "Specifies the IPv6 prefix size to request from ISP. Must be between 48 and 64.", + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(48, 64), + }, + "wan_ipv6": { + Description: "The IPv6 address of the WAN.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, + "wan_gateway_v6": { + Description: "The IPv6 gateway of the WAN.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv6Address, + }, + "wan_prefixlen": { + Description: "The IPv6 prefix length of the WAN. Must be between 1 and 128.", + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 128), + }, }, } } @@ -281,6 +397,10 @@ func resourceNetworkGetResourceData(d *schema.ResourceData, meta interface{}) (* if err != nil { return nil, fmt.Errorf("unable to convert dhcp_dns to string slice: %w", err) } + dhcpV6DNS, err := listToStringSlice(d.Get("dhcp_v6_dns").([]interface{})) + if err != nil { + return nil, fmt.Errorf("unable to convert dhcp_v6_dns to string slice: %w", err) + } wanDNS, err := listToStringSlice(d.Get("wan_dns").([]interface{})) if err != nil { return nil, fmt.Errorf("unable to convert wan_dns to string slice: %w", err) @@ -314,11 +434,28 @@ func resourceNetworkGetResourceData(d *schema.ResourceData, meta interface{}) (* Enabled: true, - IPV6InterfaceType: d.Get("ipv6_interface_type").(string), - IPV6Subnet: d.Get("ipv6_static_subnet").(string), - IPV6PDInterface: d.Get("ipv6_pd_interface").(string), - IPV6PDPrefixid: d.Get("ipv6_pd_prefixid").(string), - IPV6RaEnabled: d.Get("ipv6_ra_enable").(bool), + // Same hackish code as for DHCPv4 ¯\_(ツ)_/¯ + DHCPDV6DNS1: append(dhcpV6DNS, "")[0], + DHCPDV6DNS2: append(dhcpV6DNS, "", "")[1], + DHCPDV6DNS3: append(dhcpV6DNS, "", "", "")[2], + DHCPDV6DNS4: append(dhcpV6DNS, "", "", "", "")[3], + + DHCPDV6DNSAuto: d.Get("dhcp_v6_dns_auto").(bool), + DHCPDV6Enabled: d.Get("dhcp_v6_enabled").(bool), + DHCPDV6LeaseTime: d.Get("dhcp_v6_lease").(int), + DHCPDV6Start: d.Get("dhcp_v6_start").(string), + DHCPDV6Stop: d.Get("dhcp_v6_stop").(string), + + IPV6InterfaceType: d.Get("ipv6_interface_type").(string), + IPV6Subnet: d.Get("ipv6_static_subnet").(string), + IPV6PDInterface: d.Get("ipv6_pd_interface").(string), + IPV6PDPrefixid: d.Get("ipv6_pd_prefixid").(string), + IPV6PDStart: d.Get("ipv6_pd_start").(string), + IPV6PDStop: d.Get("ipv6_pd_stop").(string), + IPV6RaEnabled: d.Get("ipv6_ra_enable").(bool), + IPV6RaPreferredLifetime: d.Get("ipv6_ra_preferred_lifetime").(int), + IPV6RaPriority: d.Get("ipv6_ra_priority").(string), + IPV6RaValidLifetime: d.Get("ipv6_ra_valid_lifetime").(int), InternetAccessEnabled: d.Get("internet_access_enabled").(bool), IntraNetworkAccessEnabled: d.Get("intra_network_access_enabled").(bool), @@ -332,6 +469,12 @@ func resourceNetworkGetResourceData(d *schema.ResourceData, meta interface{}) (* WANUsername: d.Get("wan_username").(string), XWANPassword: d.Get("x_wan_password").(string), + WANTypeV6: d.Get("wan_type_v6").(string), + WANDHCPv6PDSize: d.Get("wan_dhcp_v6_pd_size").(int), + WANIPV6: d.Get("wan_ipv6").(string), + WANGatewayV6: d.Get("wan_gateway_v6").(string), + WANPrefixlen: d.Get("wan_prefixlen").(int), + // this is kinda hacky but ¯\_(ツ)_/¯ WANDNS1: append(wanDNS, "")[0], WANDNS2: append(wanDNS, "", "")[1], @@ -396,37 +539,67 @@ func resourceNetworkSetResourceData(resp *unifi.Network, d *schema.ResourceData, } } + dhcpV6DNS := []string{} + for _, dns := range []string{ + resp.DHCPDV6DNS1, + resp.DHCPDV6DNS2, + resp.DHCPDV6DNS3, + resp.DHCPDV6DNS4, + } { + if dns == "" { + continue + } + dhcpV6DNS = append(dhcpV6DNS, dns) + } + d.Set("site", site) d.Set("name", resp.Name) d.Set("purpose", resp.Purpose) d.Set("vlan_id", vlan) d.Set("subnet", cidrZeroBased(resp.IPSubnet)) d.Set("network_group", resp.NetworkGroup) - d.Set("dhcp_start", resp.DHCPDStart) - d.Set("dhcp_stop", resp.DHCPDStop) + + d.Set("dhcp_dns", dhcpDNS) d.Set("dhcp_enabled", resp.DHCPDEnabled) d.Set("dhcp_lease", dhcpLease) + d.Set("dhcp_relay_enabled", resp.DHCPRelayEnabled) + d.Set("dhcp_start", resp.DHCPDStart) + d.Set("dhcp_stop", resp.DHCPDStop) + d.Set("dhcp_v6_dns_auto", resp.DHCPDV6DNSAuto) + d.Set("dhcp_v6_dns", dhcpV6DNS) + d.Set("dhcp_v6_enabled", resp.DHCPDV6Enabled) + d.Set("dhcp_v6_lease", resp.DHCPDV6LeaseTime) + d.Set("dhcp_v6_start", resp.DHCPDV6Start) + d.Set("dhcp_v6_stop", resp.DHCPDV6Stop) d.Set("dhcpd_boot_enabled", resp.DHCPDBootEnabled) - d.Set("dhcpd_boot_server", resp.DHCPDBootServer) d.Set("dhcpd_boot_filename", resp.DHCPDBootFilename) - d.Set("dhcp_relay_enabled", resp.DHCPRelayEnabled) + d.Set("dhcpd_boot_server", resp.DHCPDBootServer) d.Set("domain_name", resp.DomainName) d.Set("igmp_snooping", resp.IGMPSnooping) - d.Set("dhcp_dns", dhcpDNS) + d.Set("internet_access_enabled", resp.InternetAccessEnabled) + d.Set("intra_network_access_enabled", resp.IntraNetworkAccessEnabled) d.Set("ipv6_interface_type", resp.IPV6InterfaceType) - d.Set("ipv6_static_subnet", resp.IPV6Subnet) d.Set("ipv6_pd_interface", resp.IPV6PDInterface) d.Set("ipv6_pd_prefixid", resp.IPV6PDPrefixid) + d.Set("ipv6_pd_start", resp.IPV6PDStart) + d.Set("ipv6_pd_stop", resp.IPV6PDStop) d.Set("ipv6_ra_enable", resp.IPV6RaEnabled) - d.Set("internet_access_enabled", resp.InternetAccessEnabled) - d.Set("intra_network_access_enabled", resp.IntraNetworkAccessEnabled) + d.Set("ipv6_ra_preferred_lifetime", resp.IPV6RaPreferredLifetime) + d.Set("ipv6_ra_priority", resp.IPV6RaPriority) + d.Set("ipv6_ra_valid_lifetime", resp.IPV6RaValidLifetime) + d.Set("ipv6_static_subnet", resp.IPV6Subnet) + d.Set("wan_dhcp_v6_pd_size", resp.WANDHCPv6PDSize) + d.Set("wan_dns", wanDNS) + d.Set("wan_egress_qos", resp.WANEgressQOS) + d.Set("wan_gateway_v6", resp.WANGatewayV6) + d.Set("wan_gateway", wanGateway) d.Set("wan_ip", wanIP) + d.Set("wan_ipv6", resp.WANIPV6) d.Set("wan_netmask", wanNetmask) - d.Set("wan_gateway", wanGateway) - d.Set("wan_type", wanType) - d.Set("wan_dns", wanDNS) d.Set("wan_networkgroup", resp.WANNetworkGroup) - d.Set("wan_egress_qos", resp.WANEgressQOS) + d.Set("wan_prefixlen", resp.WANPrefixlen) + d.Set("wan_type_v6", resp.WANTypeV6) + d.Set("wan_type", wanType) d.Set("wan_username", resp.WANUsername) d.Set("x_wan_password", resp.XWANPassword) diff --git a/internal/provider/resource_network_test.go b/internal/provider/resource_network_test.go index ab28388aa..aaffaaaa5 100644 --- a/internal/provider/resource_network_test.go +++ b/internal/provider/resource_network_test.go @@ -133,6 +133,7 @@ func TestAccNetwork_v6(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") vlanID1 := getTestVLAN(t) vlanID2 := getTestVLAN(t) + vlanID3 := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) }, @@ -156,6 +157,31 @@ func TestAccNetwork_v6(t *testing.T) { ), }, importStep("unifi_network.test"), + { + Config: testAccNetworkConfigDhcpV6( + name, + vlanID3, + "fd6a:37be:e364::1/64", + "fd6a:37be:e364::2", + "fd6a:37be:e364::7d1", + []string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID3)), + resource.TestCheckResourceAttr("unifi_network.test", "dhcp_v6_start", "fd6a:37be:e364::2"), + resource.TestCheckResourceAttr("unifi_network.test", "dhcp_v6_stop", "fd6a:37be:e364::7d1"), + resource.TestCheckResourceAttr("unifi_network.test", "dhcp_v6_lease", strconv.Itoa(12*60*60)), + ), + }, + { + Config: testAccNetworkConfigDhcpV6( + name, + vlanID3, + "fd6a:37be:e365::1/64", + "fd6a:37be:e364::2", + "fd6a:37be:e364::7d1", + []string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), + ExpectError: regexp.MustCompile(regexp.QuoteMeta("api.err.InvalidDHCPv6Range")), + }, }, }) } @@ -198,6 +224,21 @@ func TestAccNetwork_wan(t *testing.T) { ), }, importStep("unifi_network.wan_test"), + { + Config: testWanV6NetworkConfig(name, "dhcpv6", 47), + ExpectError: regexp.MustCompile(regexp.QuoteMeta("expected wan_dhcp_v6_pd_size to be in the range (48 - 64)")), + }, + { + Config: testWanV6NetworkConfig(name, "invalid", 48), + ExpectError: regexp.MustCompile(regexp.QuoteMeta("invalid value for wan_type_v6")), + }, + { + Config: testWanV6NetworkConfig(name, "dhcpv6", 48), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_type_v6", "dhcpv6"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_dhcp_v6_pd_size", "48"), + ), + }, }, }) } @@ -318,6 +359,7 @@ func TestAccNetwork_dhcpRelay(t *testing.T) { func TestAccNetwork_vlanOnly(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") + vlanID := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -327,9 +369,9 @@ func TestAccNetwork_vlanOnly(t *testing.T) { // TODO: CheckDestroy: , Steps: []resource.TestStep{ { - Config: testAccNetworkVlanOnly(name), + Config: testAccNetworkVlanOnly(name, vlanID), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", "101"), + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID)), ), }, { @@ -453,6 +495,26 @@ output "wan_dns2" { `, name, networkGroup, wanType, wanIP, wanEgressQOS, wanUsername, wanPassword, wanDNS1, wanDNS2) } +func testWanV6NetworkConfig(name string, wanTypeV6 string, wanDhcpV6PdSize int) string { + return fmt.Sprintf(` +resource "unifi_network" "wan_test" { + name = "%s" + purpose = "wan" + wan_networkgroup = "WAN" + wan_type = "pppoe" + wan_ip = "192.168.1.1" + wan_egress_qos = 1 + wan_username = "username" + x_wan_password = "password" + + wan_dns = ["8.8.8.8", "4.4.4.4"] + + wan_type_v6 = "%s" + wan_dhcp_v6_pd_size = %d +} +`, name, wanTypeV6, wanDhcpV6PdSize) +} + func testAccNetworkWithSiteConfig(name string, vlan int) string { return fmt.Sprintf(` locals { @@ -527,7 +589,7 @@ resource "unifi_network" "test" { `, name, vlan, dhcpRelay) } -func testAccNetworkVlanOnly(name string) string { +func testAccNetworkVlanOnly(name string, vlan int) string { return fmt.Sprintf(` resource "unifi_site" "test" { description = "%[1]s" @@ -537,7 +599,33 @@ resource "unifi_network" "test" { site = unifi_site.test.name name = "test" purpose = "vlan-only" - vlan_id = 101 + vlan_id = %[2]d +} +`, name, vlan) +} + +func testAccNetworkConfigDhcpV6(name string, vlan int, gatewayIP string, dhcpdV6Start string, dhcpdV6Stop string, dhcpV6DNS []string) string { + return fmt.Sprintf(` +locals { + subnet = cidrsubnet("10.0.0.0/8", 6, %[2]d) + vlan_id = %[2]d +} + +resource "unifi_network" "test" { + name = "%[1]s" + purpose = "corporate" + + subnet = local.subnet + vlan_id = local.vlan_id + + ipv6_static_subnet = "%[3]s" + + dhcp_v6_dns_auto = false + dhcp_v6_dns = [%[6]s] + dhcp_v6_enabled = true + dhcp_v6_start = "%[4]s" + dhcp_v6_stop = "%[5]s" + dhcp_v6_lease = 12 * 60 * 60 } -`, name) +`, name, vlan, gatewayIP, dhcpdV6Start, dhcpdV6Stop, strings.Join(quoteStrings(dhcpV6DNS), ",")) } diff --git a/internal/provider/resource_port_forward.go b/internal/provider/resource_port_forward.go index d1b27d54b..ffa5d8c67 100644 --- a/internal/provider/resource_port_forward.go +++ b/internal/provider/resource_port_forward.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" diff --git a/internal/provider/resource_port_profile.go b/internal/provider/resource_port_profile.go index ab3fc9d40..dcf1fe33a 100644 --- a/internal/provider/resource_port_profile.go +++ b/internal/provider/resource_port_profile.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" diff --git a/internal/provider/resource_radius_profile.go b/internal/provider/resource_radius_profile.go index a116e0d17..1b23129f6 100644 --- a/internal/provider/resource_radius_profile.go +++ b/internal/provider/resource_radius_profile.go @@ -3,11 +3,12 @@ package provider import ( "context" "fmt" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" - "strings" ) func resourceRadiusProfile() *schema.Resource { diff --git a/internal/provider/resource_radius_profile_test.go b/internal/provider/resource_radius_profile_test.go index 2d94d6c6e..e223231ed 100644 --- a/internal/provider/resource_radius_profile_test.go +++ b/internal/provider/resource_radius_profile_test.go @@ -2,8 +2,9 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccRadiusProfile_basic(t *testing.T) { diff --git a/internal/provider/resource_setting_radius.go b/internal/provider/resource_setting_radius.go index ef23f84d2..984d9dd16 100644 --- a/internal/provider/resource_setting_radius.go +++ b/internal/provider/resource_setting_radius.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/internal/provider/resource_static_route.go b/internal/provider/resource_static_route.go index e9f54e74c..aaad3923f 100644 --- a/internal/provider/resource_static_route.go +++ b/internal/provider/resource_static_route.go @@ -3,8 +3,8 @@ package provider import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" diff --git a/internal/provider/resource_user.go b/internal/provider/resource_user.go index fbecd44a7..2b05a9397 100644 --- a/internal/provider/resource_user.go +++ b/internal/provider/resource_user.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/paultyng/go-unifi/unifi" diff --git a/internal/provider/resource_user_group.go b/internal/provider/resource_user_group.go index 5f1dcec99..7d9a445c4 100644 --- a/internal/provider/resource_user_group.go +++ b/internal/provider/resource_user_group.go @@ -2,8 +2,8 @@ package provider import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/paultyng/go-unifi/unifi" )