From e60a4faa5fa23ee3edf9ec17862d0e9af03704e3 Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Mon, 19 Oct 2020 11:16:05 -0400 Subject: [PATCH] Add support for controller v6 --- controller.sh | 6 +- docs/data-sources/ap_group.md | 29 ++ docs/data-sources/wlan_group.md | 13 +- docs/resources/port_forward.md | 2 +- docs/resources/wlan.md | 6 +- .../unifi_ap_group/data-source.tf | 2 + .../unifi_wlan_group/data-source.tf | 2 - go.mod | 5 +- go.sum | 36 +-- internal/provider/data_ap_group.go | 52 ++++ internal/provider/data_ap_group_test.go | 31 ++ internal/provider/data_wlan_group.go | 14 +- internal/provider/data_wlan_group_test.go | 5 +- internal/provider/lazy_client.go | 16 + internal/provider/provider.go | 20 ++ internal/provider/provider_test.go | 42 +++ internal/provider/resource_network_test.go | 88 +++--- internal/provider/resource_wlan.go | 114 ++++++-- internal/provider/resource_wlan_test.go | 169 ++++++++--- internal/provider/resource_wlan_v5_test.go | 275 ++++++++++++++++++ 20 files changed, 764 insertions(+), 163 deletions(-) create mode 100644 docs/data-sources/ap_group.md create mode 100644 examples/data-sources/unifi_ap_group/data-source.tf delete mode 100644 examples/data-sources/unifi_wlan_group/data-source.tf create mode 100644 internal/provider/data_ap_group.go create mode 100644 internal/provider/data_ap_group_test.go create mode 100644 internal/provider/resource_wlan_v5_test.go diff --git a/controller.sh b/controller.sh index 8e17e22..af6dcec 100755 --- a/controller.sh +++ b/controller.sh @@ -2,6 +2,8 @@ set -eou pipefail +default_tag="stable-6" + # Local Administrator # Username: tfacctest # Password: tfacctest1234 @@ -26,7 +28,7 @@ case "$1" in -e TZ='America/New_York' \ -v $(pwd)/testdata/unifi:/unifi \ --name unifi \ - jacobalberty/unifi:${2:-stable} + jacobalberty/unifi:${2:-$default_tag} echo "Waiting for login page..." timeout 300 bash -c 'while [[ "$(curl --insecure -s -o /dev/null -w "%{http_code}" '"https://localhost:${DOCKER_HTTPS_PORT}/manage/account/login"')" != "200" ]]; do sleep 5; done' @@ -45,7 +47,7 @@ case "$1" in docker stop unifi ;; "update") - docker pull jacobalberty/unifi:${2:-stable} + docker pull jacobalberty/unifi:${2:-$default_tag} ;; "reset") git checkout - testdata/unifi/ diff --git a/docs/data-sources/ap_group.md b/docs/data-sources/ap_group.md new file mode 100644 index 0000000..ad59de1 --- /dev/null +++ b/docs/data-sources/ap_group.md @@ -0,0 +1,29 @@ +--- +page_title: "unifi_ap_group Data Source - terraform-provider-unifi" +subcategory: "" +description: |- + unifi_ap_group data source can be used to retrieve the ID for an AP group by name. +--- + +# Data Source `unifi_ap_group` + +`unifi_ap_group` data source can be used to retrieve the ID for an AP group by name. + +## Example Usage + +```terraform +data "unifi_ap_group" "default" { +} +``` + +## Schema + +### Optional + +- **name** (String, Optional) The name of the AP group to look up, leave blank to look up the default AP group. + +### Read-only + +- **id** (String, Read-only) The ID of this AP group. + + diff --git a/docs/data-sources/wlan_group.md b/docs/data-sources/wlan_group.md index 64a5034..e73af8e 100644 --- a/docs/data-sources/wlan_group.md +++ b/docs/data-sources/wlan_group.md @@ -3,24 +3,25 @@ page_title: "unifi_wlan_group Data Source - terraform-provider-unifi" subcategory: "" description: |- unifi_wlan_group data source can be used to retrieve the ID for a WLAN group by name. + Please note that WLAN Groups are deprecated in v6 of the controller. --- # Data Source `unifi_wlan_group` `unifi_wlan_group` data source can be used to retrieve the ID for a WLAN group by name. -## Example Usage +Please note that WLAN Groups are deprecated in v6 of the controller. + -```terraform -data "unifi_wlan_group" "default" { -} -``` ## Schema ### Optional -- **id** (String, Optional) The ID of this resource. - **name** (String, Optional) The name of the WLAN group to look up. Defaults to `Default`. +### Read-only + +- **id** (String, Read-only) The ID of this AP group. + diff --git a/docs/resources/port_forward.md b/docs/resources/port_forward.md index 6331664..d6fda0f 100644 --- a/docs/resources/port_forward.md +++ b/docs/resources/port_forward.md @@ -16,7 +16,7 @@ description: |- ### Optional - **dst_port** (String, Optional) The destination port for the forwarding. -- **enabled** (Boolean, Optional, Deprecated) Specifies whether the port forwarding rule is enabled or not. +- **enabled** (Boolean, Optional, Deprecated) Specifies whether the port forwarding rule is enabled or not. This will attribute will be removed in a future release. Instead of disabling a port forwarding rule you can remove it from your configuration. - **fwd_ip** (String, Optional) The IPv4 address to forward traffic to. - **fwd_port** (String, Optional) The port to forward traffic to. - **log** (Boolean, Optional) Specifies whether to log forwarded traffic or not. Defaults to `false`. diff --git a/docs/resources/wlan.md b/docs/resources/wlan.md index 032954f..28f8803 100644 --- a/docs/resources/wlan.md +++ b/docs/resources/wlan.md @@ -35,20 +35,22 @@ resource "unifi_wlan" "wifi" { - **name** (String, Required) The SSID of the network. - **security** (String, Required) The type of WiFi security for this network. Valid values are: `wpapsk`, `wpaeap`, and `open`. - **user_group_id** (String, Required) ID of the user group to use for this network. -- **wlan_group_id** (String, Required) ID of the WLAN group to use for this network. ### Optional +- **ap_group_ids** (Set of String, Optional) IDs of the AP groups to use for this network. - **hide_ssid** (Boolean, Optional) Indicates whether or not to hide the SSID from broadcast. - **is_guest** (Boolean, Optional) Indicates that this is a guest WLAN and should use guest behaviors. - **mac_filter_enabled** (Boolean, Optional) Indicates whether or not the MAC filter is turned of for the network. - **mac_filter_list** (Set of String, Optional) List of MAC addresses to filter (only valid if `mac_filter_enabled` is `true`). - **mac_filter_policy** (String, Optional) MAC address filter policy (only valid if `mac_filter_enabled` is `true`). Defaults to `deny`. - **multicast_enhance** (Boolean, Optional) Indicates whether or not Multicast Enhance is turned of for the network. +- **network_id** (String, Optional) ID of the network for this SSID - **passphrase** (String, Optional) The passphrase for the network, this is only required if `security` is not set to `open`. - **radius_profile_id** (String, Optional) ID of the RADIUS profile to use when security `wpaeap`. You can query this via the `unifi_radius_profile` data source. - **schedule** (Block List) Start and stop schedules for the WLAN (see [below for nested schema](#nestedblock--schedule)) -- **vlan_id** (Number, Optional) VLAN ID for the network. Defaults to `1`. +- **vlan_id** (Number, Optional, Deprecated) VLAN ID for the network. Set network_id instead of vlan_id for controller version >= 6. +- **wlan_group_id** (String, Optional, Deprecated) ID of the WLAN group to use for this network. Set ap_group_ids instead of wlan_group_id for controller version >= 6. ### Read-only diff --git a/examples/data-sources/unifi_ap_group/data-source.tf b/examples/data-sources/unifi_ap_group/data-source.tf new file mode 100644 index 0000000..c0e4d8e --- /dev/null +++ b/examples/data-sources/unifi_ap_group/data-source.tf @@ -0,0 +1,2 @@ +data "unifi_ap_group" "default" { +} \ No newline at end of file diff --git a/examples/data-sources/unifi_wlan_group/data-source.tf b/examples/data-sources/unifi_wlan_group/data-source.tf deleted file mode 100644 index c985a7f..0000000 --- a/examples/data-sources/unifi_wlan_group/data-source.tf +++ /dev/null @@ -1,2 +0,0 @@ -data "unifi_wlan_group" "default" { -} \ No newline at end of file diff --git a/go.mod b/go.mod index d54d011..3c341f1 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/hashicorp/go-hclog v0.14.1 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/terraform-exec v0.11.0 // indirect github.com/hashicorp/terraform-json v0.6.0 // indirect github.com/hashicorp/terraform-plugin-docs v0.2.0 @@ -26,8 +27,8 @@ require ( github.com/mattn/go-colorable v0.1.8 // indirect github.com/mitchellh/cli v1.1.2 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/paultyng/go-unifi v1.9.0 - github.com/posener/complete v1.2.3 // indirect + github.com/paultyng/go-unifi v1.10.0 + github.com/posener/complete v1.2.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/zclconf/go-cty v1.6.1 // indirect golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect diff --git a/go.sum b/go.sum index 1cf59c5..96ce628 100644 --- a/go.sum +++ b/go.sum @@ -336,19 +336,17 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/paultyng/go-unifi v1.9.0 h1:kAjTG1WCx0X80Zs1CnjEtIM6vX9cQbiUYK9QZEqLE1E= -github.com/paultyng/go-unifi v1.9.0/go.mod h1:136w8wenUPfYXRTAYtKqYJ/u32ZFMHE9QVD3NQfEcuI= +github.com/paultyng/go-unifi v1.10.0 h1:B42QK7OxW8laPSMpFp+uLh5eX/tskal3qF+5LSGnJVY= +github.com/paultyng/go-unifi v1.10.0/go.mod h1:0QJAKEi4w9CU1qraI3Co2huU1KsklzEbLc5/19MqONE= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= +github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -362,13 +360,11 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= -github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -385,18 +381,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.4.1 h1:Xzr4m4utRDhHDifag1onwwUSq32HLoLBsp+w6tD0880= github.com/zclconf/go-cty v1.4.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= github.com/zclconf/go-cty v1.6.1 h1:wHtZ+LSSQVwUSb+XIJ5E9hgAQxyWATZsAWT+ESJ9dQ0= github.com/zclconf/go-cty v1.6.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= @@ -407,7 +399,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= @@ -463,7 +454,6 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -471,7 +461,6 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -480,10 +469,8 @@ golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6f golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -519,14 +506,12 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -537,7 +522,6 @@ golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -582,9 +566,7 @@ golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY= golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729181040-64cdafbe085c h1:jLQLIAedRoS9I2Py7l/ZAGGzUxLFsdg42JXEpS/a+ow= golang.org/x/tools v0.0.0-20200729181040-64cdafbe085c/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -599,14 +581,12 @@ golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools/gopls v0.4.4/go.mod h1:zhyGzA+CAtREUwwq/btQxEx2FHnGzDwJvGs5YqdVCbE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -618,7 +598,6 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= @@ -629,9 +608,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -662,7 +639,6 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -682,11 +658,9 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -706,7 +680,6 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -715,7 +688,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/internal/provider/data_ap_group.go b/internal/provider/data_ap_group.go new file mode 100644 index 0000000..681edcb --- /dev/null +++ b/internal/provider/data_ap_group.go @@ -0,0 +1,52 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataAPGroup() *schema.Resource { + return &schema.Resource{ + Description: "`unifi_ap_group` data source can be used to retrieve the ID for an AP group by name.", + + Read: dataAPGroupRead, + + Schema: map[string]*schema.Schema{ + "id": { + Description: "The ID of this AP group.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the AP group to look up, leave blank to look up the default AP group.", + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func dataAPGroupRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*client) + + if v := c.ControllerVersion(); !v.GreaterThanOrEqual(controllerV6) { + return fmt.Errorf("AP groups are not supported on controller version %q", v) + } + + name := d.Get("name").(string) + + groups, err := c.c.ListAPGroup(context.TODO(), c.site) + if err != nil { + return err + } + for _, g := range groups { + if (name == "" && g.HiddenID == "default") || g.Name == name { + d.SetId(g.ID) + return nil + } + } + + return fmt.Errorf("AP group not found with name %s", name) +} diff --git a/internal/provider/data_ap_group_test.go b/internal/provider/data_ap_group_test.go new file mode 100644 index 0000000..bffd85b --- /dev/null +++ b/internal/provider/data_ap_group_test.go @@ -0,0 +1,31 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataAPGroup_default(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + }, + ProviderFactories: providerFactories, + // TODO: CheckDestroy: , + Steps: []resource.TestStep{ + { + Config: testAccDataAPGroupConfig_default, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + }, + }) +} + +const testAccDataAPGroupConfig_default = ` +data "unifi_ap_group" "default" { +} +` diff --git a/internal/provider/data_wlan_group.go b/internal/provider/data_wlan_group.go index 5ff32c4..61c1a5b 100644 --- a/internal/provider/data_wlan_group.go +++ b/internal/provider/data_wlan_group.go @@ -9,11 +9,19 @@ import ( func dataWLANGroup() *schema.Resource { return &schema.Resource{ - Description: "`unifi_wlan_group` data source can be used to retrieve the ID for a WLAN group by name.", + Description: "`unifi_wlan_group` data source can be used to retrieve the ID for a WLAN group by name.\n\n" + + "Please note that WLAN Groups are deprecated in v6 of the controller.", + + DeprecationMessage: "WLAN groups are deprecated in controller version 6 and greater.", Read: dataWLANGroupRead, Schema: map[string]*schema.Schema{ + "id": { + Description: "The ID of this AP group.", + Type: schema.TypeString, + Computed: true, + }, "name": { Description: "The name of the WLAN group to look up.", Type: schema.TypeString, @@ -27,6 +35,10 @@ func dataWLANGroup() *schema.Resource { func dataWLANGroupRead(d *schema.ResourceData, meta interface{}) error { c := meta.(*client) + if v := c.ControllerVersion(); !v.LessThan(controllerV6) { + return fmt.Errorf("WLAN groups are not supported on controller version %q", v) + } + name := d.Get("name").(string) groups, err := c.c.ListWLANGroup(context.TODO(), c.site) diff --git a/internal/provider/data_wlan_group_test.go b/internal/provider/data_wlan_group_test.go index ca95828..a897594 100644 --- a/internal/provider/data_wlan_group_test.go +++ b/internal/provider/data_wlan_group_test.go @@ -8,7 +8,10 @@ import ( func TestAccDataWLANGroup_default(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , Steps: []resource.TestStep{ diff --git a/internal/provider/lazy_client.go b/internal/provider/lazy_client.go index db9a37f..5bdc3dd 100644 --- a/internal/provider/lazy_client.go +++ b/internal/provider/lazy_client.go @@ -3,6 +3,8 @@ package provider import ( "context" "crypto/tls" + "fmt" + "log" "net" "net/http" "net/http/cookiejar" @@ -63,10 +65,18 @@ func (c *lazyClient) init(ctx context.Context) error { } initErr = c.inner.Login(ctx, c.user, c.pass) + + log.Printf("[TRACE] Unifi controller version: %q", c.inner.Version()) }) return initErr } +func (c *lazyClient) Version() string { + if err := c.init(context.Background()); err != nil { + panic(fmt.Sprintf("client not initialized: %s", err)) + } + return c.inner.Version() +} func (c *lazyClient) ListUserGroup(ctx context.Context, site string) ([]unifi.UserGroup, error) { if err := c.init(ctx); err != nil { return nil, err @@ -79,6 +89,12 @@ func (c *lazyClient) ListWLANGroup(ctx context.Context, site string) ([]unifi.WL } return c.inner.ListWLANGroup(ctx, site) } +func (c *lazyClient) ListAPGroup(ctx context.Context, site string) ([]unifi.APGroup, error) { + if err := c.init(ctx); err != nil { + return nil, err + } + return c.inner.ListAPGroup(ctx, site) +} func (c *lazyClient) DeleteNetwork(ctx context.Context, site, id, name string) error { if err := c.init(ctx); err != nil { return err diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d9d69fb..41c182a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,10 +5,16 @@ import ( "fmt" "strings" + "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/paultyng/go-unifi/unifi" ) +var ( + controllerV5 = version.Must(version.NewVersion("5.0.0")) + controllerV6 = version.Must(version.NewVersion("6.0.0")) +) + func init() { schema.DescriptionKind = 1 @@ -17,6 +23,9 @@ func init() { if s.Default != nil { desc += fmt.Sprintf(" Defaults to `%v`.", s.Default) } + if s.Deprecated != "" { + desc += " " + s.Deprecated + } return strings.TrimSpace(desc) } } @@ -65,11 +74,13 @@ func New(version string) func() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ + "unifi_ap_group": dataAPGroup(), "unifi_radius_profile": dataRADIUSProfile(), "unifi_user_group": dataUserGroup(), "unifi_wlan_group": dataWLANGroup(), }, ResourcesMap: map[string]*schema.Resource{ + // TODO: "unifi_ap_group" "unifi_firewall_group": resourceFirewallGroup(), "unifi_firewall_rule": resourceFirewallRule(), "unifi_network": resourceNetwork(), @@ -79,6 +90,7 @@ func New(version string) func() *schema.Provider { "unifi_wlan": resourceWLAN(), }, } + p.ConfigureFunc = configure(version, p) return p } @@ -107,6 +119,8 @@ func configure(version string, p *schema.Provider) schema.ConfigureFunc { } type unifiClient interface { + Version() string + ListUserGroup(ctx context.Context, site string) ([]unifi.UserGroup, error) DeleteUserGroup(ctx context.Context, site, id string) error CreateUserGroup(ctx context.Context, site string, d *unifi.UserGroup) (*unifi.UserGroup, error) @@ -127,6 +141,8 @@ type unifiClient interface { ListWLANGroup(ctx context.Context, site string) ([]unifi.WLANGroup, error) + ListAPGroup(ctx context.Context, site string) ([]unifi.APGroup, error) + DeleteNetwork(ctx context.Context, site, id, name string) error CreateNetwork(ctx context.Context, site string, d *unifi.Network) (*unifi.Network, error) GetNetwork(ctx context.Context, site, id string) (*unifi.Network, error) @@ -161,3 +177,7 @@ type client struct { c unifiClient site string } + +func (c *client) ControllerVersion() *version.Version { + return version.Must(version.NewVersion(c.c.Version())) +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index ed93ab7..6dc2713 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -3,8 +3,10 @@ package provider import ( "context" "os" + "sync" "testing" + "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/paultyng/go-unifi/unifi" @@ -68,3 +70,43 @@ func preCheck(t *testing.T) { } } } + +func preCheckV6Only(t *testing.T) { + v, err := version.NewVersion(testClient.Version()) + if err != nil { + t.Fatalf("error parsing version: %s", err) + } + if v.LessThan(controllerV6) { + t.Skipf("skipping test on controller version %q", v) + } +} + +func preCheckV5Only(t *testing.T) { + v, err := version.NewVersion(testClient.Version()) + if err != nil { + t.Fatalf("error parsing version: %s", err) + } + if v.GreaterThanOrEqual(controllerV6) { + t.Skipf("skipping test on controller version %q", v) + } +} + +const ( + vlanMin = 2 + vlanMax = 4095 +) + +var ( + vlanLock sync.Mutex + vlanNext = vlanMin +) + +func getTestVLAN(t *testing.T) int { + vlanLock.Lock() + defer vlanLock.Unlock() + + vl := vlanNext + vlanNext++ + + return vl +} diff --git a/internal/provider/resource_network_test.go b/internal/provider/resource_network_test.go index ad540c3..87bf983 100644 --- a/internal/provider/resource_network_test.go +++ b/internal/provider/resource_network_test.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "strconv" "strings" "testing" @@ -9,26 +10,26 @@ import ( ) func TestAccNetwork_basic(t *testing.T) { + vlanID1 := getTestVLAN(t) + vlanID2 := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , Steps: []resource.TestStep{ { - Config: testAccNetworkConfig("10.0.202.0/24", 202, true, nil), + Config: testAccNetworkConfig(vlanID1, true, nil), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "domain_name", "foo.local"), - resource.TestCheckResourceAttr("unifi_network.test", "subnet", "10.0.202.0/24"), - resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", "202"), + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID1)), resource.TestCheckResourceAttr("unifi_network.test", "igmp_snooping", "true"), ), }, importStep("unifi_network.test"), { - Config: testAccNetworkConfig("10.0.203.0/24", 203, false, nil), + Config: testAccNetworkConfig(vlanID2, false, nil), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("unifi_network.test", "subnet", "10.0.203.0/24"), - resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", "203"), + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID2)), resource.TestCheckResourceAttr("unifi_network.test", "igmp_snooping", "false"), ), }, @@ -38,15 +39,17 @@ func TestAccNetwork_basic(t *testing.T) { } func TestAccNetwork_weird_cidr(t *testing.T) { + vlanID := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , Steps: []resource.TestStep{ { - Config: testAccNetworkConfig("10.0.204.3/24", 204, true, nil), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("unifi_network.test", "subnet", "10.0.204.0/24"), + Config: testAccNetworkConfig(vlanID, true, nil), + Check: resource.ComposeTestCheckFunc( + // TODO: ... ), }, importStep("unifi_network.test"), @@ -55,20 +58,22 @@ func TestAccNetwork_weird_cidr(t *testing.T) { } func TestAccNetwork_dhcp_dns(t *testing.T) { + vlanID := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , Steps: []resource.TestStep{ { - Config: testAccNetworkConfig("10.0.205.0/24", 205, true, []string{"192.168.1.101"}), + Config: testAccNetworkConfig(vlanID, true, []string{"192.168.1.101"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.0", "192.168.1.101"), ), }, importStep("unifi_network.test"), { - Config: testAccNetworkConfig("10.0.205.0/24", 205, true, []string{"192.168.1.101", "192.168.1.102"}), + Config: testAccNetworkConfig(vlanID, true, []string{"192.168.1.101", "192.168.1.102"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.0", "192.168.1.101"), resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.1", "192.168.1.102"), @@ -76,13 +81,13 @@ func TestAccNetwork_dhcp_dns(t *testing.T) { }, importStep("unifi_network.test"), { - Config: testAccNetworkConfig("10.0.205.0/24", 205, true, nil), + Config: testAccNetworkConfig(vlanID, true, nil), Check: resource.ComposeTestCheckFunc( resource.TestCheckNoResourceAttr("unifi_network.test", "dhcp_dns"), ), }, { - Config: testAccNetworkConfig("10.0.205.0/24", 205, true, []string{"192.168.1.101"}), + Config: testAccNetworkConfig(vlanID, true, []string{"192.168.1.101"}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.0", "192.168.1.101"), ), @@ -92,26 +97,27 @@ func TestAccNetwork_dhcp_dns(t *testing.T) { } func TestAccNetwork_v6(t *testing.T) { + vlanID1 := getTestVLAN(t) + vlanID2 := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , Steps: []resource.TestStep{ { - Config: testAccNetworkConfigV6("10.0.206.0/24", 206, "static", "fd6a:37be:e362::1/64"), + Config: testAccNetworkConfigV6(vlanID1, "static", "fd6a:37be:e362::1/64"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "domain_name", "foo.local"), - resource.TestCheckResourceAttr("unifi_network.test", "subnet", "10.0.206.0/24"), - resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", "206"), + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID1)), resource.TestCheckResourceAttr("unifi_network.test", "ipv6_static_subnet", "fd6a:37be:e362::1/64"), ), }, importStep("unifi_network.test"), { - Config: testAccNetworkConfigV6("10.0.207.0/24", 207, "static", "fd6a:37be:e363::1/64"), + Config: testAccNetworkConfigV6(vlanID2, "static", "fd6a:37be:e363::1/64"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("unifi_network.test", "subnet", "10.0.207.0/24"), - resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", "207"), + resource.TestCheckResourceAttr("unifi_network.test", "vlan_id", strconv.Itoa(vlanID2)), resource.TestCheckResourceAttr("unifi_network.test", "ipv6_static_subnet", "fd6a:37be:e363::1/64"), ), }, @@ -130,49 +136,51 @@ func quoteStrings(src []string) []string { return dst } -func testAccNetworkConfig(subnet string, vlan int, igmpSnoop bool, dhcpDNS []string) string { +func testAccNetworkConfig(vlan int, igmpSnoop bool, dhcpDNS []string) string { return fmt.Sprintf(` -variable "subnet" { - default = "%s" +locals { + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d } resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = var.subnet - vlan_id = %d - dhcp_start = cidrhost(var.subnet, 6) - dhcp_stop = cidrhost(var.subnet, 254) + subnet = local.subnet + vlan_id = local.vlan_id + dhcp_start = cidrhost(local.subnet, 6) + dhcp_stop = cidrhost(local.subnet, 254) dhcp_enabled = true domain_name = "foo.local" - igmp_snooping = %t + igmp_snooping = %[2]t - dhcp_dns = [%s] + dhcp_dns = [%[3]s] } -`, subnet, vlan, igmpSnoop, strings.Join(quoteStrings(dhcpDNS), ",")) +`, vlan, igmpSnoop, strings.Join(quoteStrings(dhcpDNS), ",")) } -func testAccNetworkConfigV6(subnet string, vlan int, ipv6Type string, ipv6Subnet string) string { +func testAccNetworkConfigV6(vlan int, ipv6Type string, ipv6Subnet string) string { return fmt.Sprintf(` -variable "subnet" { - default = "%s" +locals { + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d } - + resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = var.subnet - vlan_id = %d - dhcp_start = cidrhost(var.subnet, 6) - dhcp_stop = cidrhost(var.subnet, 254) + subnet = local.subnet + vlan_id = local.vlan_id + dhcp_start = cidrhost(local.subnet, 6) + dhcp_stop = cidrhost(local.subnet, 254) dhcp_enabled = true domain_name = "foo.local" - ipv6_interface_type = "%s" - ipv6_static_subnet = "%s" + ipv6_interface_type = "%[2]s" + ipv6_static_subnet = "%[3]s" ipv6_ra_enable = true } -`, subnet, vlan, ipv6Type, ipv6Subnet) +`, vlan, ipv6Type, ipv6Subnet) } diff --git a/internal/provider/resource_wlan.go b/internal/provider/resource_wlan.go index 4a55d9d..af661bb 100644 --- a/internal/provider/resource_wlan.go +++ b/internal/provider/resource_wlan.go @@ -34,17 +34,7 @@ func resourceWLAN() *schema.Resource { Type: schema.TypeString, Required: true, }, - "vlan_id": { - Description: "VLAN ID for the network.", - Type: schema.TypeInt, - Optional: true, - Default: 1, - }, - "wlan_group_id": { - Description: "ID of the WLAN group to use for this network.", - Type: schema.TypeString, - Required: true, - }, + "user_group_id": { Description: "ID of the user group to use for this network.", Type: schema.TypeString, @@ -135,12 +125,45 @@ func resourceWLAN() *schema.Resource { }, }, }, + + // controller v6 fields + "network_id": { + Description: "ID of the network for this SSID", + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"vlan_id"}, + }, + "ap_group_ids": { + Description: "IDs of the AP groups to use for this network.", + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"wlan_group_id"}, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + // controller v5 fields + "vlan_id": { + Description: "VLAN ID for the network.", + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"network_id"}, + Deprecated: "Set network_id instead of vlan_id for controller version >= 6.", + }, + "wlan_group_id": { + Description: "ID of the WLAN group to use for this network.", + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"ap_group_ids"}, + Deprecated: "Set ap_group_ids instead of wlan_group_id for controller version >= 6.", + }, }, } } -func resourceWLANGetResourceData(d *schema.ResourceData) (*unifi.WLAN, error) { - vlan := d.Get("vlan_id").(int) +func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*unifi.WLAN, error) { + c := meta.(*client) security := d.Get("security").(string) passphrase := d.Get("passphrase").(string) @@ -158,20 +181,46 @@ func resourceWLANGetResourceData(d *schema.ResourceData) (*unifi.WLAN, error) { macFilterList = nil } + // version specific fields and validation + networkID := d.Get("network_id").(string) + vlan := d.Get("vlan_id").(int) + apGroupIDs, err := setToStringSlice(d.Get("ap_group_ids").(*schema.Set)) + if err != nil { + return nil, err + } + wlanGroupID := d.Get("wlan_group_id").(string) + switch v := c.ControllerVersion(); { + case v.GreaterThanOrEqual(controllerV6): + if wlanGroupID != "" { + return nil, fmt.Errorf("wlan_group_id is not supported on controller version %q", v) + } + if vlan != 0 { + return nil, fmt.Errorf("vlan_id %d is not supported on controller version %q", vlan, v) + } + case v.GreaterThanOrEqual(controllerV5): + if networkID != "" { + return nil, fmt.Errorf("network_id is not supported on controller version %q", v) + } + if len(apGroupIDs) > 0 { + return nil, fmt.Errorf("ap_group_ids is not supported on controller version %q", v) + } + default: + return nil, fmt.Errorf("controller version %q not supported", v) + } + schedule, err := listToScheduleStrings(d.Get("schedule").([]interface{})) if err != nil { return nil, fmt.Errorf("unable to process schedule block: %w", err) } - log.Printf("[TRACE] TF Schedule: %#v", schedule) return &unifi.WLAN{ Name: d.Get("name").(string), - VLAN: vlan, XPassphrase: passphrase, HideSSID: d.Get("hide_ssid").(bool), IsGuest: d.Get("is_guest").(bool), - WLANGroupID: d.Get("wlan_group_id").(string), + NetworkID: networkID, + ApGroupIDs: apGroupIDs, UserGroupID: d.Get("user_group_id").(string), Security: security, MulticastEnhanceEnabled: d.Get("multicast_enhance").(bool), @@ -182,6 +231,9 @@ func resourceWLANGetResourceData(d *schema.ResourceData) (*unifi.WLAN, error) { Schedule: schedule, ScheduleEnabled: len(schedule) > 0, + // v5 + VLAN: vlan, + WLANGroupID: wlanGroupID, VLANEnabled: vlan != 0 && vlan != 1, // TODO: add to schema @@ -200,7 +252,7 @@ func resourceWLANGetResourceData(d *schema.ResourceData) (*unifi.WLAN, error) { func resourceWLANCreate(d *schema.ResourceData, meta interface{}) error { c := meta.(*client) - req, err := resourceWLANGetResourceData(d) + req, err := resourceWLANGetResourceData(d, meta) if err != nil { return err } @@ -212,10 +264,12 @@ func resourceWLANCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(resp.ID) - return resourceWLANSetResourceData(resp, d) + return resourceWLANSetResourceData(resp, d, meta) } -func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData) error { +func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta interface{}) error { + // c := meta.(*client) + vlan := 0 if resp.VLANEnabled { vlan = resp.VLAN @@ -236,20 +290,19 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData) error macFilterPolicy = resp.MACFilterPolicy } - log.Printf("[TRACE] API Schedule: %#v", resp.Schedule) + apGroupIDs := stringSliceToSet(resp.ApGroupIDs) + log.Printf("[TRACE] API Schedule: %#v", resp.Schedule) schedule, err := listFromScheduleStrings(resp.Schedule) if err != nil { return fmt.Errorf("unable to parse schedule: %w", err) } d.Set("name", resp.Name) - d.Set("vlan_id", vlan) + d.Set("user_group_id", resp.UserGroupID) d.Set("passphrase", passphrase) d.Set("hide_ssid", resp.HideSSID) d.Set("is_guest", resp.IsGuest) - d.Set("wlan_group_id", resp.WLANGroupID) - d.Set("user_group_id", resp.UserGroupID) d.Set("security", security) d.Set("multicast_enhance", resp.MulticastEnhanceEnabled) d.Set("mac_filter_enabled", macFilterEnabled) @@ -258,6 +311,15 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData) error d.Set("radius_profile_id", resp.RADIUSProfileID) d.Set("schedule", schedule) + // switch v := c.ControllerVersion(); { + // case v.GreaterThanOrEqual(controllerV6): + d.Set("ap_group_ids", apGroupIDs) + d.Set("network_id", resp.NetworkID) + // case v.GreaterThanOrEqual(controllerV5): + d.Set("vlan_id", vlan) + d.Set("wlan_group_id", resp.WLANGroupID) + // } + return nil } @@ -275,13 +337,13 @@ func resourceWLANRead(d *schema.ResourceData, meta interface{}) error { return err } - return resourceWLANSetResourceData(resp, d) + return resourceWLANSetResourceData(resp, d, meta) } func resourceWLANUpdate(d *schema.ResourceData, meta interface{}) error { c := meta.(*client) - req, err := resourceWLANGetResourceData(d) + req, err := resourceWLANGetResourceData(d, meta) if err != nil { return err } @@ -294,7 +356,7 @@ func resourceWLANUpdate(d *schema.ResourceData, meta interface{}) error { return err } - return resourceWLANSetResourceData(resp, d) + return resourceWLANSetResourceData(resp, d, meta) } func resourceWLANDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/internal/provider/resource_wlan_test.go b/internal/provider/resource_wlan_test.go index ab3a897..4c7f180 100644 --- a/internal/provider/resource_wlan_test.go +++ b/internal/provider/resource_wlan_test.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "os" "strconv" "testing" @@ -28,21 +29,22 @@ func init() { wlanConcurrency = make(chan struct{}, wc) } -func wlanPreCheck(t *testing.T) func() { - return func() { - if cap(wlanConcurrency) == 0 { - t.Skip("concurrency for WLAN testing set to 0") - } - - preCheck(t) - - wlanConcurrency <- struct{}{} +func wlanPreCheck(t *testing.T) { + if cap(wlanConcurrency) == 0 { + t.Skip("concurrency for WLAN testing set to 0") } + + wlanConcurrency <- struct{}{} } func TestAccWLAN_wpapsk(t *testing.T) { + vlanID := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ - PreCheck: wlanPreCheck(t), + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + wlanPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { // TODO: actual CheckDestroy @@ -52,7 +54,7 @@ func TestAccWLAN_wpapsk(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccWLANConfig_wpapsk, + Config: testAccWLANConfig_wpapsk(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), @@ -63,8 +65,13 @@ func TestAccWLAN_wpapsk(t *testing.T) { } func TestAccWLAN_open(t *testing.T) { + vlanID := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ - PreCheck: wlanPreCheck(t), + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + wlanPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { // TODO: actual CheckDestroy @@ -74,21 +81,21 @@ func TestAccWLAN_open(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccWLANConfig_open, + Config: testAccWLANConfig_open(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, importStep("unifi_wlan.test"), { - Config: testAccWLANConfig_open_mac_filter, + Config: testAccWLANConfig_open_mac_filter(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, importStep("unifi_wlan.test"), { - Config: testAccWLANConfig_open, + Config: testAccWLANConfig_open(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), @@ -99,8 +106,13 @@ func TestAccWLAN_open(t *testing.T) { } func TestAccWLAN_change_security(t *testing.T) { + vlanID := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ - PreCheck: wlanPreCheck(t), + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + wlanPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { // TODO: actual CheckDestroy @@ -110,21 +122,21 @@ func TestAccWLAN_change_security(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccWLANConfig_wpapsk, + Config: testAccWLANConfig_wpapsk(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, importStep("unifi_wlan.test"), { - Config: testAccWLANConfig_open, + Config: testAccWLANConfig_open(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, importStep("unifi_wlan.test"), { - Config: testAccWLANConfig_wpapsk, + Config: testAccWLANConfig_wpapsk(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), @@ -134,8 +146,13 @@ func TestAccWLAN_change_security(t *testing.T) { } func TestAccWLAN_schedule(t *testing.T) { + vlanID := getTestVLAN(t) resource.ParallelTest(t, resource.TestCase{ - PreCheck: wlanPreCheck(t), + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + wlanPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { // TODO: actual CheckDestroy @@ -145,7 +162,7 @@ func TestAccWLAN_schedule(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccWLANConfig_schedule, + Config: testAccWLANConfig_schedule(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), @@ -160,8 +177,14 @@ func TestAccWLAN_wpaeap(t *testing.T) { t.Skip("UNIFI_TEST_RADIUS not set, skipping RADIUS test") } + vlanID := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ - PreCheck: wlanPreCheck(t), + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + wlanPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { // TODO: actual CheckDestroy @@ -171,7 +194,7 @@ func TestAccWLAN_wpaeap(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccWLANConfig_wpaeap, + Config: testAccWLANConfig_wpaeap(vlanID), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), @@ -181,27 +204,38 @@ func TestAccWLAN_wpaeap(t *testing.T) { }) } -const testAccWLANConfig_wpapsk = ` -data "unifi_wlan_group" "default" { +func testAccWLANConfig_wpapsk(vlanID int) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { } data "unifi_user_group" "default" { } +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d +} + resource "unifi_wlan" "test" { name = "tfacc-wpapsk" - vlan_id = 202 + network_id = unifi_network.test.id passphrase = "12345678" - wlan_group_id = data.unifi_wlan_group.default.id + ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id security = "wpapsk" multicast_enhance = true } -` +`, vlanID) +} -const testAccWLANConfig_wpaeap = ` -data "unifi_wlan_group" "default" { +func testAccWLANConfig_wpaeap(vlanID int) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { } data "unifi_user_group" "default" { @@ -210,45 +244,73 @@ data "unifi_user_group" "default" { data "unifi_radius_profile" "default" { } +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d +} + resource "unifi_wlan" "test" { name = "tfacc-wpapsk" - vlan_id = 202 + network_id = unifi_network.test.id passphrase = "12345678" - wlan_group_id = data.unifi_wlan_group.default.id + ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id security = "wpaeap" radius_profile_id = data.unifi_radius_profile.default.id } -` +`, vlanID) +} -const testAccWLANConfig_open = ` -data "unifi_wlan_group" "default" { +func testAccWLANConfig_open(vlanID int) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { } data "unifi_user_group" "default" { } +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d +} + resource "unifi_wlan" "test" { name = "tfacc-open" - vlan_id = 202 - wlan_group_id = data.unifi_wlan_group.default.id + network_id = unifi_network.test.id + ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id security = "open" } -` +`, vlanID) +} -const testAccWLANConfig_schedule = ` -data "unifi_wlan_group" "default" { +func testAccWLANConfig_schedule(vlanID int) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { } data "unifi_user_group" "default" { } +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d +} + resource "unifi_wlan" "test" { name = "tfacc-open-schedule" - vlan_id = 202 - wlan_group_id = data.unifi_wlan_group.default.id + network_id = unifi_network.test.id + ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id security = "open" @@ -264,19 +326,29 @@ resource "unifi_wlan" "test" { block_end = "17:00" } } -` +`, vlanID) +} -const testAccWLANConfig_open_mac_filter = ` -data "unifi_wlan_group" "default" { +func testAccWLANConfig_open_mac_filter(vlanID int) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { } data "unifi_user_group" "default" { } +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + vlan_id = %[1]d +} + resource "unifi_wlan" "test" { name = "tfacc-open" - vlan_id = 202 - wlan_group_id = data.unifi_wlan_group.default.id + network_id = unifi_network.test.id + ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id security = "open" @@ -284,4 +356,5 @@ resource "unifi_wlan" "test" { mac_filter_list = ["ab:cd:ef:12:34:56"] mac_filter_policy = "allow" } -` +`, vlanID) +} diff --git a/internal/provider/resource_wlan_v5_test.go b/internal/provider/resource_wlan_v5_test.go new file mode 100644 index 0000000..8aa518e --- /dev/null +++ b/internal/provider/resource_wlan_v5_test.go @@ -0,0 +1,275 @@ +package provider + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccWLAN_v5_wpapsk(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_v5_wpapsk, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + }, + }) +} + +func TestAccWLAN_v5_open(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_v5_open, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_v5_open_mac_filter, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_v5_open, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + }, + }) +} + +func TestAccWLAN_v5_change_security(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_v5_wpapsk, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_v5_open, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_v5_wpapsk, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + }, + }) +} + +func TestAccWLAN_v5_schedule(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_v5_schedule, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + }, + }) +} + +func TestAccWLAN_v5_wpaeap(t *testing.T) { + if os.Getenv("UNIFI_TEST_RADIUS") == "" { + t.Skip("UNIFI_TEST_RADIUS not set, skipping RADIUS test") + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV5Only(t) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_v5_wpaeap, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + }, + }) +} + +const testAccWLANConfig_v5_wpapsk = ` +data "unifi_wlan_group" "default" { +} + +data "unifi_user_group" "default" { +} + +resource "unifi_wlan" "test" { + name = "tfacc-wpapsk" + vlan_id = 202 + passphrase = "12345678" + wlan_group_id = data.unifi_wlan_group.default.id + user_group_id = data.unifi_user_group.default.id + security = "wpapsk" + + multicast_enhance = true +} +` + +const testAccWLANConfig_v5_wpaeap = ` +data "unifi_wlan_group" "default" { +} + +data "unifi_user_group" "default" { +} + +data "unifi_radius_profile" "default" { +} + +resource "unifi_wlan" "test" { + name = "tfacc-wpapsk" + vlan_id = 202 + passphrase = "12345678" + wlan_group_id = data.unifi_wlan_group.default.id + user_group_id = data.unifi_user_group.default.id + security = "wpaeap" + + radius_profile_id = data.unifi_radius_profile.default.id +} +` + +const testAccWLANConfig_v5_open = ` +data "unifi_wlan_group" "default" { +} + +data "unifi_user_group" "default" { +} + +resource "unifi_wlan" "test" { + name = "tfacc-open" + vlan_id = 202 + wlan_group_id = data.unifi_wlan_group.default.id + user_group_id = data.unifi_user_group.default.id + security = "open" +} +` + +const testAccWLANConfig_v5_schedule = ` +data "unifi_wlan_group" "default" { +} + +data "unifi_user_group" "default" { +} + +resource "unifi_wlan" "test" { + name = "tfacc-open-schedule" + vlan_id = 202 + wlan_group_id = data.unifi_wlan_group.default.id + user_group_id = data.unifi_user_group.default.id + security = "open" + + schedule { + day_of_week = "mon" + block_start = "03:00" + block_end = "9:00" + } + + schedule { + day_of_week = "wed" + block_start = "13:00" + block_end = "17:00" + } +} +` + +const testAccWLANConfig_v5_open_mac_filter = ` +data "unifi_wlan_group" "default" { +} + +data "unifi_user_group" "default" { +} + +resource "unifi_wlan" "test" { + name = "tfacc-open" + vlan_id = 202 + wlan_group_id = data.unifi_wlan_group.default.id + user_group_id = data.unifi_user_group.default.id + security = "open" + + mac_filter_enabled = true + mac_filter_list = ["ab:cd:ef:12:34:56"] + mac_filter_policy = "allow" +} +`