From 325d7b7f209a6238b6195f5a2d9c10b37b83c7d7 Mon Sep 17 00:00:00 2001 From: Mateusz Filipowicz Date: Mon, 24 Feb 2025 00:11:41 +0100 Subject: [PATCH] feat: initialize Terraform Plugin Framework (#23) * feat: initialize Terraform Plugin Framework * fix docker-compose path for tests * fix: ensure documentation can be generated with old provider SDK and new plugin framework * lint --- docs/data-sources/account.md | 4 +- docs/data-sources/ap_group.md | 2 - docs/data-sources/network.md | 2 - docs/data-sources/port_profile.md | 2 - docs/data-sources/radius_profile.md | 2 - docs/data-sources/user.md | 2 - docs/data-sources/user_group.md | 2 - docs/index.md | 21 +- docs/resources/account.md | 2 - docs/resources/device.md | 3 +- docs/resources/dynamic_dns.md | 2 - docs/resources/firewall_group.md | 2 - docs/resources/network.md | 3 +- docs/resources/port_forward.md | 2 - docs/resources/port_profile.md | 7 +- docs/resources/radius_profile.md | 2 - docs/resources/setting_mgmt.md | 2 - docs/resources/setting_radius.md | 2 - docs/resources/setting_usg.md | 5 - docs/resources/static_route.md | 2 - docs/resources/user.md | 2 - docs/resources/wlan.md | 2 +- go.mod | 3 + go.sum | 6 + internal/provider/cidr.go | 56 ----- internal/provider/client.go | 78 ++++++ internal/provider/controller_versions.go | 30 +-- internal/provider/provider.go | 223 +----------------- .../{ => v1}/controller_versions_test.go | 2 +- internal/provider/{ => v1}/data_account.go | 11 +- .../provider/{ => v1}/data_account_test.go | 2 +- internal/provider/{ => v1}/data_ap_group.go | 9 +- .../provider/{ => v1}/data_ap_group_test.go | 2 +- internal/provider/{ => v1}/data_network.go | 12 +- .../provider/{ => v1}/data_network_test.go | 7 +- .../provider/{ => v1}/data_port_profile.go | 9 +- .../{ => v1}/data_port_profile_test.go | 2 +- .../provider/{ => v1}/data_radius_profile.go | 9 +- internal/provider/{ => v1}/data_user.go | 11 +- internal/provider/{ => v1}/data_user_group.go | 9 +- .../provider/{ => v1}/data_user_group_test.go | 2 +- internal/provider/{ => v1}/data_user_test.go | 2 +- internal/provider/v1/diff_suppressions.go | 20 ++ internal/provider/{ => v1}/importer.go | 2 +- internal/provider/{ => v1}/mac.go | 2 +- internal/provider/{ => v1}/port_range.go | 2 +- internal/provider/v1/provider.go | 165 +++++++++++++ internal/provider/{ => v1}/provider_test.go | 4 +- .../provider/{ => v1}/resource_account.go | 27 ++- .../{ => v1}/resource_account_test.go | 2 +- internal/provider/{ => v1}/resource_device.go | 41 ++-- .../provider/{ => v1}/resource_device_test.go | 2 +- .../provider/{ => v1}/resource_dynamic_dns.go | 27 ++- .../{ => v1}/resource_dynamic_dns_test.go | 2 +- .../{ => v1}/resource_firewall_group.go | 34 +-- .../{ => v1}/resource_firewall_group_test.go | 2 +- .../{ => v1}/resource_firewall_rule.go | 38 +-- .../{ => v1}/resource_firewall_rule_test.go | 2 +- .../provider/{ => v1}/resource_network.go | 48 ++-- .../{ => v1}/resource_network_test.go | 5 +- .../{ => v1}/resource_port_forward.go | 30 +-- .../{ => v1}/resource_port_forward_test.go | 2 +- .../{ => v1}/resource_port_profile.go | 36 +-- .../{ => v1}/resource_port_profile_test.go | 2 +- .../{ => v1}/resource_radius_profile.go | 33 +-- .../{ => v1}/resource_radius_profile_test.go | 2 +- .../{ => v1}/resource_setting_mgmt.go | 21 +- .../{ => v1}/resource_setting_mgmt_test.go | 2 +- .../{ => v1}/resource_setting_radius.go | 21 +- .../{ => v1}/resource_setting_radius_test.go | 2 +- .../provider/{ => v1}/resource_setting_usg.go | 24 +- .../{ => v1}/resource_setting_usg_test.go | 2 +- internal/provider/{ => v1}/resource_site.go | 25 +- .../provider/{ => v1}/resource_site_test.go | 2 +- .../{ => v1}/resource_static_route.go | 34 +-- .../{ => v1}/resource_static_route_test.go | 2 +- internal/provider/{ => v1}/resource_user.go | 47 ++-- .../provider/{ => v1}/resource_user_group.go | 27 ++- .../{ => v1}/resource_user_group_test.go | 2 +- .../provider/{ => v1}/resource_user_test.go | 2 +- internal/provider/{ => v1}/resource_wlan.go | 46 ++-- .../provider/{ => v1}/resource_wlan_test.go | 5 +- internal/provider/v2/provider.go | 172 ++++++++++++++ internal/utils/cidr.go | 40 ++++ internal/{provider => utils}/cidr_test.go | 4 +- internal/utils/env.go | 50 ++++ internal/{provider => utils}/markdown.go | 4 +- internal/{provider => utils}/strings.go | 14 +- main.go | 63 ++++- templates/index.md.tmpl | 6 +- tools/tools.go | 3 + 91 files changed, 1005 insertions(+), 705 deletions(-) delete mode 100644 internal/provider/cidr.go create mode 100644 internal/provider/client.go rename internal/provider/{ => v1}/controller_versions_test.go (97%) rename internal/provider/{ => v1}/data_account.go (89%) rename internal/provider/{ => v1}/data_account_test.go (98%) rename internal/provider/{ => v1}/data_ap_group.go (88%) rename internal/provider/{ => v1}/data_ap_group_test.go (97%) rename internal/provider/{ => v1}/data_network.go (97%) rename internal/provider/{ => v1}/data_network_test.go (91%) rename internal/provider/{ => v1}/data_port_profile.go (88%) rename internal/provider/{ => v1}/data_port_profile_test.go (98%) rename internal/provider/{ => v1}/data_radius_profile.go (88%) rename internal/provider/{ => v1}/data_user.go (93%) rename internal/provider/{ => v1}/data_user_group.go (90%) rename internal/provider/{ => v1}/data_user_group_test.go (98%) rename internal/provider/{ => v1}/data_user_test.go (98%) create mode 100644 internal/provider/v1/diff_suppressions.go rename internal/provider/{ => v1}/importer.go (96%) rename internal/provider/{ => v1}/mac.go (98%) rename internal/provider/{ => v1}/port_range.go (98%) create mode 100644 internal/provider/v1/provider.go rename internal/provider/{ => v1}/provider_test.go (97%) rename internal/provider/{ => v1}/resource_account.go (91%) rename internal/provider/{ => v1}/resource_account_test.go (98%) rename internal/provider/{ => v1}/resource_device.go (94%) rename internal/provider/{ => v1}/resource_device_test.go (99%) rename internal/provider/{ => v1}/resource_dynamic_dns.go (90%) rename internal/provider/{ => v1}/resource_dynamic_dns_test.go (97%) rename internal/provider/{ => v1}/resource_firewall_group.go (83%) rename internal/provider/{ => v1}/resource_firewall_group_test.go (99%) rename internal/provider/{ => v1}/resource_firewall_rule.go (92%) rename internal/provider/{ => v1}/resource_firewall_rule_test.go (99%) rename internal/provider/{ => v1}/resource_network.go (95%) rename internal/provider/{ => v1}/resource_network_test.go (99%) rename internal/provider/{ => v1}/resource_port_forward.go (91%) rename internal/provider/{ => v1}/resource_port_forward_test.go (99%) rename internal/provider/{ => v1}/resource_port_profile.go (94%) rename internal/provider/{ => v1}/resource_port_profile_test.go (98%) rename internal/provider/{ => v1}/resource_radius_profile.go (95%) rename internal/provider/{ => v1}/resource_radius_profile_test.go (99%) rename internal/provider/{ => v1}/resource_setting_mgmt.go (93%) rename internal/provider/{ => v1}/resource_setting_mgmt_test.go (99%) rename internal/provider/{ => v1}/resource_setting_radius.go (92%) rename internal/provider/{ => v1}/resource_setting_radius_test.go (99%) rename internal/provider/{ => v1}/resource_setting_usg.go (89%) rename internal/provider/{ => v1}/resource_setting_usg_test.go (99%) rename internal/provider/{ => v1}/resource_site.go (85%) rename internal/provider/{ => v1}/resource_site_test.go (99%) rename internal/provider/{ => v1}/resource_static_route.go (87%) rename internal/provider/{ => v1}/resource_static_route_test.go (99%) rename internal/provider/{ => v1}/resource_user.go (88%) rename internal/provider/{ => v1}/resource_user_group.go (87%) rename internal/provider/{ => v1}/resource_user_group_test.go (98%) rename internal/provider/{ => v1}/resource_user_test.go (99%) rename internal/provider/{ => v1}/resource_wlan.go (93%) rename internal/provider/{ => v1}/resource_wlan_test.go (99%) create mode 100644 internal/provider/v2/provider.go create mode 100644 internal/utils/cidr.go rename internal/{provider => utils}/cidr_test.go (93%) create mode 100644 internal/utils/env.go rename internal/{provider => utils}/markdown.go (82%) rename internal/{provider => utils}/strings.go (54%) diff --git a/docs/data-sources/account.md b/docs/data-sources/account.md index 9f3b9aa..7778e85 100644 --- a/docs/data-sources/account.md +++ b/docs/data-sources/account.md @@ -8,7 +8,7 @@ description: |- # unifi_account (Data Source) -`unifi_account` data source can be used to retrieve RADIUS user accounts +unifi_account data source can be used to retrieve RADIUS user accounts @@ -30,5 +30,3 @@ description: |- - `password` (String, Sensitive) The password of the account. - `tunnel_medium_type` (Number) See RFC2868 section 3.2 - `tunnel_type` (Number) See RFC2868 section 3.1 - - diff --git a/docs/data-sources/ap_group.md b/docs/data-sources/ap_group.md index ceaea54..0fdf5c8 100644 --- a/docs/data-sources/ap_group.md +++ b/docs/data-sources/ap_group.md @@ -28,5 +28,3 @@ data "unifi_ap_group" "default" { ### Read-Only - `id` (String) The ID of this AP group. - - diff --git a/docs/data-sources/network.md b/docs/data-sources/network.md index d2d8b5c..9dfa89d 100644 --- a/docs/data-sources/network.md +++ b/docs/data-sources/network.md @@ -83,5 +83,3 @@ data "unifi_network" "my_network" { - `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/data-sources/port_profile.md b/docs/data-sources/port_profile.md index a5dc515..7786404 100644 --- a/docs/data-sources/port_profile.md +++ b/docs/data-sources/port_profile.md @@ -28,5 +28,3 @@ data "unifi_port_profile" "all" { ### Read-Only - `id` (String) The ID of this port profile. - - diff --git a/docs/data-sources/radius_profile.md b/docs/data-sources/radius_profile.md index b0f6103..e079a60 100644 --- a/docs/data-sources/radius_profile.md +++ b/docs/data-sources/radius_profile.md @@ -23,5 +23,3 @@ description: |- ### Read-Only - `id` (String) The ID of this AP group. - - diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md index e728574..9fe4ff2 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -42,5 +42,3 @@ data "unifi_user" "client" { - `network_id` (String) The network ID for this user. - `note` (String) A note with additional information for the user. - `user_group_id` (String) The user group ID for the user. - - diff --git a/docs/data-sources/user_group.md b/docs/data-sources/user_group.md index 85bda38..ca8741c 100644 --- a/docs/data-sources/user_group.md +++ b/docs/data-sources/user_group.md @@ -25,5 +25,3 @@ description: |- - `id` (String) The ID of this AP group. - `qos_rate_max_down` (Number) - `qos_rate_max_up` (Number) - - diff --git a/docs/index.md b/docs/index.md index f91111b..a54f134 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,16 +13,14 @@ It is not recommended to use your own account for management of your controller. Terraform is recommended. You can create a **Limited Admin** with **Local Access Only** and provide that information for authentication. Two-factor authentication is not supported in the provider. -It is recommended to use API Key authentication, if you are on controller version 9.0.108 or higher. - ## Example Usage ```terraform provider "unifi" { - api_key = var.api_key # optionally use UNIFI_API_KEY env var username = var.username # optionally use UNIFI_USERNAME env var password = var.password # optionally use UNIFI_PASSWORD env var - api_url = var.api_url # optionally use UNIFI_API env var + api_key = var.api_key # optionally use UNIFI_API_KEY env var + api_url = var.api_url # optionally use UNIFI_API env var # you may need to allow insecure TLS communications unless you have configured # certificates for your controller @@ -33,25 +31,14 @@ provider "unifi" { } ``` -### Obtaining an API Key - -1. Open your Site in UniFi Site Manager -2. Click on Control Plane -> Admins & Users. -3. Select your Admin user. -4. Click Create API Key. -5. Add a name for your API Key. -6. Copy the key and store it securely, as it will only be displayed once. -7. Click Done to ensure the key is hashed and securely stored. -8. Use the API Key 🎉 - ## Schema ### Optional - `allow_insecure` (Boolean) Skip verification of TLS certificates of API requests. You may need to set this to `true` if you are using your local API without setting up a signed certificate. Can be specified with the `UNIFI_INSECURE` environment variable. -- `api_key` (String) API key for the user accessing the API. Can be specified with the `UNIFI_API_KEY` environment variable. Requires controller version 9.0.108 or higher and `username` and `password` to be empty +- `api_key` (String, Sensitive) API Key for the user accessing the API. Can be specified with the `UNIFI_API_KEY` environment variable. Controller version 9.0.108 or later is required. - `api_url` (String) URL of the controller API. Can be specified with the `UNIFI_API` environment variable. You should **NOT** supply the path (`/api`), the SDK will discover the appropriate paths. This is to support UDM Pro style API paths as well as more standard controller paths. -- `password` (String) Password for the user accessing the API. Can be specified with the `UNIFI_PASSWORD` environment variable. +- `password` (String, Sensitive) Password for the user accessing the API. Can be specified with the `UNIFI_PASSWORD` environment variable. - `site` (String) The site in the Unifi controller this provider will manage. Can be specified with the `UNIFI_SITE` environment variable. Default: `default` - `username` (String) Local user name for the Unifi controller API. Can be specified with the `UNIFI_USERNAME` environment variable. diff --git a/docs/resources/account.md b/docs/resources/account.md index 503cebd..44b3f98 100644 --- a/docs/resources/account.md +++ b/docs/resources/account.md @@ -41,5 +41,3 @@ NOTE: MAC-based authentication accounts can only be used for wireless and wired ### Read-Only - `id` (String) The ID of the account. - - diff --git a/docs/resources/device.md b/docs/resources/device.md index 782694d..049090c 100644 --- a/docs/resources/device.md +++ b/docs/resources/device.md @@ -90,6 +90,5 @@ Optional: - `aggregate_num_ports` (Number) Number of ports in the aggregate. - `name` (String) Human-readable name of the port. - `op_mode` (String) Operating mode of the port, valid values are `switch`, `mirror`, and `aggregate`. Defaults to `switch`. +- `poe_mode` (String) PoE mode of the port; valid values are `auto`, `pasv24`, `passthrough`, and `off`. - `port_profile_id` (String) ID of the Port Profile used on this port. - - diff --git a/docs/resources/dynamic_dns.md b/docs/resources/dynamic_dns.md index 4ba779e..8e7119d 100644 --- a/docs/resources/dynamic_dns.md +++ b/docs/resources/dynamic_dns.md @@ -43,5 +43,3 @@ resource "unifi_dynamic_dns" "test" { ### Read-Only - `id` (String) The ID of the dynamic DNS. - - diff --git a/docs/resources/firewall_group.md b/docs/resources/firewall_group.md index c6324b7..810c7b9 100644 --- a/docs/resources/firewall_group.md +++ b/docs/resources/firewall_group.md @@ -41,5 +41,3 @@ resource "unifi_firewall_group" "can_print" { ### Read-Only - `id` (String) The ID of the firewall group. - - diff --git a/docs/resources/network.md b/docs/resources/network.md index 7c3a76a..b3d6a75 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -67,9 +67,9 @@ resource "unifi_network" "wan" { - `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. - `domain_name` (String) The domain name of this network. +- `enabled` (Boolean) Specifies whether this network is enabled or not. Defaults to `true`. - `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. 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. @@ -82,6 +82,7 @@ resource "unifi_network" "wan" { - `ipv6_static_subnet` (String) Specifies the static IPv6 subnet when `ipv6_interface_type` is 'static'. - `multicast_dns` (Boolean) Specifies whether Multicast DNS (mDNS) is enabled or not on the network (Controller >=v7). - `network_group` (String) The group of the network. Defaults to `LAN`. +- `network_isolation_enabled` (Boolean) Specifies whether this network should be isolated from other networks or not. Defaults to `false`. - `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. diff --git a/docs/resources/port_forward.md b/docs/resources/port_forward.md index 2ea39ad..d996628 100644 --- a/docs/resources/port_forward.md +++ b/docs/resources/port_forward.md @@ -31,5 +31,3 @@ description: |- ### Read-Only - `id` (String) The ID of the port forwarding rule. - - diff --git a/docs/resources/port_profile.md b/docs/resources/port_profile.md index cf868ce..4411ba2 100644 --- a/docs/resources/port_profile.md +++ b/docs/resources/port_profile.md @@ -46,6 +46,7 @@ resource "unifi_port_profile" "poe_disabled" { - `dot1x_idle_timeout` (Number) The timeout, in seconds, to use when using the MAC Based 802.1X control. Can be between 0 and 65535 Defaults to `300`. - `egress_rate_limit_kbps` (Number) The egress rate limit, in kpbs, for the port profile. Can be between `64` and `9999999`. - `egress_rate_limit_kbps_enabled` (Boolean) Enable egress rate limiting for the port profile. Defaults to `false`. +- `excluded_network_ids` (Set of String) List of network IDs to exclude on the port profile when forward is set to customize. - `forward` (String) The type forwarding to use for the port profile. Can be `all`, `native`, `customize` or `disabled`. Defaults to `native`. - `full_duplex` (Boolean) Enable full duplex for the port profile. Defaults to `false`. - `isolation` (Boolean) Enable port isolation for the port profile. Defaults to `false`. @@ -62,7 +63,7 @@ resource "unifi_port_profile" "poe_disabled" { - `priority_queue3_level` (Number) The priority queue 3 level for the port profile. Can be between 0 and 100. - `priority_queue4_level` (Number) The priority queue 4 level for the port profile. Can be between 0 and 100. - `site` (String) The name of the site to associate the port profile with. -- `speed` (Number) The link speed to set for the port profile. Can be one of `10`, `100`, `1000`, `2500`, `5000`, `10000`, `20000`, `25000`, `40000`, `50000` or `100000` +- `speed` (Number) The link speed to set for the port profile in Mbps. Can be one of `10`, `100`, `1000`, `2500`, `5000`, `10000`, `20000`, `25000`, `40000`, `50000` or `100000`. When `autoneg` is true, this setting is ignored. - `stormctrl_bcast_enabled` (Boolean) Enable broadcast Storm Control for the port profile. Defaults to `false`. - `stormctrl_bcast_level` (Number) The broadcast Storm Control level for the port profile. Can be between 0 and 100. - `stormctrl_bcast_rate` (Number) The broadcast Storm Control rate for the port profile. Can be between 0 and 14880000. @@ -74,11 +75,9 @@ resource "unifi_port_profile" "poe_disabled" { - `stormctrl_ucast_level` (Number) The unknown unicast Storm Control level for the port profile. Can be between 0 and 100. - `stormctrl_ucast_rate` (Number) The unknown unicast Storm Control rate for the port profile. Can be between 0 and 14880000. - `stp_port_mode` (Boolean) Enable spanning tree protocol on the port profile. Defaults to `true`. -- `tagged_networkconf_ids` (Set of String) The IDs of networks to tag traffic with for the port profile. +- `tagged_vlan_mgmt` (String) The VLAN management type for the port profile. Can be one of 'auto', 'block_all', or 'custom'. - `voice_networkconf_id` (String) The ID of network to use as the voice network on the port profile. ### Read-Only - `id` (String) The ID of the port profile. - - diff --git a/docs/resources/radius_profile.md b/docs/resources/radius_profile.md index 3e1b6bd..7cf4b27 100644 --- a/docs/resources/radius_profile.md +++ b/docs/resources/radius_profile.md @@ -60,5 +60,3 @@ Required: Optional: - `port` (Number) Port of authentication service. Defaults to `1812`. - - diff --git a/docs/resources/setting_mgmt.md b/docs/resources/setting_mgmt.md index 5f2b79d..7b56561 100644 --- a/docs/resources/setting_mgmt.md +++ b/docs/resources/setting_mgmt.md @@ -49,5 +49,3 @@ Optional: - `comment` (String) Comment. - `key` (String) Public SSH key. - - diff --git a/docs/resources/setting_radius.md b/docs/resources/setting_radius.md index 73824c7..96f9f08 100644 --- a/docs/resources/setting_radius.md +++ b/docs/resources/setting_radius.md @@ -29,5 +29,3 @@ description: |- ### Read-Only - `id` (String) The ID of the settings. - - diff --git a/docs/resources/setting_usg.md b/docs/resources/setting_usg.md index 98daf2f..f37976b 100644 --- a/docs/resources/setting_usg.md +++ b/docs/resources/setting_usg.md @@ -18,14 +18,9 @@ description: |- ### Optional - `dhcp_relay_servers` (List of String) The DHCP relay servers. -- `firewall_guest_default_log` (Boolean) Whether the guest firewall log is enabled. -- `firewall_lan_default_log` (Boolean) Whether the LAN firewall log is enabled. -- `firewall_wan_default_log` (Boolean) Whether the WAN firewall log is enabled. - `multicast_dns_enabled` (Boolean) Whether multicast DNS is enabled. - `site` (String) The name of the site to associate the settings with. ### Read-Only - `id` (String) The ID of the settings. - - diff --git a/docs/resources/static_route.md b/docs/resources/static_route.md index c6b20fa..9ac4767 100644 --- a/docs/resources/static_route.md +++ b/docs/resources/static_route.md @@ -56,5 +56,3 @@ resource "unifi_static_route" "interface" { ### Read-Only - `id` (String) The ID of the static route. - - diff --git a/docs/resources/user.md b/docs/resources/user.md index ae38479..29d5464 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -52,5 +52,3 @@ resource "unifi_user" "test" { - `hostname` (String) The hostname of the user. - `id` (String) The ID of the user. - `ip` (String) The IP address of the user. - - diff --git a/docs/resources/wlan.md b/docs/resources/wlan.md index bb46978..fa2098c 100644 --- a/docs/resources/wlan.md +++ b/docs/resources/wlan.md @@ -55,7 +55,7 @@ resource "unifi_wlan" "wifi" { ### Required -- `name` (String) The SSID of the network. +- `name` (String) The SSID of the network. SSID length must be between 1 and 32 characters. - `security` (String) The type of WiFi security for this network. Valid values are: `wpapsk`, `wpaeap`, and `open`. - `user_group_id` (String) ID of the user group to use for this network. diff --git a/go.mod b/go.mod index 115e67c..2ec9878 100644 --- a/go.mod +++ b/go.mod @@ -193,8 +193,11 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.22.0 // indirect github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/hashicorp/terraform-plugin-framework v1.14.1 // indirect + github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 // indirect github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-mux v0.18.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.4 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect diff --git a/go.sum b/go.sum index 077181f..8d6b33c 100644 --- a/go.sum +++ b/go.sum @@ -492,10 +492,16 @@ github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4 github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= github.com/hashicorp/terraform-plugin-docs v0.20.1 h1:Fq7E/HrU8kuZu3hNliZGwloFWSYfWEOWnylFhYQIoys= github.com/hashicorp/terraform-plugin-docs v0.20.1/go.mod h1:Yz6HoK7/EgzSrHPB9J/lWFzwl9/xep2OPnc5jaJDV90= +github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY= +github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= +github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg= +github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0= github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-mux v0.18.0 h1:7491JFSpWyAe0v9YqBT+kel7mzHAbO5EpxxT0cUL/Ms= +github.com/hashicorp/terraform-plugin-mux v0.18.0/go.mod h1:Ho1g4Rr8qv0qTJlcRKfjjXTIO67LNbDtM6r+zHUNHJQ= github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw= github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4= github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A= diff --git a/internal/provider/cidr.go b/internal/provider/cidr.go deleted file mode 100644 index 1d6da24..0000000 --- a/internal/provider/cidr.go +++ /dev/null @@ -1,56 +0,0 @@ -package provider - -import ( - "fmt" - "net" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func cidrValidate(raw interface{}, key string) ([]string, []error) { - v, ok := raw.(string) - if !ok { - return nil, []error{fmt.Errorf("expected string, got %T", raw)} - } - - _, _, err := net.ParseCIDR(v) - if err != nil { - return nil, []error{err} - } - - return nil, nil -} - -func cidrDiffSuppress(k, old, new string, d *schema.ResourceData) bool { - _, oldNet, err := net.ParseCIDR(old) - if err != nil { - return false - } - - _, newNet, err := net.ParseCIDR(new) - if err != nil { - return false - } - - return oldNet.String() == newNet.String() -} - -func cidrZeroBased(cidr string) string { - _, cidrNet, err := net.ParseCIDR(cidr) - if err != nil { - return "" - } - - return cidrNet.String() -} - -func cidrOneBased(cidr string) string { - _, cidrNet, err := net.ParseCIDR(cidr) - if err != nil { - return "" - } - - cidrNet.IP[3]++ - - return cidrNet.String() -} diff --git a/internal/provider/client.go b/internal/provider/client.go new file mode 100644 index 0000000..9672f38 --- /dev/null +++ b/internal/provider/client.go @@ -0,0 +1,78 @@ +package provider + +import ( + "errors" + "fmt" + "github.com/filipowm/go-unifi/unifi" + "github.com/hashicorp/go-version" + "log" + "net/http" + "strings" +) + +func IsServerErrorContains(err error, messageContains string) bool { + if err == nil { + return false + } + var se *unifi.ServerError + if errors.As(err, &se) { + if strings.Contains(se.Message, messageContains) { + return true + } + // check details + if se.Details != nil { + for _, m := range se.Details { + if strings.Contains(m.Message, messageContains) { + return true + } + } + } + } + return false +} + +type ClientConfig struct { + Username string + Password string + ApiKey string + Url string + Site string + Insecure bool + HttpConfigurer func() http.RoundTripper +} + +func NewClient(cfg *ClientConfig) (*Client, error) { + unifiClient, err := unifi.NewClient(&unifi.ClientConfig{ + URL: cfg.Url, + User: cfg.Username, + Password: cfg.Password, + APIKey: cfg.ApiKey, + HttpRoundTripperProvider: cfg.HttpConfigurer, + ValidationMode: unifi.DisableValidation, + Logger: unifi.NewDefaultLogger(unifi.WarnLevel), + }) + + if err != nil { + return nil, err + } + err = CheckMinimumControllerVersion(unifiClient.Version()) + log.Printf("[TRACE] Unifi controller version: %q", unifiClient.Version()) + if err != nil { + return nil, err + } + c := &Client{ + Client: unifiClient, + Site: cfg.Site, + Version: version.Must(version.NewVersion(unifiClient.Version())), + } + if cfg.ApiKey != "" && !c.SupportsApiKeyAuthentication() { + return nil, fmt.Errorf("API key authentication is not supported on this controller version: %s, you must be on %s or higher", c.Version, ControllerVersionApiKeyAuth) + } + return c, nil +} + +type Client struct { + unifi.Client + Site string + Version *version.Version +} diff --git a/internal/provider/controller_versions.go b/internal/provider/controller_versions.go index abaa70b..e5c6d3e 100644 --- a/internal/provider/controller_versions.go +++ b/internal/provider/controller_versions.go @@ -11,37 +11,37 @@ func asVersion(versionString string) *version.Version { } var ( - controllerV6 = asVersion("6.0.0") - controllerV7 = asVersion("7.0.0") - controllerVersionApiKeyAuth = asVersion("9.0.108") + ControllerV6 = asVersion("6.0.0") + ControllerV7 = asVersion("7.0.0") + ControllerVersionApiKeyAuth = asVersion("9.0.108") // https://community.ui.com/releases/UniFi-Network-Controller-6-1-61/62f1ad38-1ac5-430c-94b0-becbb8f71d7d - controllerVersionWPA3 = asVersion("6.1.61") + ControllerVersionWPA3 = asVersion("6.1.61") ) -func (c *client) IsControllerV6() bool { - return c.version.GreaterThanOrEqual(controllerV6) +func (c *Client) IsControllerV6() bool { + return c.Version.GreaterThanOrEqual(ControllerV6) } -func (c *client) IsControllerV7() bool { - return c.version.GreaterThanOrEqual(controllerV7) +func (c *Client) IsControllerV7() bool { + return c.Version.GreaterThanOrEqual(ControllerV7) } -func (c *client) SupportsApiKeyAuthentication() bool { - return c.version.GreaterThanOrEqual(controllerVersionApiKeyAuth) +func (c *Client) SupportsApiKeyAuthentication() bool { + return c.Version.GreaterThanOrEqual(ControllerVersionApiKeyAuth) } -func (c *client) SupportsWPA3() bool { - return c.version.GreaterThanOrEqual(controllerVersionWPA3) +func (c *Client) SupportsWPA3() bool { + return c.Version.GreaterThanOrEqual(ControllerVersionWPA3) } -func checkMinimumControllerVersion(versionString string) error { +func CheckMinimumControllerVersion(versionString string) error { v, err := version.NewVersion(versionString) if err != nil { return err } - if v.LessThan(controllerV6) { - return fmt.Errorf("Controller version %q or greater is required to use the provider, found %q.", controllerV6, v) + if v.LessThan(ControllerV6) { + return fmt.Errorf("Controller version %q or greater is required to use the provider, found %q.", ControllerV6, v) } return nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 188ad6c..344a7be 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,216 +1,13 @@ package provider -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "github.com/hashicorp/go-version" - "log" - "net" - "net/http" - "strings" - "time" - - "github.com/filipowm/go-unifi/unifi" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +const ( + ProviderUsernameDescription = "Local user name for the Unifi controller API. Can be specified with the `UNIFI_USERNAME` environment variable." + ProviderPasswordDescription = "Password for the user accessing the API. Can be specified with the `UNIFI_PASSWORD` environment variable." + ProviderAPIKeyDescription = "API Key for the user accessing the API. Can be specified with the `UNIFI_API_KEY` environment variable. Controller version 9.0.108 or later is required." + ProviderAPIURLDescription = "URL of the controller API. Can be specified with the `UNIFI_API` environment variable. " + + "You should **NOT** supply the path (`/api`), the SDK will discover the appropriate paths. This is to support UDM Pro style API paths as well as more standard controller paths." + ProviderSiteDescription = "The site in the Unifi controller this provider will manage. Can be specified with the `UNIFI_SITE` environment variable. Default: `default`" + ProviderAllowInsecureDescription = "Skip verification of TLS certificates of API requests. You may need to set this to `true` " + + "if you are using your local API without setting up a signed certificate. Can be specified with the " + + "`UNIFI_INSECURE` environment variable." ) - -func init() { - schema.DescriptionKind = schema.StringMarkdown - - schema.SchemaDescriptionBuilder = func(s *schema.Schema) string { - desc := s.Description - if s.Default != nil { - desc += fmt.Sprintf(" Defaults to `%v`.", s.Default) - } - if s.Deprecated != "" { - desc += " " + s.Deprecated - } - return strings.TrimSpace(desc) - } -} - -func New(version string) func() *schema.Provider { - return func() *schema.Provider { - p := &schema.Provider{ - Schema: map[string]*schema.Schema{ - "username": { - Description: "Local user name for the Unifi controller API. Can be specified with the `UNIFI_USERNAME` " + - "environment variable.", - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_USERNAME", ""), - }, - "password": { - Description: "Password for the user accessing the API. Can be specified with the `UNIFI_PASSWORD` " + - "environment variable.", - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_PASSWORD", ""), - }, - "api_key": { - Description: "API Key for the user accessing the API. Can be specified with the `UNIFI_API_KEY` " + - "environment variable. Controller version 9.0.108 or later is required.", - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_API_KEY", ""), - }, - "api_url": { - Description: "URL of the controller API. Can be specified with the `UNIFI_API` environment variable. " + - "You should **NOT** supply the path (`/api`), the SDK will discover the appropriate paths. This is " + - "to support UDM Pro style API paths as well as more standard controller paths.", - - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_API", ""), - }, - "site": { - Description: "The site in the Unifi controller this provider will manage. Can be specified with " + - "the `UNIFI_SITE` environment variable. Default: `default`", - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_SITE", "default"), - }, - "allow_insecure": { - Description: "Skip verification of TLS certificates of API requests. You may need to set this to `true` " + - "if you are using your local API without setting up a signed certificate. Can be specified with the " + - "`UNIFI_INSECURE` environment variable.", - Type: schema.TypeBool, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("UNIFI_INSECURE", false), - }, - }, - DataSourcesMap: map[string]*schema.Resource{ - "unifi_ap_group": dataAPGroup(), - "unifi_network": dataNetwork(), - "unifi_port_profile": dataPortProfile(), - "unifi_radius_profile": dataRADIUSProfile(), - "unifi_user_group": dataUserGroup(), - "unifi_user": dataUser(), - "unifi_account": dataAccount(), - }, - ResourcesMap: map[string]*schema.Resource{ - // TODO: "unifi_ap_group" - "unifi_device": resourceDevice(), - "unifi_dynamic_dns": resourceDynamicDNS(), - "unifi_firewall_group": resourceFirewallGroup(), - "unifi_firewall_rule": resourceFirewallRule(), - "unifi_network": resourceNetwork(), - "unifi_port_forward": resourcePortForward(), - "unifi_port_profile": resourcePortProfile(), - "unifi_radius_profile": resourceRadiusProfile(), - "unifi_site": resourceSite(), - "unifi_static_route": resourceStaticRoute(), - "unifi_user_group": resourceUserGroup(), - "unifi_user": resourceUser(), - "unifi_wlan": resourceWLAN(), - "unifi_account": resourceAccount(), - - "unifi_setting_mgmt": resourceSettingMgmt(), - "unifi_setting_radius": resourceSettingRadius(), - "unifi_setting_usg": resourceSettingUsg(), - }, - } - - p.ConfigureContextFunc = configure(version, p) - return p - } -} - -func createHTTPTransport(insecure bool, subsystem string) http.RoundTripper { - transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecure, - }, - } - - t := logging.NewSubsystemLoggingHTTPTransport(subsystem, transport) - return t -} - -func configure(v string, p *schema.Provider) schema.ConfigureContextFunc { - return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { - user := d.Get("username").(string) - pass := d.Get("password").(string) - apiKey := d.Get("api_key").(string) - if apiKey != "" && (user != "" || pass != "") { - return nil, diag.FromErr(errors.New("only one of `username`/`password` or `api_key` can be set")) - } else if apiKey == "" && (user == "" || pass == "") { - return nil, diag.FromErr(errors.New("either `username` and `password` or `api_key` must be set")) - } - baseURL := d.Get("api_url").(string) - site := d.Get("site").(string) - insecure := d.Get("allow_insecure").(bool) - unifiClient, err := unifi.NewClient(&unifi.ClientConfig{ - URL: baseURL, - User: user, - Password: pass, - APIKey: apiKey, - HttpRoundTripperProvider: func() http.RoundTripper { - return createHTTPTransport(insecure, "unifi") - }, - ValidationMode: unifi.DisableValidation, - Logger: unifi.NewDefaultLogger(unifi.WarnLevel), - }) - - if err != nil { - return nil, diag.FromErr(err) - } - err = checkMinimumControllerVersion(unifiClient.Version()) - log.Printf("[TRACE] Unifi controller version: %q", unifiClient.Version()) - if err != nil { - return nil, diag.FromErr(err) - } - c := &client{ - c: unifiClient, - site: site, - version: version.Must(version.NewVersion(unifiClient.Version())), - } - if apiKey != "" && !c.SupportsApiKeyAuthentication() { - return nil, diag.FromErr(fmt.Errorf("API key authentication is not supported on this controller version: %s, you must be on %s or higher", c.version, controllerVersionApiKeyAuth)) - } - - return c, nil - } -} - -func IsServerErrorContains(err error, messageContains string) bool { - if err == nil { - return false - } - var se *unifi.ServerError - if errors.As(err, &se) { - if strings.Contains(se.Message, messageContains) { - return true - } - // check details - if se.Details != nil { - for _, m := range se.Details { - if strings.Contains(m.Message, messageContains) { - return true - } - } - } - } - return false -} - -type client struct { - c unifi.Client - site string - version *version.Version -} diff --git a/internal/provider/controller_versions_test.go b/internal/provider/v1/controller_versions_test.go similarity index 97% rename from internal/provider/controller_versions_test.go rename to internal/provider/v1/controller_versions_test.go index af27728..382ebe5 100644 --- a/internal/provider/controller_versions_test.go +++ b/internal/provider/v1/controller_versions_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "testing" diff --git a/internal/provider/data_account.go b/internal/provider/v1/data_account.go similarity index 89% rename from internal/provider/data_account.go rename to internal/provider/v1/data_account.go index 52cbc23..fe13f5c 100644 --- a/internal/provider/data_account.go +++ b/internal/provider/v1/data_account.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -9,7 +10,7 @@ import ( func dataAccount() *schema.Resource { return &schema.Resource{ - Description: "`unifi_account` data source can be used to retrieve RADIUS user accounts", + Description: "unifi_account data source can be used to retrieve RADIUS user accounts", ReadContext: dataAccountRead, @@ -57,15 +58,15 @@ func dataAccount() *schema.Resource { } func dataAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - accounts, err := c.c.ListAccount(ctx, site) + accounts, err := c.ListAccount(ctx, site) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_account_test.go b/internal/provider/v1/data_account_test.go similarity index 98% rename from internal/provider/data_account_test.go rename to internal/provider/v1/data_account_test.go index b3bba53..c1cf114 100644 --- a/internal/provider/data_account_test.go +++ b/internal/provider/v1/data_account_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/data_ap_group.go b/internal/provider/v1/data_ap_group.go similarity index 88% rename from internal/provider/data_ap_group.go rename to internal/provider/v1/data_ap_group.go index 38e4827..ece2922 100644 --- a/internal/provider/data_ap_group.go +++ b/internal/provider/v1/data_ap_group.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -35,15 +36,15 @@ func dataAPGroup() *schema.Resource { } func dataAPGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - groups, err := c.c.ListAPGroup(ctx, site) + groups, err := c.ListAPGroup(ctx, site) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_ap_group_test.go b/internal/provider/v1/data_ap_group_test.go similarity index 97% rename from internal/provider/data_ap_group_test.go rename to internal/provider/v1/data_ap_group_test.go index cfe596b..2c977a3 100644 --- a/internal/provider/data_ap_group_test.go +++ b/internal/provider/v1/data_ap_group_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "testing" diff --git a/internal/provider/data_network.go b/internal/provider/v1/data_network.go similarity index 97% rename from internal/provider/data_network.go rename to internal/provider/v1/data_network.go index 0a18436..b7043d7 100644 --- a/internal/provider/data_network.go +++ b/internal/provider/v1/data_network.go @@ -1,7 +1,9 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -279,19 +281,19 @@ func dataNetwork() *schema.Resource { } func dataNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) id := d.Get("id").(string) if site == "" { - site = c.site + site = c.Site } if (name == "" && id == "") || (name != "" && id != "") { return diag.Errorf("One of 'name' OR 'id' is required") } - networks, err := c.c.ListNetwork(ctx, site) + networks, err := c.ListNetwork(ctx, site) if err != nil { return diag.FromErr(err) } @@ -327,7 +329,7 @@ func dataNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface d.Set("name", n.Name) d.Set("purpose", n.Purpose) d.Set("vlan_id", n.VLAN) - d.Set("subnet", cidrZeroBased(n.IPSubnet)) + d.Set("subnet", utils.CidrZeroBased(n.IPSubnet)) d.Set("network_group", n.NetworkGroup) d.Set("dhcp_dns", dhcpDNS) d.Set("dhcp_start", n.DHCPDStart) diff --git a/internal/provider/data_network_test.go b/internal/provider/v1/data_network_test.go similarity index 91% rename from internal/provider/data_network_test.go rename to internal/provider/v1/data_network_test.go index 371eb31..211fbc7 100644 --- a/internal/provider/data_network_test.go +++ b/internal/provider/v1/data_network_test.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "testing" "github.com/hashicorp/go-version" @@ -14,7 +15,7 @@ func TestAccDataNetwork_byName(t *testing.T) { if err != nil { t.Fatalf("error parsing version: %s", err) } - if v.LessThan(controllerV7) { + if v.LessThan(provider.ControllerV7) { defaultName = "LAN" } @@ -41,7 +42,7 @@ func TestAccDataNetwork_byID(t *testing.T) { if err != nil { t.Fatalf("error parsing version: %s", err) } - if v.LessThan(controllerV7) { + if v.LessThan(provider.ControllerV7) { defaultName = "LAN" } diff --git a/internal/provider/data_port_profile.go b/internal/provider/v1/data_port_profile.go similarity index 88% rename from internal/provider/data_port_profile.go rename to internal/provider/v1/data_port_profile.go index 45702bf..ba4fc67 100644 --- a/internal/provider/data_port_profile.go +++ b/internal/provider/v1/data_port_profile.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -36,15 +37,15 @@ func dataPortProfile() *schema.Resource { } func dataPortProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - groups, err := c.c.ListPortProfile(ctx, site) + groups, err := c.ListPortProfile(ctx, site) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_port_profile_test.go b/internal/provider/v1/data_port_profile_test.go similarity index 98% rename from internal/provider/data_port_profile_test.go rename to internal/provider/v1/data_port_profile_test.go index 3d56fd4..e688f03 100644 --- a/internal/provider/data_port_profile_test.go +++ b/internal/provider/v1/data_port_profile_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "testing" diff --git a/internal/provider/data_radius_profile.go b/internal/provider/v1/data_radius_profile.go similarity index 88% rename from internal/provider/data_radius_profile.go rename to internal/provider/v1/data_radius_profile.go index 2943a1d..3a04498 100644 --- a/internal/provider/data_radius_profile.go +++ b/internal/provider/v1/data_radius_profile.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -36,15 +37,15 @@ func dataRADIUSProfile() *schema.Resource { } func dataRADIUSProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - profiles, err := c.c.ListRADIUSProfile(ctx, site) + profiles, err := c.ListRADIUSProfile(ctx, site) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_user.go b/internal/provider/v1/data_user.go similarity index 93% rename from internal/provider/data_user.go rename to internal/provider/v1/data_user.go index fd36c5f..4a721af 100644 --- a/internal/provider/data_user.go +++ b/internal/provider/v1/data_user.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -91,20 +92,20 @@ func dataUser() *schema.Resource { } func dataUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } mac := d.Get("mac").(string) - macResp, err := c.c.GetUserByMAC(ctx, site, strings.ToLower(mac)) + macResp, err := c.GetUserByMAC(ctx, site, strings.ToLower(mac)) if err != nil { return diag.FromErr(err) } - resp, err := c.c.GetUser(ctx, site, macResp.ID) + resp, err := c.GetUser(ctx, site, macResp.ID) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_user_group.go b/internal/provider/v1/data_user_group.go similarity index 90% rename from internal/provider/data_user_group.go rename to internal/provider/v1/data_user_group.go index 4de44c4..13b44b1 100644 --- a/internal/provider/data_user_group.go +++ b/internal/provider/v1/data_user_group.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -45,15 +46,15 @@ func dataUserGroup() *schema.Resource { } func dataUserGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - groups, err := c.c.ListUserGroup(ctx, site) + groups, err := c.ListUserGroup(ctx, site) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/data_user_group_test.go b/internal/provider/v1/data_user_group_test.go similarity index 98% rename from internal/provider/data_user_group_test.go rename to internal/provider/v1/data_user_group_test.go index d031da8..4fbe1c1 100644 --- a/internal/provider/data_user_group_test.go +++ b/internal/provider/v1/data_user_group_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "testing" diff --git a/internal/provider/data_user_test.go b/internal/provider/v1/data_user_test.go similarity index 98% rename from internal/provider/data_user_test.go rename to internal/provider/v1/data_user_test.go index 71a7a47..d94bd21 100644 --- a/internal/provider/data_user_test.go +++ b/internal/provider/v1/data_user_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "context" diff --git a/internal/provider/v1/diff_suppressions.go b/internal/provider/v1/diff_suppressions.go new file mode 100644 index 0000000..f93b5c0 --- /dev/null +++ b/internal/provider/v1/diff_suppressions.go @@ -0,0 +1,20 @@ +package v1 + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "net" +) + +func cidrDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + _, oldNet, err := net.ParseCIDR(old) + if err != nil { + return false + } + + _, newNet, err := net.ParseCIDR(new) + if err != nil { + return false + } + + return oldNet.String() == newNet.String() +} diff --git a/internal/provider/importer.go b/internal/provider/v1/importer.go similarity index 96% rename from internal/provider/importer.go rename to internal/provider/v1/importer.go index be15ab3..4c044f8 100644 --- a/internal/provider/importer.go +++ b/internal/provider/v1/importer.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "context" diff --git a/internal/provider/mac.go b/internal/provider/v1/mac.go similarity index 98% rename from internal/provider/mac.go rename to internal/provider/v1/mac.go index 194724a..63dd190 100644 --- a/internal/provider/mac.go +++ b/internal/provider/v1/mac.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( mapset "github.com/deckarep/golang-set/v2" diff --git a/internal/provider/port_range.go b/internal/provider/v1/port_range.go similarity index 98% rename from internal/provider/port_range.go rename to internal/provider/v1/port_range.go index 4f89e92..379986d 100644 --- a/internal/provider/port_range.go +++ b/internal/provider/v1/port_range.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "regexp" diff --git a/internal/provider/v1/provider.go b/internal/provider/v1/provider.go new file mode 100644 index 0000000..f805c5f --- /dev/null +++ b/internal/provider/v1/provider.go @@ -0,0 +1,165 @@ +package v1 + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "net" + "net/http" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func init() { + schema.DescriptionKind = schema.StringMarkdown + + schema.SchemaDescriptionBuilder = func(s *schema.Schema) string { + desc := s.Description + if s.Default != nil { + desc += fmt.Sprintf(" Defaults to `%v`.", s.Default) + } + if s.Deprecated != "" { + desc += " " + s.Deprecated + } + return strings.TrimSpace(desc) + } +} + +func New(version string) func() *schema.Provider { + return func() *schema.Provider { + p := &schema.Provider{ + Schema: map[string]*schema.Schema{ + "username": { + Description: provider.ProviderUsernameDescription, + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_USERNAME", ""), + }, + "password": { + Description: provider.ProviderPasswordDescription, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_PASSWORD", ""), + }, + "api_key": { + Description: provider.ProviderAPIKeyDescription, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_API_KEY", ""), + }, + "api_url": { + Description: provider.ProviderAPIURLDescription, + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_API", ""), + }, + "site": { + Description: provider.ProviderSiteDescription, + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_SITE", "default"), + }, + "allow_insecure": { + Description: provider.ProviderAllowInsecureDescription, + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_INSECURE", false), + }, + }, + DataSourcesMap: map[string]*schema.Resource{ + "unifi_ap_group": dataAPGroup(), + "unifi_network": dataNetwork(), + "unifi_port_profile": dataPortProfile(), + "unifi_radius_profile": dataRADIUSProfile(), + "unifi_user_group": dataUserGroup(), + "unifi_user": dataUser(), + "unifi_account": dataAccount(), + }, + ResourcesMap: map[string]*schema.Resource{ + // TODO: "unifi_ap_group" + "unifi_device": resourceDevice(), + "unifi_dynamic_dns": resourceDynamicDNS(), + "unifi_firewall_group": resourceFirewallGroup(), + "unifi_firewall_rule": resourceFirewallRule(), + "unifi_network": resourceNetwork(), + "unifi_port_forward": resourcePortForward(), + "unifi_port_profile": resourcePortProfile(), + "unifi_radius_profile": resourceRadiusProfile(), + "unifi_site": resourceSite(), + "unifi_static_route": resourceStaticRoute(), + "unifi_user_group": resourceUserGroup(), + "unifi_user": resourceUser(), + "unifi_wlan": resourceWLAN(), + "unifi_account": resourceAccount(), + + "unifi_setting_mgmt": resourceSettingMgmt(), + "unifi_setting_radius": resourceSettingRadius(), + "unifi_setting_usg": resourceSettingUsg(), + }, + } + + p.ConfigureContextFunc = configure(version, p) + return p + } +} + +func createHTTPTransport(insecure bool, subsystem string) http.RoundTripper { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecure, + }, + } + + t := logging.NewSubsystemLoggingHTTPTransport(subsystem, transport) + return t +} + +func configure(v string, p *schema.Provider) schema.ConfigureContextFunc { + return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { + user := d.Get("username").(string) + pass := d.Get("password").(string) + apiKey := d.Get("api_key").(string) + if apiKey != "" && (user != "" || pass != "") { + return nil, diag.FromErr(errors.New("only one of `username`/`password` or `api_key` can be set")) + } else if apiKey == "" && (user == "" || pass == "") { + return nil, diag.FromErr(errors.New("either `username` and `password` or `api_key` must be set")) + } + baseURL := d.Get("api_url").(string) + site := d.Get("site").(string) + insecure := d.Get("allow_insecure").(bool) + + c, err := provider.NewClient(&provider.ClientConfig{ + Username: user, + Password: pass, + ApiKey: apiKey, + Url: baseURL, + Site: site, + HttpConfigurer: func() http.RoundTripper { + return createHTTPTransport(insecure, "unifi") + }, + }) + if err != nil { + return nil, diag.FromErr(err) + } + return c, nil + } +} diff --git a/internal/provider/provider_test.go b/internal/provider/v1/provider_test.go similarity index 97% rename from internal/provider/provider_test.go rename to internal/provider/v1/provider_test.go index 2feeef0..fa0a555 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/v1/provider_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "bytes" @@ -38,7 +38,7 @@ func TestMain(m *testing.M) { } func runAcceptanceTests(m *testing.M) int { - dc, err := compose.NewDockerCompose("../../docker-compose.yaml") + dc, err := compose.NewDockerCompose("../../../docker-compose.yaml") if err != nil { panic(err) } diff --git a/internal/provider/resource_account.go b/internal/provider/v1/resource_account.go similarity index 91% rename from internal/provider/resource_account.go rename to internal/provider/v1/resource_account.go index dc57135..8b563c8 100644 --- a/internal/provider/resource_account.go +++ b/internal/provider/v1/resource_account.go @@ -1,8 +1,9 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -74,7 +75,7 @@ func resourceAccount() *schema.Resource { } func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceAccountGetResourceData(d) if err != nil { @@ -83,10 +84,10 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateAccount(ctx, site, req) + resp, err := c.CreateAccount(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -97,11 +98,11 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req, err := resourceAccountGetResourceData(d) @@ -112,7 +113,7 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int req.ID = d.Id() req.SiteID = site - resp, err := c.c.UpdateAccount(ctx, site, req) + resp, err := c.UpdateAccount(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -121,16 +122,16 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) //name := d.Get("name").(string) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } id := d.Id() - err := c.c.DeleteAccount(ctx, site, id) + err := c.DeleteAccount(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } @@ -138,16 +139,16 @@ func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetAccount(ctx, site, id) + resp, err := c.GetAccount(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil diff --git a/internal/provider/resource_account_test.go b/internal/provider/v1/resource_account_test.go similarity index 98% rename from internal/provider/resource_account_test.go rename to internal/provider/v1/resource_account_test.go index 62be79d..e028e92 100644 --- a/internal/provider/resource_account_test.go +++ b/internal/provider/v1/resource_account_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_device.go b/internal/provider/v1/resource_device.go similarity index 94% rename from internal/provider/resource_device.go rename to internal/provider/v1/resource_device.go index b6cd1ae..df3c2ef 100644 --- a/internal/provider/resource_device.go +++ b/internal/provider/v1/resource_device.go @@ -1,9 +1,10 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "strconv" "strings" "time" @@ -138,11 +139,11 @@ func resourceDevice() *schema.Resource { } func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } if colons := strings.Count(id, ":"); colons == 1 || colons == 6 { @@ -154,7 +155,7 @@ func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta inte if macAddressRegexp.MatchString(id) { // look up id by mac mac := cleanMAC(id) - device, err := c.c.GetDeviceByMAC(ctx, site, mac) + device, err := c.GetDeviceByMAC(ctx, site, mac) if err != nil { return nil, err @@ -174,11 +175,11 @@ func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } mac := d.Get("mac").(string) @@ -187,7 +188,7 @@ func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta inte } mac = cleanMAC(mac) - device, err := c.c.GetDeviceByMAC(ctx, site, mac) + device, err := c.GetDeviceByMAC(ctx, site, mac) if device == nil { return diag.Errorf("device not found using mac %q", mac) @@ -201,7 +202,7 @@ func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("Device must be adopted before it can be managed") } - err := c.c.AdoptDevice(ctx, site, mac) + err := c.AdoptDevice(ctx, site, mac) if err != nil { return diag.FromErr(err) } @@ -217,11 +218,11 @@ func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req, err := resourceDeviceGetResourceData(d) @@ -232,7 +233,7 @@ func resourceDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta inte req.ID = d.Id() req.SiteID = site - resp, err := c.c.UpdateDevice(ctx, site, req) + resp, err := c.UpdateDevice(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -246,7 +247,7 @@ func resourceDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) if !d.Get("forget_on_destroy").(bool) { return nil @@ -256,10 +257,10 @@ func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta inte mac := d.Get("mac").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.ForgetDevice(ctx, site, mac) + err := c.ForgetDevice(ctx, site, mac) if err != nil { return diag.FromErr(err) } @@ -273,16 +274,16 @@ func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetDevice(ctx, site, id) + resp, err := c.GetDevice(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -388,13 +389,13 @@ func fromPortOverride(po unifi.DevicePortOverrides) (map[string]interface{}, err } func waitForDeviceState(ctx context.Context, d *schema.ResourceData, meta interface{}, targetState unifi.DeviceState, pendingStates []unifi.DeviceState, timeout time.Duration) (*unifi.Device, error) { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) mac := d.Get("mac").(string) if site == "" { - site = c.site + site = c.Site } // Always consider unknown to be a pending state. @@ -409,7 +410,7 @@ func waitForDeviceState(ctx context.Context, d *schema.ResourceData, meta interf Pending: pending, Target: []string{targetState.String()}, Refresh: func() (interface{}, string, error) { - device, err := c.c.GetDeviceByMAC(ctx, site, mac) + device, err := c.GetDeviceByMAC(ctx, site, mac) if errors.Is(err, unifi.ErrNotFound) { err = nil diff --git a/internal/provider/resource_device_test.go b/internal/provider/v1/resource_device_test.go similarity index 99% rename from internal/provider/resource_device_test.go rename to internal/provider/v1/resource_device_test.go index 73a6fc0..271ae16 100644 --- a/internal/provider/resource_device_test.go +++ b/internal/provider/v1/resource_device_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "context" diff --git a/internal/provider/resource_dynamic_dns.go b/internal/provider/v1/resource_dynamic_dns.go similarity index 90% rename from internal/provider/resource_dynamic_dns.go rename to internal/provider/v1/resource_dynamic_dns.go index af1308c..993ecd1 100644 --- a/internal/provider/resource_dynamic_dns.go +++ b/internal/provider/v1/resource_dynamic_dns.go @@ -1,8 +1,9 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -75,7 +76,7 @@ func resourceDynamicDNS() *schema.Resource { } func resourceDynamicDNSCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceDynamicDNSGetResourceData(d) if err != nil { @@ -84,10 +85,10 @@ func resourceDynamicDNSCreate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateDynamicDNS(ctx, site, req) + resp, err := c.CreateDynamicDNS(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -126,16 +127,16 @@ func resourceDynamicDNSSetResourceData(resp *unifi.DynamicDNS, d *schema.Resourc } func resourceDynamicDNSRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetDynamicDNS(ctx, site, id) + resp, err := c.GetDynamicDNS(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -148,7 +149,7 @@ func resourceDynamicDNSRead(ctx context.Context, d *schema.ResourceData, meta in } func resourceDynamicDNSUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceDynamicDNSGetResourceData(d) if err != nil { @@ -159,11 +160,11 @@ func resourceDynamicDNSUpdate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateDynamicDNS(ctx, site, req) + resp, err := c.UpdateDynamicDNS(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -172,15 +173,15 @@ func resourceDynamicDNSUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourceDynamicDNSDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteDynamicDNS(ctx, site, id) + err := c.DeleteDynamicDNS(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_dynamic_dns_test.go b/internal/provider/v1/resource_dynamic_dns_test.go similarity index 97% rename from internal/provider/resource_dynamic_dns_test.go rename to internal/provider/v1/resource_dynamic_dns_test.go index 1f4f050..9ae47c2 100644 --- a/internal/provider/resource_dynamic_dns_test.go +++ b/internal/provider/v1/resource_dynamic_dns_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "testing" diff --git a/internal/provider/resource_firewall_group.go b/internal/provider/v1/resource_firewall_group.go similarity index 83% rename from internal/provider/resource_firewall_group.go rename to internal/provider/v1/resource_firewall_group.go index b053a17..040ea09 100644 --- a/internal/provider/resource_firewall_group.go +++ b/internal/provider/v1/resource_firewall_group.go @@ -1,8 +1,10 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -57,7 +59,7 @@ func resourceFirewallGroup() *schema.Resource { } func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceFirewallGroupGetResourceData(d) if err != nil { @@ -66,12 +68,12 @@ func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, me site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateFirewallGroup(ctx, site, req) + resp, err := c.CreateFirewallGroup(ctx, site, req) if err != nil { - if IsServerErrorContains(err, "api.err.FirewallGroupExisted") { + if provider.IsServerErrorContains(err, "api.err.FirewallGroupExisted") { return diag.Errorf("firewall groups must have unique names: %s", err) } return diag.FromErr(err) @@ -83,7 +85,7 @@ func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, me } func resourceFirewallGroupGetResourceData(d *schema.ResourceData) (*unifi.FirewallGroup, error) { - members, err := setToStringSlice(d.Get("members").(*schema.Set)) + members, err := utils.SetToStringSlice(d.Get("members").(*schema.Set)) if err != nil { return nil, err } @@ -99,22 +101,22 @@ func resourceFirewallGroupSetResourceData(resp *unifi.FirewallGroup, d *schema.R d.Set("site", site) d.Set("name", resp.Name) d.Set("type", resp.GroupType) - d.Set("members", stringSliceToSet(resp.GroupMembers)) + d.Set("members", utils.StringSliceToSet(resp.GroupMembers)) return nil } func resourceFirewallGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetFirewallGroup(ctx, site, id) + resp, err := c.GetFirewallGroup(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -127,7 +129,7 @@ func resourceFirewallGroupRead(ctx context.Context, d *schema.ResourceData, meta } func resourceFirewallGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceFirewallGroupGetResourceData(d) if err != nil { @@ -138,11 +140,11 @@ func resourceFirewallGroupUpdate(ctx context.Context, d *schema.ResourceData, me site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateFirewallGroup(ctx, site, req) + resp, err := c.UpdateFirewallGroup(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -151,16 +153,16 @@ func resourceFirewallGroupUpdate(ctx context.Context, d *schema.ResourceData, me } func resourceFirewallGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteFirewallGroup(ctx, site, id) + err := c.DeleteFirewallGroup(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_firewall_group_test.go b/internal/provider/v1/resource_firewall_group_test.go similarity index 99% rename from internal/provider/resource_firewall_group_test.go rename to internal/provider/v1/resource_firewall_group_test.go index 8267bc2..ec5f31c 100644 --- a/internal/provider/resource_firewall_group_test.go +++ b/internal/provider/v1/resource_firewall_group_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_firewall_rule.go b/internal/provider/v1/resource_firewall_rule.go similarity index 92% rename from internal/provider/resource_firewall_rule.go rename to internal/provider/v1/resource_firewall_rule.go index 3e7aa8d..540fc00 100644 --- a/internal/provider/resource_firewall_rule.go +++ b/internal/provider/v1/resource_firewall_rule.go @@ -1,8 +1,10 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "regexp" "github.com/filipowm/go-unifi/unifi" @@ -210,7 +212,7 @@ func resourceFirewallRule() *schema.Resource { } func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceFirewallRuleGetResourceData(d) if err != nil { @@ -219,12 +221,12 @@ func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, met site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateFirewallRule(ctx, site, req) + resp, err := c.CreateFirewallRule(ctx, site, req) if err != nil { - if IsServerErrorContains(err, "api.err.FirewallGroupTypeExists") { + if provider.IsServerErrorContains(err, "api.err.FirewallGroupTypeExists") { return diag.Errorf("firewall rule groups must be of different group types (ie. a port group and address group): %s", err) } @@ -237,12 +239,12 @@ func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, met } func resourceFirewallRuleGetResourceData(d *schema.ResourceData) (*unifi.FirewallRule, error) { - srcFirewallGroupIDs, err := setToStringSlice(d.Get("src_firewall_group_ids").(*schema.Set)) + srcFirewallGroupIDs, err := utils.SetToStringSlice(d.Get("src_firewall_group_ids").(*schema.Set)) if err != nil { return nil, err } - dstFirewallGroupIDs, err := setToStringSlice(d.Get("dst_firewall_group_ids").(*schema.Set)) + dstFirewallGroupIDs, err := utils.SetToStringSlice(d.Get("dst_firewall_group_ids").(*schema.Set)) if err != nil { return nil, err } @@ -300,7 +302,7 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res d.Set("state_related", resp.StateRelated) d.Set("src_network_type", resp.SrcNetworkType) - d.Set("src_firewall_group_ids", stringSliceToSet(resp.SrcFirewallGroupIDs)) + d.Set("src_firewall_group_ids", utils.StringSliceToSet(resp.SrcFirewallGroupIDs)) d.Set("src_mac", resp.SrcMACAddress) d.Set("src_address", resp.SrcAddress) d.Set("src_address_ipv6", resp.SrcAddressIPV6) @@ -308,7 +310,7 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res d.Set("src_port", resp.SrcPort) d.Set("dst_network_type", resp.DstNetworkType) - d.Set("dst_firewall_group_ids", stringSliceToSet(resp.DstFirewallGroupIDs)) + d.Set("dst_firewall_group_ids", utils.StringSliceToSet(resp.DstFirewallGroupIDs)) d.Set("dst_address", resp.DstAddress) d.Set("dst_address_ipv6", resp.DstAddressIPV6) d.Set("dst_network_id", resp.DstNetworkID) @@ -318,16 +320,16 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res } func resourceFirewallRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetFirewallRule(ctx, site, id) + resp, err := c.GetFirewallRule(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -340,7 +342,7 @@ func resourceFirewallRuleRead(ctx context.Context, d *schema.ResourceData, meta } func resourceFirewallRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceFirewallRuleGetResourceData(d) if err != nil { @@ -351,11 +353,11 @@ func resourceFirewallRuleUpdate(ctx context.Context, d *schema.ResourceData, met site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateFirewallRule(ctx, site, req) + resp, err := c.UpdateFirewallRule(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -364,15 +366,15 @@ func resourceFirewallRuleUpdate(ctx context.Context, d *schema.ResourceData, met } func resourceFirewallRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteFirewallRule(ctx, site, id) + err := c.DeleteFirewallRule(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_firewall_rule_test.go b/internal/provider/v1/resource_firewall_rule_test.go similarity index 99% rename from internal/provider/resource_firewall_rule_test.go rename to internal/provider/v1/resource_firewall_rule_test.go index 01d6b92..fff6bd6 100644 --- a/internal/provider/resource_firewall_rule_test.go +++ b/internal/provider/v1/resource_firewall_rule_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_network.go b/internal/provider/v1/resource_network.go similarity index 95% rename from internal/provider/resource_network.go rename to internal/provider/v1/resource_network.go index 2229c11..dd72eaf 100644 --- a/internal/provider/resource_network.go +++ b/internal/provider/v1/resource_network.go @@ -1,9 +1,11 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "regexp" "strings" @@ -90,7 +92,7 @@ func resourceNetwork() *schema.Resource { Type: schema.TypeString, Optional: true, DiffSuppressFunc: cidrDiffSuppress, - ValidateFunc: cidrValidate, + ValidateFunc: utils.CidrValidate, }, "network_group": { Description: "The group of the network.", @@ -382,7 +384,7 @@ func resourceNetwork() *schema.Resource { } func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceNetworkGetResourceData(d, meta) if err != nil { @@ -391,10 +393,10 @@ func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta int site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateNetwork(ctx, site, req) + resp, err := c.CreateNetwork(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -405,18 +407,18 @@ func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta int } func resourceNetworkGetResourceData(d *schema.ResourceData, meta interface{}) (*unifi.Network, error) { - // c := meta.(*client) + // c := meta.(*provider.Client) vlan := d.Get("vlan_id").(int) - dhcpDNS, err := listToStringSlice(d.Get("dhcp_dns").([]interface{})) + dhcpDNS, err := utils.ListToStringSlice(d.Get("dhcp_dns").([]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{})) + dhcpV6DNS, err := utils.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{})) + wanDNS, err := utils.ListToStringSlice(d.Get("wan_dns").([]interface{})) if err != nil { return nil, fmt.Errorf("unable to convert wan_dns to string slice: %w", err) } @@ -425,7 +427,7 @@ func resourceNetworkGetResourceData(d *schema.ResourceData, meta interface{}) (* Name: d.Get("name").(string), Purpose: d.Get("purpose").(string), VLAN: vlan, - IPSubnet: cidrOneBased(d.Get("subnet").(string)), + IPSubnet: utils.CidrOneBased(d.Get("subnet").(string)), NetworkGroup: d.Get("network_group").(string), DHCPDStart: d.Get("dhcp_start").(string), DHCPDStop: d.Get("dhcp_stop").(string), @@ -571,7 +573,7 @@ func resourceNetworkSetResourceData(resp *unifi.Network, d *schema.ResourceData, d.Set("name", resp.Name) d.Set("purpose", resp.Purpose) d.Set("vlan_id", vlan) - d.Set("subnet", cidrZeroBased(resp.IPSubnet)) + d.Set("subnet", utils.CidrZeroBased(resp.IPSubnet)) d.Set("network_group", resp.NetworkGroup) d.Set("dhcp_dns", dhcpDNS) @@ -624,16 +626,16 @@ func resourceNetworkSetResourceData(resp *unifi.Network, d *schema.ResourceData, } func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetNetwork(ctx, site, id) + resp, err := c.GetNetwork(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -646,7 +648,7 @@ func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta inter } func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceNetworkGetResourceData(d, meta) if err != nil { @@ -656,11 +658,11 @@ func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta int req.ID = d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateNetwork(ctx, site, req) + resp, err := c.UpdateNetwork(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -669,15 +671,15 @@ func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } id := d.Id() - err := c.c.DeleteNetwork(ctx, site, id) + err := c.DeleteNetwork(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } @@ -685,11 +687,11 @@ func resourceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta int } func importNetwork(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } if strings.Contains(id, ":") { @@ -701,7 +703,7 @@ func importNetwork(ctx context.Context, d *schema.ResourceData, meta interface{} if strings.HasPrefix(id, "name=") { targetName := strings.TrimPrefix(id, "name=") var err error - if id, err = getNetworkIDByName(ctx, c.c, targetName, site); err != nil { + if id, err = getNetworkIDByName(ctx, c.Client, targetName, site); err != nil { return nil, err } } diff --git a/internal/provider/resource_network_test.go b/internal/provider/v1/resource_network_test.go similarity index 99% rename from internal/provider/resource_network_test.go rename to internal/provider/v1/resource_network_test.go index 87c7d4e..ce457da 100644 --- a/internal/provider/resource_network_test.go +++ b/internal/provider/v1/resource_network_test.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "net" "regexp" "strconv" @@ -412,7 +413,7 @@ func TestAccNetwork_mdns(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) - preCheckMinVersion(t, controllerV7) + preCheckMinVersion(t, provider.ControllerV7) }, ProviderFactories: providerFactories, // TODO: CheckDestroy: , diff --git a/internal/provider/resource_port_forward.go b/internal/provider/v1/resource_port_forward.go similarity index 91% rename from internal/provider/resource_port_forward.go rename to internal/provider/v1/resource_port_forward.go index 99b3cf6..cc4612a 100644 --- a/internal/provider/resource_port_forward.go +++ b/internal/provider/v1/resource_port_forward.go @@ -1,8 +1,10 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -94,7 +96,7 @@ func resourcePortForward() *schema.Resource { ValidateFunc: validation.Any( validation.StringInSlice([]string{"any"}, false), validation.IsIPv4Address, - cidrValidate, + utils.CidrValidate, ), }, }, @@ -102,7 +104,7 @@ func resourcePortForward() *schema.Resource { } func resourcePortForwardCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourcePortForwardGetResourceData(d) if err != nil { @@ -111,9 +113,9 @@ func resourcePortForwardCreate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreatePortForward(ctx, site, req) + resp, err := c.CreatePortForward(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -153,15 +155,15 @@ func resourcePortForwardSetResourceData(resp *unifi.PortForward, d *schema.Resou } func resourcePortForwardRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetPortForward(ctx, site, id) + resp, err := c.GetPortForward(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -174,7 +176,7 @@ func resourcePortForwardRead(ctx context.Context, d *schema.ResourceData, meta i } func resourcePortForwardUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourcePortForwardGetResourceData(d) if err != nil { @@ -185,11 +187,11 @@ func resourcePortForwardUpdate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdatePortForward(ctx, site, req) + resp, err := c.UpdatePortForward(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -198,15 +200,15 @@ func resourcePortForwardUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourcePortForwardDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeletePortForward(ctx, site, id) + err := c.DeletePortForward(ctx, site, id) return diag.FromErr(err) } diff --git a/internal/provider/resource_port_forward_test.go b/internal/provider/v1/resource_port_forward_test.go similarity index 99% rename from internal/provider/resource_port_forward_test.go rename to internal/provider/v1/resource_port_forward_test.go index 69981b6..12d1201 100644 --- a/internal/provider/resource_port_forward_test.go +++ b/internal/provider/v1/resource_port_forward_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_port_profile.go b/internal/provider/v1/resource_port_profile.go similarity index 94% rename from internal/provider/resource_port_profile.go rename to internal/provider/v1/resource_port_profile.go index 694c049..d39f4ea 100644 --- a/internal/provider/resource_port_profile.go +++ b/internal/provider/v1/resource_port_profile.go @@ -1,8 +1,10 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -256,7 +258,7 @@ func resourcePortProfile() *schema.Resource { } func resourcePortProfileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourcePortProfileGetResourceData(d) if err != nil { @@ -265,9 +267,9 @@ func resourcePortProfileCreate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreatePortProfile(ctx, site, req) + resp, err := c.CreatePortProfile(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -278,12 +280,12 @@ func resourcePortProfileCreate(ctx context.Context, d *schema.ResourceData, meta } func resourcePortProfileGetResourceData(d *schema.ResourceData) (*unifi.PortProfile, error) { - portSecurityMacAddress, err := setToStringSlice(d.Get("port_security_mac_address").(*schema.Set)) + portSecurityMacAddress, err := utils.SetToStringSlice(d.Get("port_security_mac_address").(*schema.Set)) if err != nil { return nil, err } - excludedNetworkIDs, err := setToStringSlice(d.Get("excluded_network_ids").(*schema.Set)) + excludedNetworkIDs, err := utils.SetToStringSlice(d.Get("excluded_network_ids").(*schema.Set)) if err != nil { return nil, err } @@ -334,7 +336,7 @@ func resourcePortProfileSetResourceData(resp *unifi.PortProfile, d *schema.Resou d.Set("dot1x_idle_timeout", resp.Dot1XIDleTimeout) d.Set("egress_rate_limit_kbps", resp.EgressRateLimitKbps) d.Set("egress_rate_limit_kbps_enabled", resp.EgressRateLimitKbpsEnabled) - d.Set("excluded_network_ids", stringSliceToSet(resp.ExcludedNetworkIDs)) + d.Set("excluded_network_ids", utils.StringSliceToSet(resp.ExcludedNetworkIDs)) d.Set("forward", resp.Forward) d.Set("full_duplex", resp.FullDuplex) d.Set("isolation", resp.Isolation) @@ -345,7 +347,7 @@ func resourcePortProfileSetResourceData(resp *unifi.PortProfile, d *schema.Resou d.Set("op_mode", resp.OpMode) d.Set("poe_mode", resp.PoeMode) d.Set("port_security_enabled", resp.PortSecurityEnabled) - d.Set("port_security_mac_address", stringSliceToSet(resp.PortSecurityMACAddress)) + d.Set("port_security_mac_address", utils.StringSliceToSet(resp.PortSecurityMACAddress)) d.Set("priority_queue1_level", resp.PriorityQueue1Level) d.Set("priority_queue2_level", resp.PriorityQueue2Level) d.Set("priority_queue3_level", resp.PriorityQueue3Level) @@ -369,15 +371,15 @@ func resourcePortProfileSetResourceData(resp *unifi.PortProfile, d *schema.Resou } func resourcePortProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetPortProfile(ctx, site, id) + resp, err := c.GetPortProfile(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -390,7 +392,7 @@ func resourcePortProfileRead(ctx context.Context, d *schema.ResourceData, meta i } func resourcePortProfileUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourcePortProfileGetResourceData(d) if err != nil { @@ -401,11 +403,11 @@ func resourcePortProfileUpdate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdatePortProfile(ctx, site, req) + resp, err := c.UpdatePortProfile(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -414,15 +416,15 @@ func resourcePortProfileUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourcePortProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeletePortProfile(ctx, site, id) + err := c.DeletePortProfile(ctx, site, id) return diag.FromErr(err) } diff --git a/internal/provider/resource_port_profile_test.go b/internal/provider/v1/resource_port_profile_test.go similarity index 98% rename from internal/provider/resource_port_profile_test.go rename to internal/provider/v1/resource_port_profile_test.go index d3a852a..fe37895 100644 --- a/internal/provider/resource_port_profile_test.go +++ b/internal/provider/v1/resource_port_profile_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_radius_profile.go b/internal/provider/v1/resource_radius_profile.go similarity index 95% rename from internal/provider/resource_radius_profile.go rename to internal/provider/v1/resource_radius_profile.go index 07f775a..eb9d457 100644 --- a/internal/provider/resource_radius_profile.go +++ b/internal/provider/v1/resource_radius_profile.go @@ -1,9 +1,10 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "strings" "github.com/filipowm/go-unifi/unifi" @@ -234,7 +235,7 @@ func fromAcctServer(sshKey unifi.RADIUSProfileAcctServers) (map[string]interface } func resourceRadiusProfileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceRadiusProfileGetResourceData(d) if err != nil { return diag.FromErr(err) @@ -242,9 +243,9 @@ func resourceRadiusProfileCreate(ctx context.Context, d *schema.ResourceData, me site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateRADIUSProfile(ctx, site, req) + resp, err := c.CreateRADIUSProfile(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -302,15 +303,15 @@ func resourceRadiusProfileSetResourceData(resp *unifi.RADIUSProfile, d *schema.R } func resourceRadiusProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetRADIUSProfile(ctx, site, id) + resp, err := c.GetRADIUSProfile(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -323,7 +324,7 @@ func resourceRadiusProfileRead(ctx context.Context, d *schema.ResourceData, meta } func resourceRadiusProfileUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceRadiusProfileGetResourceData(d) if err != nil { @@ -334,11 +335,11 @@ func resourceRadiusProfileUpdate(ctx context.Context, d *schema.ResourceData, me site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateRADIUSProfile(ctx, site, req) + resp, err := c.UpdateRADIUSProfile(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -347,25 +348,25 @@ func resourceRadiusProfileUpdate(ctx context.Context, d *schema.ResourceData, me } func resourceRadiusProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteRADIUSProfile(ctx, site, id) + err := c.DeleteRADIUSProfile(ctx, site, id) return diag.FromErr(err) } func importRadiusProfile(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } if strings.Contains(id, ":") { @@ -377,7 +378,7 @@ func importRadiusProfile(ctx context.Context, d *schema.ResourceData, meta inter if strings.HasPrefix(id, "name=") { targetName := strings.TrimPrefix(id, "name=") var err error - if id, err = getRadiusProfileIDByName(ctx, c.c, targetName, site); err != nil { + if id, err = getRadiusProfileIDByName(ctx, c.Client, targetName, site); err != nil { return nil, err } } diff --git a/internal/provider/resource_radius_profile_test.go b/internal/provider/v1/resource_radius_profile_test.go similarity index 99% rename from internal/provider/resource_radius_profile_test.go rename to internal/provider/v1/resource_radius_profile_test.go index 300f429..d0e8d6f 100644 --- a/internal/provider/resource_radius_profile_test.go +++ b/internal/provider/v1/resource_radius_profile_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_setting_mgmt.go b/internal/provider/v1/resource_setting_mgmt.go similarity index 93% rename from internal/provider/resource_setting_mgmt.go rename to internal/provider/v1/resource_setting_mgmt.go index 0e11da1..7bb6456 100644 --- a/internal/provider/resource_setting_mgmt.go +++ b/internal/provider/v1/resource_setting_mgmt.go @@ -1,9 +1,10 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -141,7 +142,7 @@ func resourceSettingMgmtGetResourceData(d *schema.ResourceData, meta interface{} } func resourceSettingMgmtCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceSettingMgmtGetResourceData(d, meta) if err != nil { @@ -150,10 +151,10 @@ func resourceSettingMgmtCreate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.UpdateSettingMgmt(ctx, site, req) + resp, err := c.UpdateSettingMgmt(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -177,14 +178,14 @@ func resourceSettingMgmtSetResourceData(resp *unifi.SettingMgmt, d *schema.Resou } func resourceSettingMgmtRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetSettingMgmt(ctx, site) + resp, err := c.GetSettingMgmt(ctx, site) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -197,7 +198,7 @@ func resourceSettingMgmtRead(ctx context.Context, d *schema.ResourceData, meta i } func resourceSettingMgmtUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceSettingMgmtGetResourceData(d, meta) if err != nil { @@ -207,10 +208,10 @@ func resourceSettingMgmtUpdate(ctx context.Context, d *schema.ResourceData, meta req.ID = d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.UpdateSettingMgmt(ctx, site, req) + resp, err := c.UpdateSettingMgmt(ctx, site, req) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/resource_setting_mgmt_test.go b/internal/provider/v1/resource_setting_mgmt_test.go similarity index 99% rename from internal/provider/resource_setting_mgmt_test.go rename to internal/provider/v1/resource_setting_mgmt_test.go index 80ee6d6..7b68386 100644 --- a/internal/provider/resource_setting_mgmt_test.go +++ b/internal/provider/v1/resource_setting_mgmt_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "sync" diff --git a/internal/provider/resource_setting_radius.go b/internal/provider/v1/resource_setting_radius.go similarity index 92% rename from internal/provider/resource_setting_radius.go rename to internal/provider/v1/resource_setting_radius.go index 1756d1b..548da87 100644 --- a/internal/provider/resource_setting_radius.go +++ b/internal/provider/v1/resource_setting_radius.go @@ -1,8 +1,9 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -98,7 +99,7 @@ func resourceSettingRadiusGetResourceData(d *schema.ResourceData, meta interface } func resourceSettingRadiusCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceSettingRadiusGetResourceData(d, meta) if err != nil { @@ -107,10 +108,10 @@ func resourceSettingRadiusCreate(ctx context.Context, d *schema.ResourceData, me site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.UpdateSettingRadius(ctx, site, req) + resp, err := c.UpdateSettingRadius(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -133,14 +134,14 @@ func resourceSettingRadiusSetResourceData(resp *unifi.SettingRadius, d *schema.R } func resourceSettingRadiusRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetSettingRadius(ctx, site) + resp, err := c.GetSettingRadius(ctx, site) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -153,7 +154,7 @@ func resourceSettingRadiusRead(ctx context.Context, d *schema.ResourceData, meta } func resourceSettingRadiusUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceSettingRadiusGetResourceData(d, meta) if err != nil { @@ -163,10 +164,10 @@ func resourceSettingRadiusUpdate(ctx context.Context, d *schema.ResourceData, me req.ID = d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.UpdateSettingRadius(ctx, site, req) + resp, err := c.UpdateSettingRadius(ctx, site, req) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/resource_setting_radius_test.go b/internal/provider/v1/resource_setting_radius_test.go similarity index 99% rename from internal/provider/resource_setting_radius_test.go rename to internal/provider/v1/resource_setting_radius_test.go index 6a4c6d3..97e6aca 100644 --- a/internal/provider/resource_setting_radius_test.go +++ b/internal/provider/v1/resource_setting_radius_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "sync" diff --git a/internal/provider/resource_setting_usg.go b/internal/provider/v1/resource_setting_usg.go similarity index 89% rename from internal/provider/resource_setting_usg.go rename to internal/provider/v1/resource_setting_usg.go index 772c98f..dacb261 100644 --- a/internal/provider/resource_setting_usg.go +++ b/internal/provider/v1/resource_setting_usg.go @@ -1,9 +1,11 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "sync" "github.com/filipowm/go-unifi/unifi" @@ -73,18 +75,18 @@ func resourceSettingUsg() *schema.Resource { } func resourceSettingUsgUpdateResourceData(d *schema.ResourceData, meta interface{}, setting *unifi.SettingUsg) error { - c := meta.(*client) + c := meta.(*provider.Client) //nolint // GetOkExists is deprecated, but using here: if mdns, hasMdns := d.GetOkExists("multicast_dns_enabled"); hasMdns { if c.IsControllerV7() { - return fmt.Errorf("multicast_dns_enabled is not supported on controller version %v", c.version) + return fmt.Errorf("multicast_dns_enabled is not supported on controller version %v", c.Version) } setting.MdnsEnabled = mdns.(bool) } - dhcpRelay, err := listToStringSlice(d.Get("dhcp_relay_servers").([]interface{})) + dhcpRelay, err := utils.ListToStringSlice(d.Get("dhcp_relay_servers").([]interface{})) if err != nil { return fmt.Errorf("unable to convert dhcp_relay_servers to string slice: %w", err) } @@ -98,14 +100,14 @@ func resourceSettingUsgUpdateResourceData(d *schema.ResourceData, meta interface } func resourceSettingUsgUpsert(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - req, err := c.c.GetSettingUsg(ctx, c.site) + req, err := c.GetSettingUsg(ctx, c.Site) if err != nil { return diag.FromErr(err) } @@ -115,7 +117,7 @@ func resourceSettingUsgUpsert(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } - resp, err := c.c.UpdateSettingUsg(ctx, site, req) + resp, err := c.UpdateSettingUsg(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -147,14 +149,14 @@ func resourceSettingUsgSetResourceData(resp *unifi.SettingUsg, d *schema.Resourc } func resourceSettingUsgRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetSettingUsg(ctx, site) + resp, err := c.GetSettingUsg(ctx, site) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil diff --git a/internal/provider/resource_setting_usg_test.go b/internal/provider/v1/resource_setting_usg_test.go similarity index 99% rename from internal/provider/resource_setting_usg_test.go rename to internal/provider/v1/resource_setting_usg_test.go index 505d1db..2ccaae1 100644 --- a/internal/provider/resource_setting_usg_test.go +++ b/internal/provider/v1/resource_setting_usg_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_site.go b/internal/provider/v1/resource_site.go similarity index 85% rename from internal/provider/resource_site.go rename to internal/provider/v1/resource_site.go index ef08a3e..f159adf 100644 --- a/internal/provider/resource_site.go +++ b/internal/provider/v1/resource_site.go @@ -1,9 +1,10 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -43,10 +44,10 @@ func resourceSite() *schema.Resource { } func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() - _, err := c.c.GetSite(ctx, id) + _, err := c.GetSite(ctx, id) if err != nil { if !errors.Is(err, unifi.ErrNotFound) { return nil, err @@ -57,7 +58,7 @@ func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta interf } // lookup site by name - sites, err := c.c.ListSites(ctx) + sites, err := c.ListSites(ctx) if err != nil { return nil, err } @@ -73,11 +74,11 @@ func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta interf } func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) description := d.Get("description").(string) - resp, err := c.c.CreateSite(ctx, description) + resp, err := c.CreateSite(ctx, description) if err != nil { return diag.FromErr(err) } @@ -95,11 +96,11 @@ func resourceSiteSetResourceData(resp *unifi.Site, d *schema.ResourceData) diag. } func resourceSiteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() - site, err := c.c.GetSite(ctx, id) + site, err := c.GetSite(ctx, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -112,7 +113,7 @@ func resourceSiteRead(ctx context.Context, d *schema.ResourceData, meta interfac } func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := &unifi.Site{ ID: d.Id(), @@ -120,7 +121,7 @@ func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interf Description: d.Get("description").(string), } - resp, err := c.c.UpdateSite(ctx, site.Name, site.Description) + resp, err := c.UpdateSite(ctx, site.Name, site.Description) if err != nil { return diag.FromErr(err) } @@ -129,8 +130,8 @@ func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceSiteDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() - _, err := c.c.DeleteSite(ctx, id) + _, err := c.DeleteSite(ctx, id) return diag.FromErr(err) } diff --git a/internal/provider/resource_site_test.go b/internal/provider/v1/resource_site_test.go similarity index 99% rename from internal/provider/resource_site_test.go rename to internal/provider/v1/resource_site_test.go index fc18ba9..82e2265 100644 --- a/internal/provider/resource_site_test.go +++ b/internal/provider/v1/resource_site_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "context" diff --git a/internal/provider/resource_static_route.go b/internal/provider/v1/resource_static_route.go similarity index 87% rename from internal/provider/resource_static_route.go rename to internal/provider/v1/resource_static_route.go index 8e0d09b..44ab540 100644 --- a/internal/provider/resource_static_route.go +++ b/internal/provider/v1/resource_static_route.go @@ -1,9 +1,11 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -46,7 +48,7 @@ func resourceStaticRoute() *schema.Resource { Description: "The network subnet address.", Type: schema.TypeString, Required: true, - ValidateFunc: cidrValidate, + ValidateFunc: utils.CidrValidate, DiffSuppressFunc: cidrDiffSuppress, }, "type": { @@ -77,7 +79,7 @@ func resourceStaticRoute() *schema.Resource { } func resourceStaticRouteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceStaticRouteGetResourceData(d) if err != nil { @@ -86,10 +88,10 @@ func resourceStaticRouteCreate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateRouting(ctx, site, req) + resp, err := c.CreateRouting(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -107,7 +109,7 @@ func resourceStaticRouteGetResourceData(d *schema.ResourceData) (*unifi.Routing, Type: "static-route", Name: d.Get("name").(string), - StaticRouteNetwork: cidrZeroBased(d.Get("network").(string)), + StaticRouteNetwork: utils.CidrZeroBased(d.Get("network").(string)), StaticRouteDistance: d.Get("distance").(int), StaticRouteType: t, } @@ -128,7 +130,7 @@ func resourceStaticRouteGetResourceData(d *schema.ResourceData) (*unifi.Routing, func resourceStaticRouteSetResourceData(resp *unifi.Routing, d *schema.ResourceData, site string) diag.Diagnostics { d.Set("site", site) d.Set("name", resp.Name) - d.Set("network", cidrZeroBased(resp.StaticRouteNetwork)) + d.Set("network", utils.CidrZeroBased(resp.StaticRouteNetwork)) d.Set("distance", resp.StaticRouteDistance) t := resp.StaticRouteType @@ -152,16 +154,16 @@ func resourceStaticRouteSetResourceData(resp *unifi.Routing, d *schema.ResourceD } func resourceStaticRouteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetRouting(ctx, site, id) + resp, err := c.GetRouting(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -174,7 +176,7 @@ func resourceStaticRouteRead(ctx context.Context, d *schema.ResourceData, meta i } func resourceStaticRouteUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceStaticRouteGetResourceData(d) if err != nil { @@ -185,11 +187,11 @@ func resourceStaticRouteUpdate(ctx context.Context, d *schema.ResourceData, meta site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateRouting(ctx, site, req) + resp, err := c.UpdateRouting(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -198,15 +200,15 @@ func resourceStaticRouteUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourceStaticRouteDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteRouting(ctx, site, id) + err := c.DeleteRouting(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_static_route_test.go b/internal/provider/v1/resource_static_route_test.go similarity index 99% rename from internal/provider/resource_static_route_test.go rename to internal/provider/v1/resource_static_route_test.go index b177e64..f42afb4 100644 --- a/internal/provider/resource_static_route_test.go +++ b/internal/provider/v1/resource_static_route_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_user.go b/internal/provider/v1/resource_user.go similarity index 88% rename from internal/provider/resource_user.go rename to internal/provider/v1/resource_user.go index 39572a8..df57332 100644 --- a/internal/provider/resource_user.go +++ b/internal/provider/v1/resource_user.go @@ -1,9 +1,10 @@ -package provider +package v1 import ( "context" "errors" "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "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" @@ -118,7 +119,7 @@ func resourceUser() *schema.Resource { } func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceUserGetResourceData(d) if err != nil { @@ -129,18 +130,18 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateUser(ctx, site, req) + resp, err := c.CreateUser(ctx, site, req) if err != nil { - if !IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { + if !provider.IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { return diag.FromErr(err) } // mac in use, just absorb it mac := d.Get("mac").(string) - existing, err := c.c.GetUserByMAC(ctx, site, mac) + existing, err := c.GetUserByMAC(ctx, site, mac) if err != nil { return diag.FromErr(err) } @@ -148,7 +149,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf req.ID = existing.ID req.SiteID = existing.SiteID - resp, err = c.c.UpdateUser(ctx, site, req) + resp, err = c.UpdateUser(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -157,7 +158,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf d.SetId(resp.ID) if d.Get("blocked").(bool) { - err := c.c.BlockUserByMAC(ctx, site, d.Get("mac").(string)) + err := c.BlockUserByMAC(ctx, site, d.Get("mac").(string)) if err != nil { return diag.FromErr(err) } @@ -167,7 +168,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf mac := d.Get("mac").(string) device := d.Get("dev_id_override").(int) - err := c.c.OverrideUserFingerprint(context.TODO(), site, mac, device) + err := c.OverrideUserFingerprint(context.TODO(), site, mac, device) if err != nil { return diag.FromErr(err) } @@ -226,16 +227,16 @@ func resourceUserSetResourceData(resp *unifi.User, d *schema.ResourceData, site } func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetUser(ctx, site, id) + resp, err := c.GetUser(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -245,7 +246,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac } // for some reason the IP address is only on this endpoint, so issue another request - macResp, err := c.c.GetUserByMAC(ctx, site, resp.MAC) + macResp, err := c.GetUserByMAC(ctx, site, resp.MAC) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -262,22 +263,22 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac } func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } if d.HasChange("blocked") { mac := d.Get("mac").(string) if d.Get("blocked").(bool) { - err := c.c.BlockUserByMAC(ctx, site, mac) + err := c.BlockUserByMAC(ctx, site, mac) if err != nil { return diag.FromErr(err) } } else { - err := c.c.UnblockUserByMAC(ctx, site, mac) + err := c.UnblockUserByMAC(ctx, site, mac) if err != nil { return diag.FromErr(err) } @@ -288,7 +289,7 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf mac := d.Get("mac").(string) device := d.Get("dev_id_override").(int) - err := c.c.OverrideUserFingerprint(context.TODO(), site, mac, device) + err := c.OverrideUserFingerprint(context.TODO(), site, mac, device) if err != nil { return diag.FromErr(err) } @@ -306,7 +307,7 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf req.ID = d.Id() req.SiteID = site - resp, err := c.c.UpdateUser(ctx, site, req) + resp, err := c.UpdateUser(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -315,7 +316,7 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() @@ -325,11 +326,11 @@ func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interf site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } // lookup MAC instead of trusting state - u, err := c.c.GetUser(ctx, site, id) + u, err := c.GetUser(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } @@ -337,6 +338,6 @@ func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interf return diag.FromErr(err) } - err = c.c.DeleteUserByMAC(ctx, site, u.MAC) + err = c.DeleteUserByMAC(ctx, site, u.MAC) return diag.FromErr(err) } diff --git a/internal/provider/resource_user_group.go b/internal/provider/v1/resource_user_group.go similarity index 87% rename from internal/provider/resource_user_group.go rename to internal/provider/v1/resource_user_group.go index 91e6817..3c4cb58 100644 --- a/internal/provider/resource_user_group.go +++ b/internal/provider/v1/resource_user_group.go @@ -1,8 +1,9 @@ -package provider +package v1 import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -59,7 +60,7 @@ func resourceUserGroup() *schema.Resource { } func resourceUserGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceUserGroupGetResourceData(d) if err != nil { @@ -68,10 +69,10 @@ func resourceUserGroupCreate(ctx context.Context, d *schema.ResourceData, meta i site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateUserGroup(context.TODO(), site, req) + resp, err := c.CreateUserGroup(context.TODO(), site, req) if err != nil { return diag.FromErr(err) } @@ -98,16 +99,16 @@ func resourceUserGroupSetResourceData(resp *unifi.UserGroup, d *schema.ResourceD } func resourceUserGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetUserGroup(context.TODO(), site, id) + resp, err := c.GetUserGroup(context.TODO(), site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -120,7 +121,7 @@ func resourceUserGroupRead(ctx context.Context, d *schema.ResourceData, meta int } func resourceUserGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceUserGroupGetResourceData(d) if err != nil { @@ -131,11 +132,11 @@ func resourceUserGroupUpdate(ctx context.Context, d *schema.ResourceData, meta i site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateUserGroup(context.TODO(), site, req) + resp, err := c.UpdateUserGroup(context.TODO(), site, req) if err != nil { return diag.FromErr(err) } @@ -144,15 +145,15 @@ func resourceUserGroupUpdate(ctx context.Context, d *schema.ResourceData, meta i } func resourceUserGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteUserGroup(context.TODO(), site, id) + err := c.DeleteUserGroup(context.TODO(), site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_user_group_test.go b/internal/provider/v1/resource_user_group_test.go similarity index 98% rename from internal/provider/resource_user_group_test.go rename to internal/provider/v1/resource_user_group_test.go index 46b3b19..e1b611f 100644 --- a/internal/provider/resource_user_group_test.go +++ b/internal/provider/v1/resource_user_group_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "fmt" diff --git a/internal/provider/resource_user_test.go b/internal/provider/v1/resource_user_test.go similarity index 99% rename from internal/provider/resource_user_test.go rename to internal/provider/v1/resource_user_test.go index c9e7d61..84caee0 100644 --- a/internal/provider/resource_user_test.go +++ b/internal/provider/v1/resource_user_test.go @@ -1,4 +1,4 @@ -package provider +package v1 import ( "context" diff --git a/internal/provider/resource_wlan.go b/internal/provider/v1/resource_wlan.go similarity index 93% rename from internal/provider/resource_wlan.go rename to internal/provider/v1/resource_wlan.go index f408ebd..c1fe42e 100644 --- a/internal/provider/resource_wlan.go +++ b/internal/provider/v1/resource_wlan.go @@ -1,9 +1,11 @@ -package provider +package v1 import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -203,7 +205,7 @@ func resourceWLAN() *schema.Resource { "minimum_data_rate_2g_kbps": { Description: "Set minimum data rate control for 2G devices, in Kbps. " + "Use `0` to disable minimum data rates. " + - "Valid values are: " + markdownValueListInt(wlanValidMinimumDataRate2g) + ".", + "Valid values are: " + utils.MarkdownValueListInt(wlanValidMinimumDataRate2g) + ".", Type: schema.TypeInt, Optional: true, // TODO: this validation is from the UI, if other values work, perhaps remove this is set it to a range instead? @@ -212,7 +214,7 @@ func resourceWLAN() *schema.Resource { "minimum_data_rate_5g_kbps": { Description: "Set minimum data rate control for 5G devices, in Kbps. " + "Use `0` to disable minimum data rates. " + - "Valid values are: " + markdownValueListInt(wlanValidMinimumDataRate5g) + ".", + "Valid values are: " + utils.MarkdownValueListInt(wlanValidMinimumDataRate5g) + ".", Type: schema.TypeInt, Optional: true, // TODO: this validation is from the UI, if other values work, perhaps remove this is set it to a range instead? @@ -243,7 +245,7 @@ func resourceWLAN() *schema.Resource { } func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*unifi.WLAN, error) { - c := meta.(*client) + c := meta.(*provider.Client) security := d.Get("security").(string) passphrase := d.Get("passphrase").(string) @@ -265,7 +267,7 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni } if !c.SupportsWPA3() { if wpa3 || wpa3Transition { - return nil, fmt.Errorf("WPA 3 support is not available on controller version %q, you must be on %q or higher", c.version, controllerVersionWPA3) + return nil, fmt.Errorf("WPA 3 support is not available on controller version %q, you must be on %q or higher", c.Version, provider.ControllerVersionWPA3) } } @@ -276,7 +278,7 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni } macFilterEnabled := d.Get("mac_filter_enabled").(bool) - macFilterList, err := setToStringSlice(d.Get("mac_filter_list").(*schema.Set)) + macFilterList, err := utils.SetToStringSlice(d.Get("mac_filter_list").(*schema.Set)) if err != nil { return nil, err } @@ -286,7 +288,7 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni // version specific fields and validation networkID := d.Get("network_id").(string) - apGroupIDs, err := setToStringSlice(d.Get("ap_group_ids").(*schema.Set)) + apGroupIDs, err := utils.SetToStringSlice(d.Get("ap_group_ids").(*schema.Set)) if err != nil { return nil, err } @@ -353,7 +355,7 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni } func resourceWLANCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceWLANGetResourceData(d, meta) if err != nil { @@ -362,10 +364,10 @@ func resourceWLANCreate(ctx context.Context, d *schema.ResourceData, meta interf site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.CreateWLAN(ctx, site, req) + resp, err := c.CreateWLAN(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -376,7 +378,7 @@ func resourceWLANCreate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta interface{}, site string) diag.Diagnostics { - // c := meta.(*client) + // c := meta.(*provider.Client) security := resp.Security passphrase := resp.XPassphrase wpa3 := false @@ -393,11 +395,11 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta var macFilterList *schema.Set macFilterPolicy := "deny" if macFilterEnabled { - macFilterList = stringSliceToSet(resp.MACFilterList) + macFilterList = utils.StringSliceToSet(resp.MACFilterList) macFilterPolicy = resp.MACFilterPolicy } - apGroupIDs := stringSliceToSet(resp.ApGroupIDs) + apGroupIDs := utils.StringSliceToSet(resp.ApGroupIDs) schedule := listFromSchedules(resp.ScheduleWithDuration) @@ -441,15 +443,15 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta } func resourceWLANRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - resp, err := c.c.GetWLAN(ctx, site, id) + resp, err := c.GetWLAN(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { d.SetId("") return nil @@ -462,7 +464,7 @@ func resourceWLANRead(ctx context.Context, d *schema.ResourceData, meta interfac } func resourceWLANUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) req, err := resourceWLANGetResourceData(d, meta) if err != nil { @@ -472,11 +474,11 @@ func resourceWLANUpdate(ctx context.Context, d *schema.ResourceData, meta interf req.ID = d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } req.SiteID = site - resp, err := c.c.UpdateWLAN(ctx, site, req) + resp, err := c.UpdateWLAN(ctx, site, req) if err != nil { return diag.FromErr(err) } @@ -485,15 +487,15 @@ func resourceWLANUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceWLANDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client) + c := meta.(*provider.Client) id := d.Id() site := d.Get("site").(string) if site == "" { - site = c.site + site = c.Site } - err := c.c.DeleteWLAN(ctx, site, id) + err := c.DeleteWLAN(ctx, site, id) if errors.Is(err, unifi.ErrNotFound) { return nil } diff --git a/internal/provider/resource_wlan_test.go b/internal/provider/v1/resource_wlan_test.go similarity index 99% rename from internal/provider/resource_wlan_test.go rename to internal/provider/v1/resource_wlan_test.go index 5cb0943..a328be6 100644 --- a/internal/provider/resource_wlan_test.go +++ b/internal/provider/v1/resource_wlan_test.go @@ -1,7 +1,8 @@ -package provider +package v1 import ( "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "net" "testing" @@ -331,7 +332,7 @@ func TestAccWLAN_wpa3(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { preCheck(t) - preCheckMinVersion(t, controllerVersionWPA3) + preCheckMinVersion(t, provider.ControllerVersionWPA3) }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { diff --git a/internal/provider/v2/provider.go b/internal/provider/v2/provider.go new file mode 100644 index 0000000..0d9964b --- /dev/null +++ b/internal/provider/v2/provider.go @@ -0,0 +1,172 @@ +package v2 + +import ( + "context" + up "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &unifiProvider{ + version: version, + } + } +} + +type unifiProvider struct { + version string +} + +type unifiProviderModel struct { + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + APIKey types.String `tfsdk:"api_key"` + APIUrl types.String `tfsdk:"api_url"` + Site types.String `tfsdk:"site"` + Insecure types.Bool `tfsdk:"allow_insecure"` +} + +func (p *unifiProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "unifi" + resp.Version = p.version +} + +func (p *unifiProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "username": schema.StringAttribute{ + MarkdownDescription: up.ProviderUsernameDescription, + Optional: true, + }, + "password": schema.StringAttribute{ + MarkdownDescription: up.ProviderPasswordDescription, + Optional: true, + Sensitive: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: up.ProviderAPIKeyDescription, + Optional: true, + Sensitive: true, + }, + "api_url": schema.StringAttribute{ + MarkdownDescription: up.ProviderAPIURLDescription, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), // workaround for `required: true`, because it fails on doc generation due to incorrectly detected difference between v1 and v2 + }, + Optional: true, + }, + "site": schema.StringAttribute{ + MarkdownDescription: up.ProviderSiteDescription, + Optional: true, + }, + "allow_insecure": schema.BoolAttribute{ + MarkdownDescription: up.ProviderAllowInsecureDescription, + Optional: true, + }, + }, + } +} + +func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + tflog.Info(ctx, "Configuring Unifi provider...") + // Retrieve provider data from the configuration + var cfg unifiProviderModel + diags := req.Config.Get(ctx, &cfg) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // If practitioner provided a configuration value for any of the + // attributes, it must be a known value. + + if cfg.APIUrl.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_url"), + "Unknown UniFi Controller API URL", + "The provider cannot create the UniFi Controller API client as there is an unknown configuration value "+ + "for the API endpoint. Either target apply the source of the value first, set the value statically in "+ + "the configuration, or use the UNIFI_API_URL environment variable.", + ) + } + + if resp.Diagnostics.HasError() { + return + } + // Default values to environment variables, but override + // with Terraform configuration value if set. + + // Check environment variables + username := utils.GetAnyStringEnv("UNIFI_USERNAME") + password := utils.GetAnyStringEnv("UNIFI_PASSWORD") + apiKey := utils.GetAnyStringEnv("UNIFI_API_KEY") + apiUrl := utils.GetAnyStringEnv("UNIFI_API_URL") + site := utils.GetAnyStringEnv("UNIFI_SITE") + insecure := utils.GetAnyBoolEnv("UNIFI_INSECURE") + + if !cfg.Username.IsNull() { + username = cfg.Username.ValueString() + } + if !cfg.Password.IsNull() { + password = cfg.Password.ValueString() + } + if !cfg.APIKey.IsNull() { + apiKey = cfg.APIKey.ValueString() + } + if !cfg.APIUrl.IsNull() { + apiUrl = cfg.APIUrl.ValueString() + } + if !cfg.Site.IsNull() { + site = cfg.Site.ValueString() + } + if !cfg.Insecure.IsNull() { + insecure = cfg.Insecure.ValueBool() + } + if apiKey != "" && (username != "" || password != "") { + resp.Diagnostics.AddAttributeError(path.Root("api_key"), "Two authentication methods configured", "Only one of `username`/`password` or `api_key` can be set") + } else if apiKey == "" && (username == "" || password == "") { + resp.Diagnostics.AddAttributeError(path.Root("api_key"), "Missing UniFi API credentials", "Either `username`/`password` or `api_key` must be set") + } + if apiUrl == "" { + resp.Diagnostics.AddAttributeError(path.Root("api_url"), "Missing UniFi API URL", "The `api_url` attribute must be set") + } + if resp.Diagnostics.HasError() { + return + } + if site == "" { + site = "default" // set default site if not provided + } + c, err := up.NewClient(&up.ClientConfig{ + Username: username, + Password: password, + ApiKey: apiKey, + Url: apiUrl, + Site: site, + Insecure: insecure, + }) + if err != nil { + resp.Diagnostics.AddError("Failed to create UniFi client", err.Error()) + return + } + resp.ResourceData = c + resp.DataSourceData = c +} + +func (p *unifiProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{} +} + +func (p *unifiProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} diff --git a/internal/utils/cidr.go b/internal/utils/cidr.go new file mode 100644 index 0000000..68cad28 --- /dev/null +++ b/internal/utils/cidr.go @@ -0,0 +1,40 @@ +package utils + +import ( + "fmt" + "net" +) + +func CidrValidate(raw interface{}, key string) ([]string, []error) { + v, ok := raw.(string) + if !ok { + return nil, []error{fmt.Errorf("expected string, got %T", raw)} + } + + _, _, err := net.ParseCIDR(v) + if err != nil { + return nil, []error{err} + } + + return nil, nil +} + +func CidrZeroBased(cidr string) string { + _, cidrNet, err := net.ParseCIDR(cidr) + if err != nil { + return "" + } + + return cidrNet.String() +} + +func CidrOneBased(cidr string) string { + _, cidrNet, err := net.ParseCIDR(cidr) + if err != nil { + return "" + } + + cidrNet.IP[3]++ + + return cidrNet.String() +} diff --git a/internal/provider/cidr_test.go b/internal/utils/cidr_test.go similarity index 93% rename from internal/provider/cidr_test.go rename to internal/utils/cidr_test.go index 224101e..1d098c5 100644 --- a/internal/provider/cidr_test.go +++ b/internal/utils/cidr_test.go @@ -1,4 +1,4 @@ -package provider +package utils import ( "testing" @@ -18,7 +18,7 @@ func TestCIDRValidate(t *testing.T) { {"", "192.1.2.1/20"}, } { t.Run(c.cidr, func(t *testing.T) { - _, actualErrs := cidrValidate(c.cidr, "key") + _, actualErrs := CidrValidate(c.cidr, "key") switch len(actualErrs) { case 0: if c.expectedError != "" { diff --git a/internal/utils/env.go b/internal/utils/env.go new file mode 100644 index 0000000..84db535 --- /dev/null +++ b/internal/utils/env.go @@ -0,0 +1,50 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package utils + +import ( + "os" + "strconv" +) + +// GetAnyStringEnv returns the first non-empty string value from the environment variables. +func GetAnyStringEnv(ks ...string) string { + for _, k := range ks { + if v := os.Getenv(k); v != "" { + return v + } + } + + return "" +} + +// GetAnyBoolEnv returns the first non-empty boolean value from the environment variables. +func GetAnyBoolEnv(ks ...string) bool { + val := "" + + for _, k := range ks { + if v := os.Getenv(k); v != "" { + val = v + break + } + } + + return val == "true" || val == "1" +} + +// GetAnyIntEnv returns the first non-empty integer value from the environment variables. +func GetAnyIntEnv(ks ...string) int { + for _, k := range ks { + if v := os.Getenv(k); v != "" { + if i, err := strconv.Atoi(v); err == nil { + return i + } + } + } + + return 0 +} diff --git a/internal/provider/markdown.go b/internal/utils/markdown.go similarity index 82% rename from internal/provider/markdown.go rename to internal/utils/markdown.go index 2407bb5..e210f5a 100644 --- a/internal/provider/markdown.go +++ b/internal/utils/markdown.go @@ -1,8 +1,8 @@ -package provider +package utils import "strconv" -func markdownValueListInt(values []int) string { +func MarkdownValueListInt(values []int) string { switch { case len(values) == 0: return "" diff --git a/internal/provider/strings.go b/internal/utils/strings.go similarity index 54% rename from internal/provider/strings.go rename to internal/utils/strings.go index 46a7058..2d7a163 100644 --- a/internal/provider/strings.go +++ b/internal/utils/strings.go @@ -1,4 +1,4 @@ -package provider +package utils import ( "fmt" @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func listToStringSlice(src []interface{}) ([]string, error) { +func ListToStringSlice(src []interface{}) ([]string, error) { dst := make([]string, 0, len(src)) for _, s := range src { d, ok := s.(string) @@ -18,11 +18,11 @@ func listToStringSlice(src []interface{}) ([]string, error) { return dst, nil } -func setToStringSlice(src *schema.Set) ([]string, error) { - return listToStringSlice(src.List()) +func SetToStringSlice(src *schema.Set) ([]string, error) { + return ListToStringSlice(src.List()) } -func stringSliceToList(list []string) []interface{} { +func StringSliceToList(list []string) []interface{} { vs := make([]interface{}, 0, len(list)) for _, v := range list { vs = append(vs, v) @@ -30,6 +30,6 @@ func stringSliceToList(list []string) []interface{} { return vs } -func stringSliceToSet(src []string) *schema.Set { - return schema.NewSet(schema.HashString, stringSliceToList(src)) +func StringSliceToSet(src []string) *schema.Set { + return schema.NewSet(schema.HashString, StringSliceToList(src)) } diff --git a/main.go b/main.go index ea6d2ab..5bcefda 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,20 @@ package main // import "github.com/filipowm/terraform-provider-unifi" import ( + "context" "flag" - - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - - "github.com/filipowm/terraform-provider-unifi/internal/provider" + v1 "github.com/filipowm/terraform-provider-unifi/internal/provider/v1" + v2 "github.com/filipowm/terraform-provider-unifi/internal/provider/v2" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "log" ) -// Generate docs for website -//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs - var ( // these will be set by the goreleaser configuration // to appropriate values for the compiled binary @@ -21,17 +25,50 @@ var ( ) func main() { + ctx := context.Background() var debugMode bool flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() - opts := &plugin.ServeOpts{ProviderFunc: provider.New(version)} - - if debugMode { - opts.Debug = true - opts.ProviderAddr = "registry.terraform.io/filipowm/unifi" + p := v1.New(version) + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + func() tfprotov5.ProviderServer { + return schema.NewGRPCProviderServer(p()) + }, + ) + if err != nil { + log.Fatal(err) } - plugin.Serve(opts) + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(v2.New(version)()), + func() tfprotov6.ProviderServer { + return upgradedSdkServer + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf6server.ServeOpt + if debugMode { + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) + } + + // Remove any date and time prefix in log package function output to + // prevent duplicate timestamp and incorrect log level setting + log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) + + err = tf6server.Serve( + "registry.terraform.io/filipowm/unifi", + muxServer.ProviderServer, + serveOpts..., + ) + if err != nil { + log.Fatal(err) + } } diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index 63daee3..5a8dc6f 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -1,13 +1,13 @@ --- layout: "" -page_title: "Provider: Unifi" +page_title: "Provider: UniFi" description: |- - The Unifi provider provides resources to interact with a Unifi controller API. + The Unifi provider provides resources to interact with a UniFi Controller API. --- # Unifi Provider -The Unifi provider provides resources to interact with a Unifi controller API. +The Unifi provider provides resources to interact with a UniFi Controller API. It is not recommended to use your own account for management of your controller. A user specific to Terraform is recommended. You can create a **Limited Admin** with **Local Access Only** and diff --git a/tools/tools.go b/tools/tools.go index c8d3b1e..f44dac3 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -7,3 +7,6 @@ import ( _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" ) + +// Generate documentation. +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir ../ --rendered-website-dir ./docs --provider-name "terraform-provider-unifi" --rendered-provider-name "terraform-provider-unifi" //nolint:lll