From cbc9bdc85f0e617988e207c896d3346d05455cd2 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Wed, 21 Oct 2020 09:24:19 -0400 Subject: [PATCH] Added basic WAN support, including WAN type, network group, WAN IP, username and password --- docs/resources/network.md | 24 ++++++- examples/resources/unifi_network/resource.tf | 14 +++- internal/provider/resource_network.go | 71 +++++++++++++++++++- internal/provider/resource_network_test.go | 49 ++++++++++++++ 4 files changed, 151 insertions(+), 7 deletions(-) diff --git a/docs/resources/network.md b/docs/resources/network.md index 7509640..3881570 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -2,12 +2,12 @@ page_title: "unifi_network Resource - terraform-provider-unifi" subcategory: "" description: |- - unifi_network manages LAN/VLAN networks. + unifi_network manages WAN/LAN/VLAN networks. --- # Resource `unifi_network` -`unifi_network` manages LAN/VLAN networks. +`unifi_network` manages WAN/LAN/VLAN networks. ## Example Usage @@ -26,6 +26,18 @@ resource "unifi_network" "vlan" { dhcp_stop = "10.0.0.254" dhcp_enabled = true } + +resource "unifi_network" "wan" { + name = "wan" + 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" +} ``` ## Schema @@ -33,7 +45,7 @@ resource "unifi_network" "vlan" { ### Required - **name** (String, Required) The name of the network. -- **purpose** (String, Required) The purpose of the network. Must be one of `corporate`, `guest`, or `vlan-only`. +- **purpose** (String, Required) The purpose of the network. Must be one of `corporate`, `guest`, `wan`, or `vlan-only`. ### Optional @@ -52,6 +64,12 @@ resource "unifi_network" "vlan" { - **network_group** (String, Optional) The group of the network. Defaults to `LAN`. - **subnet** (String, Optional) The subnet of the network. Must be a valid CIDR address. - **vlan_id** (Number, Optional) The VLAN ID of the network. +- **wan_egress_qos** (Number, Optional) Specifies the WAN egress quality of service. Defaults to `0`. +- **wan_ip** (String, Optional) The IPv4 address of the WAN. +- **wan_networkgroup** (String, Optional) Specifies the WAN network group. Must be one of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`. +- **wan_type** (String, Optional) Specifies the IPV4 WAN connection type. Must be one of either `disabled` or `pppoe`. Defaults to `disabled`. +- **wan_username** (String, Optional) Specifies the IPV4 WAN username. +- **x_wan_password** (String, Optional) Specifies the IPV4 WAN password. ### Read-only diff --git a/examples/resources/unifi_network/resource.tf b/examples/resources/unifi_network/resource.tf index d6c0878..8120d23 100644 --- a/examples/resources/unifi_network/resource.tf +++ b/examples/resources/unifi_network/resource.tf @@ -11,4 +11,16 @@ resource "unifi_network" "vlan" { dhcp_start = "10.0.0.6" dhcp_stop = "10.0.0.254" dhcp_enabled = true -} \ No newline at end of file +} + +resource "unifi_network" "wan" { + name = "wan" + 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" +} diff --git a/internal/provider/resource_network.go b/internal/provider/resource_network.go index 0a0984a..52796ef 100644 --- a/internal/provider/resource_network.go +++ b/internal/provider/resource_network.go @@ -3,15 +3,30 @@ package provider import ( "context" "fmt" + "regexp" "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 ( + wanUsernameRegexp = regexp.MustCompile("[^\"' ]+") + validateWANUsername = validation.StringMatch(wanUsernameRegexp, "invalid WAN username") + + wanTypeRegexp = regexp.MustCompile("disabled|dhcp|static|pppoe") + validateWANType = validation.StringMatch(wanTypeRegexp, "invalid WAN 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") +) + func resourceNetwork() *schema.Resource { return &schema.Resource{ - Description: "`unifi_network` manages LAN/VLAN networks.", + Description: "`unifi_network` manages WAN/LAN/VLAN networks.", Create: resourceNetworkCreate, Read: resourceNetworkRead, @@ -33,11 +48,11 @@ func resourceNetwork() *schema.Resource { Required: true, }, "purpose": { - Description: "The purpose of the network. Must be one of `corporate`, `guest`, or `vlan-only`.", + Description: "The purpose of the network. Must be one of `corporate`, `guest`, `wan`, or `vlan-only`.", Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"corporate", "guest", "vlan-only"}, false), + ValidateFunc: validation.StringInSlice([]string{"corporate", "guest", "wan", "vlan-only"}, false), }, "vlan_id": { Description: "The VLAN ID of the network.", @@ -131,6 +146,43 @@ func resourceNetwork() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "wan_ip": { + Description: "The IPv4 address of the WAN.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv4Address, + }, + "wan_type": { + Description: "Specifies the IPV4 WAN connection type. Must be one of either `disabled` or `pppoe`.", + Type: schema.TypeString, + Optional: true, + Default: "disabled", + ValidateFunc: validateWANType, + }, + "wan_networkgroup": { + Description: "Specifies the WAN network group. Must be one of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateWANNetworkGroup, + }, + "wan_egress_qos": { + Description: "Specifies the WAN egress quality of service.", + Type: schema.TypeInt, + Optional: true, + Default: 0, + }, + "wan_username": { + Description: "Specifies the IPV4 WAN username.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateWANUsername, + }, + "x_wan_password": { + Description: "Specifies the IPV4 WAN password.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateWANPassword, + }, }, } } @@ -189,6 +241,13 @@ func resourceNetworkGetResourceData(d *schema.ResourceData) (*unifi.Network, err IPV6PDInterface: d.Get("ipv6_pd_interface").(string), IPV6PDPrefixid: d.Get("ipv6_pd_prefixid").(string), IPV6RaEnabled: d.Get("ipv6_ra_enable").(bool), + + WANIP: d.Get("wan_ip").(string), + WANType: d.Get("wan_type").(string), + WANNetworkGroup: d.Get("wan_networkgroup").(string), + WANEgressQOS: d.Get("wan_egress_qos").(int), + WANUsername: d.Get("wan_username").(string), + XWANPassword: d.Get("x_wan_password").(string), }, nil } @@ -235,6 +294,12 @@ func resourceNetworkSetResourceData(resp *unifi.Network, d *schema.ResourceData) d.Set("ipv6_pd_interface", resp.IPV6PDInterface) d.Set("ipv6_pd_prefixid", resp.IPV6PDPrefixid) d.Set("ipv6_ra_enable", resp.IPV6RaEnabled) + d.Set("wan_ip", resp.WANIP) + d.Set("wan_type", resp.WANType) + d.Set("wan_networkgroup", resp.WANNetworkGroup) + d.Set("wan_egress_qos", resp.WANEgressQOS) + d.Set("wan_username", resp.WANUsername) + d.Set("x_wan_password", resp.XWANPassword) return nil } diff --git a/internal/provider/resource_network_test.go b/internal/provider/resource_network_test.go index 87bf983..711e0d0 100644 --- a/internal/provider/resource_network_test.go +++ b/internal/provider/resource_network_test.go @@ -126,6 +126,40 @@ func TestAccNetwork_v6(t *testing.T) { }) } +func TestAccNetwork_wan(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + ProviderFactories: providerFactories, + // TODO: CheckDestroy: , + Steps: []resource.TestStep{ + { + Config: testWanNetworkConfig("WAN", "pppoe", "192.168.1.1", 1, "username", "password"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_networkgroup", "WAN"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_type", "pppoe"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_ip", "192.168.1.1"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_egress_qos", "1"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_username", "username"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "x_wan_password", "password"), + ), + }, + importStep("unifi_network.wan_test"), + { + Config: testWanNetworkConfig("WAN", "pppoe", "192.168.1.1", 1, "username", "password"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_networkgroup", "WAN"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_type", "pppoe"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_ip", "192.168.1.1"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_egress_qos", "1"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "wan_username", "username"), + resource.TestCheckResourceAttr("unifi_network.wan_test", "x_wan_password", "password"), + ), + }, + importStep("unifi_network.wan_test"), + }, + }) +} + // TODO: ipv6 prefix delegation test func quoteStrings(src []string) []string { @@ -184,3 +218,18 @@ resource "unifi_network" "test" { } `, vlan, ipv6Type, ipv6Subnet) } + +func testWanNetworkConfig(networkGroup string, wanType string, wanIP string, wanEgressQOS int, wanUsername string, wanPassword string) string { + return fmt.Sprintf(` +resource "unifi_network" "wan_test" { + name = "tfwan" + purpose = "wan" + wan_networkgroup = "%s" + wan_type = "%s" + wan_ip = "%s" + wan_egress_qos = %d + wan_username = "%s" + x_wan_password = "%s" +} +`, networkGroup, wanType, wanIP, wanEgressQOS, wanUsername, wanPassword) +}