From e7164c0460fea3645de98a01535867ccc36d590a Mon Sep 17 00:00:00 2001 From: Mateusz Filipowicz Date: Wed, 26 Feb 2025 01:17:59 +0100 Subject: [PATCH] feat: add DNS record resource and datasources (#25) * add DNS record * revamp tests * lint * cleanup * feat dns test * chore: add DNS Record tests * linting * f --- Makefile | 2 +- .../resources/unifi_dns_record/resource.tf | 11 + go.mod | 26 +- go.sum | 33 +- .../controller_versions_test.go | 6 +- .../{v1 => acctest}/data_account_test.go | 18 +- .../{v1 => acctest}/data_ap_group_test.go | 12 +- .../{v1 => acctest}/data_network_test.go | 24 +- .../acctest/data_port_profile_test.go | 25 ++ .../provider/acctest/data_user_group_test.go | 26 ++ .../{v1 => acctest}/data_user_test.go | 11 +- .../acctest/datasource_dns_record_test.go | 106 ++++++ .../acctest/datasource_dns_records_test.go | 88 +++++ internal/provider/acctest/provider_test.go | 94 ++++++ .../{v1 => acctest}/resource_account_test.go | 17 +- .../{v1 => acctest}/resource_device_test.go | 24 +- .../acctest/resource_dns_record_test.go | 318 ++++++++++++++++++ .../resource_dynamic_dns_test.go | 9 +- .../resource_firewall_group_test.go | 25 +- .../resource_firewall_rule_test.go | 49 +-- .../{v1 => acctest}/resource_network_test.go | 126 +++---- .../resource_port_forward_test.go | 25 +- .../resource_port_profile_test.go | 13 +- .../resource_radius_profile_test.go | 19 +- .../resource_setting_mgmt_test.go | 21 +- .../resource_setting_radius_test.go | 27 +- .../resource_setting_usg_test.go | 33 +- .../{v1 => acctest}/resource_site_test.go | 11 +- .../resource_static_route_test.go | 39 +-- .../resource_user_group_test.go | 11 +- .../{v1 => acctest}/resource_user_test.go | 59 ++-- .../{v1 => acctest}/resource_wlan_test.go | 205 +++-------- .../provider/{v1 => apgroup}/data_ap_group.go | 13 +- internal/provider/base/base.go | 45 +++ internal/provider/{ => base}/client.go | 23 +- .../{ => base}/controller_versions.go | 14 +- .../{v1 => device}/resource_device.go | 29 +- .../provider/dns/datasource_dns_record.go | 131 ++++++++ .../provider/dns/datasource_dns_records.go | 81 +++++ internal/provider/dns/dns_record_model.go | 102 ++++++ internal/provider/dns/resource_dns_record.go | 236 +++++++++++++ .../{v1 => dns}/resource_dynamic_dns.go | 17 +- .../resource_firewall_group.go | 18 +- .../resource_firewall_rule.go | 22 +- .../provider/{v1 => network}/data_network.go | 12 +- .../{v1 => network}/data_port_profile.go | 9 +- .../{v1 => network}/resource_network.go | 22 +- .../{v1 => network}/resource_port_profile.go | 16 +- .../provider/{v1 => network}/resource_wlan.go | 30 +- internal/provider/provider.go | 154 +++++++++ .../{v2/provider.go => provider_v2.go} | 34 +- .../provider/{v1 => radius}/data_account.go | 9 +- .../{v1 => radius}/data_radius_profile.go | 9 +- .../{v1 => radius}/resource_account.go | 17 +- .../{v1 => radius}/resource_radius_profile.go | 16 +- .../{v1 => routing}/resource_port_forward.go | 20 +- .../{v1 => routing}/resource_static_route.go | 18 +- .../{v1 => settings}/resource_setting_mgmt.go | 15 +- .../resource_setting_radius.go | 15 +- .../{v1 => settings}/resource_setting_usg.go | 14 +- .../provider/{v1 => site}/resource_site.go | 17 +- internal/provider/testing/test_environment.go | 277 +++++++++++++++ internal/provider/testing/test_helpers.go | 78 +++++ .../test_helpers_network.go} | 48 ++- .../provider/testing/test_helpers_random.go | 23 ++ .../provider/testing/test_helpers_regex.go | 10 + internal/provider/{v1 => user}/data_user.go | 13 +- .../provider/{v1 => user}/data_user_group.go | 9 +- .../provider/{v1 => user}/resource_user.go | 23 +- .../{v1 => user}/resource_user_group.go | 17 +- .../provider/v1/data_port_profile_test.go | 63 ---- internal/provider/v1/data_user_group_test.go | 59 ---- internal/provider/v1/diff_suppressions.go | 20 -- internal/provider/v1/provider.go | 165 --------- internal/provider/v1/provider_test.go | 192 ----------- internal/utils/attribute.go | 35 ++ internal/utils/cidr.go | 15 + internal/{provider/v1 => utils}/importer.go | 7 +- internal/utils/mac.go | 19 ++ internal/{provider/v1 => utils}/port_range.go | 6 +- internal/utils/strings.go | 5 + main.go | 7 +- 82 files changed, 2488 insertions(+), 1274 deletions(-) create mode 100644 examples/resources/unifi_dns_record/resource.tf rename internal/provider/{v1 => acctest}/controller_versions_test.go (81%) rename internal/provider/{v1 => acctest}/data_account_test.go (73%) rename internal/provider/{v1 => acctest}/data_ap_group_test.go (71%) rename internal/provider/{v1 => acctest}/data_network_test.go (80%) create mode 100644 internal/provider/acctest/data_port_profile_test.go create mode 100644 internal/provider/acctest/data_user_group_test.go rename internal/provider/{v1 => acctest}/data_user_test.go (79%) create mode 100644 internal/provider/acctest/datasource_dns_record_test.go create mode 100644 internal/provider/acctest/datasource_dns_records_test.go create mode 100644 internal/provider/acctest/provider_test.go rename internal/provider/{v1 => acctest}/resource_account_test.go (74%) rename internal/provider/{v1 => acctest}/resource_device_test.go (94%) create mode 100644 internal/provider/acctest/resource_dns_record_test.go rename internal/provider/{v1 => acctest}/resource_dynamic_dns_test.go (75%) rename internal/provider/{v1 => acctest}/resource_firewall_group_test.go (76%) rename internal/provider/{v1 => acctest}/resource_firewall_rule_test.go (83%) rename internal/provider/{v1 => acctest}/resource_network_test.go (86%) rename internal/provider/{v1 => acctest}/resource_port_forward_test.go (81%) rename internal/provider/{v1 => acctest}/resource_port_profile_test.go (78%) rename internal/provider/{v1 => acctest}/resource_radius_profile_test.go (81%) rename internal/provider/{v1 => acctest}/resource_setting_mgmt_test.go (85%) rename internal/provider/{v1 => acctest}/resource_setting_radius_test.go (81%) rename internal/provider/{v1 => acctest}/resource_setting_usg_test.go (79%) rename internal/provider/{v1 => acctest}/resource_site_test.go (89%) rename internal/provider/{v1 => acctest}/resource_static_route_test.go (86%) rename internal/provider/{v1 => acctest}/resource_user_group_test.go (80%) rename internal/provider/{v1 => acctest}/resource_user_test.go (86%) rename internal/provider/{v1 => acctest}/resource_wlan_test.go (75%) rename internal/provider/{v1 => apgroup}/data_ap_group.go (87%) create mode 100644 internal/provider/base/base.go rename internal/provider/{ => base}/client.go (78%) rename internal/provider/{ => base}/controller_versions.go (73%) rename internal/provider/{v1 => device}/resource_device.go (95%) create mode 100644 internal/provider/dns/datasource_dns_record.go create mode 100644 internal/provider/dns/datasource_dns_records.go create mode 100644 internal/provider/dns/dns_record_model.go create mode 100644 internal/provider/dns/resource_dns_record.go rename internal/provider/{v1 => dns}/resource_dynamic_dns.go (94%) rename internal/provider/{v1 => firewall}/resource_firewall_group.go (93%) rename internal/provider/{v1 => firewall}/resource_firewall_rule.go (97%) rename internal/provider/{v1 => network}/data_network.go (98%) rename internal/provider/{v1 => network}/data_port_profile.go (93%) rename internal/provider/{v1 => network}/resource_network.go (98%) rename internal/provider/{v1 => network}/resource_port_profile.go (98%) rename internal/provider/{v1 => network}/resource_wlan.go (96%) rename internal/provider/{v2/provider.go => provider_v2.go} (85%) rename internal/provider/{v1 => radius}/data_account.go (96%) rename internal/provider/{v1 => radius}/data_radius_profile.go (93%) rename internal/provider/{v1 => radius}/resource_account.go (94%) rename internal/provider/{v1 => radius}/resource_radius_profile.go (98%) rename internal/provider/{v1 => routing}/resource_port_forward.go (95%) rename internal/provider/{v1 => routing}/resource_static_route.go (95%) rename internal/provider/{v1 => settings}/resource_setting_mgmt.go (95%) rename internal/provider/{v1 => settings}/resource_setting_radius.go (94%) rename internal/provider/{v1 => settings}/resource_setting_usg.go (96%) rename internal/provider/{v1 => site}/resource_site.go (93%) create mode 100644 internal/provider/testing/test_environment.go create mode 100644 internal/provider/testing/test_helpers.go rename internal/provider/{v1/mac.go => testing/test_helpers_network.go} (54%) create mode 100644 internal/provider/testing/test_helpers_random.go create mode 100644 internal/provider/testing/test_helpers_regex.go rename internal/provider/{v1 => user}/data_user.go (92%) rename internal/provider/{v1 => user}/data_user_group.go (94%) rename internal/provider/{v1 => user}/resource_user.go (94%) rename internal/provider/{v1 => user}/resource_user_group.go (93%) delete mode 100644 internal/provider/v1/data_port_profile_test.go delete mode 100644 internal/provider/v1/data_user_group_test.go delete mode 100644 internal/provider/v1/diff_suppressions.go delete mode 100644 internal/provider/v1/provider.go delete mode 100644 internal/provider/v1/provider_test.go create mode 100644 internal/utils/attribute.go rename internal/{provider/v1 => utils}/importer.go (81%) create mode 100644 internal/utils/mac.go rename internal/{provider/v1 => utils}/port_range.go (84%) diff --git a/Makefile b/Makefile index 2c46989..48baba1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ TEST ?= ./... TESTARGS ?= TEST_COUNT ?= 1 -TEST_TIMEOUT ?= 10m +TEST_TIMEOUT ?= 20m .PHONY: default default: build diff --git a/examples/resources/unifi_dns_record/resource.tf b/examples/resources/unifi_dns_record/resource.tf new file mode 100644 index 0000000..6101654 --- /dev/null +++ b/examples/resources/unifi_dns_record/resource.tf @@ -0,0 +1,11 @@ +resource "unifi_dns_record" "a_record" { + name = "example.mydomain.com" + type = "A" + record = "192.168.1.190" +} + +resource "unifi_dns_record" "cname_record" { + name = "example.mydomain.com" + type = "CNAME" + record = "example.com" +} diff --git a/go.mod b/go.mod index 2ec9878..9291f25 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,11 @@ require ( github.com/golangci/golangci-lint v1.64.5 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/terraform-plugin-docs v0.20.1 + github.com/hashicorp/terraform-plugin-framework v1.14.1 + github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 + github.com/hashicorp/terraform-plugin-go v0.26.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-mux v0.18.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 github.com/hashicorp/terraform-plugin-testing v1.11.0 github.com/testcontainers/testcontainers-go v0.35.0 @@ -34,7 +39,7 @@ require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect @@ -193,11 +198,6 @@ 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 @@ -229,7 +229,7 @@ require ( github.com/ldez/usetesting v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect @@ -297,7 +297,7 @@ require ( github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect + github.com/rogpeppe/go-internal v1.14.0 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect @@ -388,12 +388,12 @@ require ( go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.34.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/exp/typeparams v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect @@ -401,8 +401,8 @@ require ( golang.org/x/time v0.10.0 // indirect golang.org/x/tools v0.30.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 // indirect google.golang.org/grpc v1.70.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect @@ -423,5 +423,5 @@ require ( sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect - tags.cncf.io/container-device-interface v0.8.0 // indirect + tags.cncf.io/container-device-interface v0.8.1 // indirect ) diff --git a/go.sum b/go.sum index 8d6b33c..1378cf2 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVU github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -411,7 +411,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -599,8 +598,8 @@ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjS github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb h1:YU0XAr3+rMpM8fP80KEesn32Qa9qkbquokvuwzWyYuA= +github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -806,8 +805,8 @@ github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtz github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM= -github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= +github.com/rogpeppe/go-internal v1.14.0 h1:unbRd941gNa8SS77YznHXOYVBDgWcF9xhzECdm8juZc= +github.com/rogpeppe/go-internal v1.14.0/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= @@ -1064,8 +1063,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= -golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -1106,8 +1105,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1218,10 +1217,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 h1:35ZFtrCgaAjF7AFAK0+lRSf+4AyYnWRbH7og13p7rZ4= -google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:W9ynFDP/shebLB1Hl/ESTOap2jHd6pmLXPNZC7SVDbA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 h1:ilJhrCga0AptpJZXmUYG4MCrx/zf3l1okuYz7YK9PPw= +google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 h1:ZSlhAUqC4r8TPzqLXQ0m3upBNZeF+Y8jQ3c4CR3Ujms= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= @@ -1288,7 +1287,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -tags.cncf.io/container-device-interface v0.8.0 h1:8bCFo/g9WODjWx3m6EYl3GfUG31eKJbaggyBDxEldRc= -tags.cncf.io/container-device-interface v0.8.0/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y= +tags.cncf.io/container-device-interface v0.8.1 h1:c0jN4Mt6781jD67NdPajmZlD1qrqQyov/Xfoab37lj0= +tags.cncf.io/container-device-interface v0.8.1/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y= tags.cncf.io/container-device-interface/specs-go v0.8.0 h1:QYGFzGxvYK/ZLMrjhvY0RjpUavIn4KcmRmVP/JjdBTA= tags.cncf.io/container-device-interface/specs-go v0.8.0/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws= diff --git a/internal/provider/v1/controller_versions_test.go b/internal/provider/acctest/controller_versions_test.go similarity index 81% rename from internal/provider/v1/controller_versions_test.go rename to internal/provider/acctest/controller_versions_test.go index 382ebe5..291c204 100644 --- a/internal/provider/v1/controller_versions_test.go +++ b/internal/provider/acctest/controller_versions_test.go @@ -1,4 +1,4 @@ -package v1 +package acctest import ( "testing" @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/go-version" ) -func preCheckMinVersion(t *testing.T, min *version.Version) { +func PreCheckMinVersion(t *testing.T, min *version.Version) { v, err := version.NewVersion(testClient.Version()) if err != nil { t.Fatalf("error parsing version: %s", err) @@ -16,7 +16,7 @@ func preCheckMinVersion(t *testing.T, min *version.Version) { } } -func preCheckVersionConstraint(t *testing.T, cs string) { +func PreCheckVersionConstraint(t *testing.T, cs string) { v, err := version.NewVersion(testClient.Version()) if err != nil { t.Fatalf("Error parsing version: %s", err) diff --git a/internal/provider/v1/data_account_test.go b/internal/provider/acctest/data_account_test.go similarity index 73% rename from internal/provider/v1/data_account_test.go rename to internal/provider/acctest/data_account_test.go index c1cf114..eb460f8 100644 --- a/internal/provider/v1/data_account_test.go +++ b/internal/provider/acctest/data_account_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "testing" @@ -9,11 +10,7 @@ import ( func TestAccDataAccount_default(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -25,15 +22,10 @@ func TestAccDataAccount_default(t *testing.T) { } func TestAccDataAccount_mac(t *testing.T) { - mac, unallocateMac := allocateTestMac(t) + mac, unallocateMac := pt.AllocateTestMac(t) defer unallocateMac() - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, - // TODO: CheckDestroy: , + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccDataAccountConfig(mac, mac), diff --git a/internal/provider/v1/data_ap_group_test.go b/internal/provider/acctest/data_ap_group_test.go similarity index 71% rename from internal/provider/v1/data_ap_group_test.go rename to internal/provider/acctest/data_ap_group_test.go index 2c977a3..73f62d2 100644 --- a/internal/provider/v1/data_ap_group_test.go +++ b/internal/provider/acctest/data_ap_group_test.go @@ -1,18 +1,12 @@ -package v1 +package acctest import ( - "testing" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "testing" ) func TestAccDataAPGroup_default(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, - // TODO: CheckDestroy: , + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccDataAPGroupConfig_default, diff --git a/internal/provider/v1/data_network_test.go b/internal/provider/acctest/data_network_test.go similarity index 80% rename from internal/provider/v1/data_network_test.go rename to internal/provider/acctest/data_network_test.go index 211fbc7..05cf5d0 100644 --- a/internal/provider/v1/data_network_test.go +++ b/internal/provider/acctest/data_network_test.go @@ -1,12 +1,11 @@ -package v1 +package acctest import ( "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - "testing" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "testing" ) func TestAccDataNetwork_byName(t *testing.T) { @@ -15,15 +14,10 @@ func TestAccDataNetwork_byName(t *testing.T) { if err != nil { t.Fatalf("error parsing version: %s", err) } - if v.LessThan(provider.ControllerV7) { + if v.LessThan(base.ControllerV7) { defaultName = "LAN" } - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -42,15 +36,11 @@ func TestAccDataNetwork_byID(t *testing.T) { if err != nil { t.Fatalf("error parsing version: %s", err) } - if v.LessThan(provider.ControllerV7) { + if v.LessThan(base.ControllerV7) { defaultName = "LAN" } - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { diff --git a/internal/provider/acctest/data_port_profile_test.go b/internal/provider/acctest/data_port_profile_test.go new file mode 100644 index 0000000..24c97d0 --- /dev/null +++ b/internal/provider/acctest/data_port_profile_test.go @@ -0,0 +1,25 @@ +package acctest + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataPortProfile_default(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: "< 7.4", + // TODO: CheckDestroy: , + Steps: []resource.TestStep{ + { + Config: testAccDataPortProfileConfig_default, + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +const testAccDataPortProfileConfig_default = ` +data "unifi_port_profile" "default" { +} +` diff --git a/internal/provider/acctest/data_user_group_test.go b/internal/provider/acctest/data_user_group_test.go new file mode 100644 index 0000000..34d5ce6 --- /dev/null +++ b/internal/provider/acctest/data_user_group_test.go @@ -0,0 +1,26 @@ +package acctest + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataUserGroup_default(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + // TODO: CheckDestroy: , + Steps: []resource.TestStep{ + { + Config: testAccDataUserGroupConfig_default, + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + }, + }) +} + +const testAccDataUserGroupConfig_default = ` +data "unifi_user_group" "default" { +} +` diff --git a/internal/provider/v1/data_user_test.go b/internal/provider/acctest/data_user_test.go similarity index 79% rename from internal/provider/v1/data_user_test.go rename to internal/provider/acctest/data_user_test.go index d94bd21..eddaf07 100644 --- a/internal/provider/v1/data_user_test.go +++ b/internal/provider/acctest/data_user_test.go @@ -1,4 +1,4 @@ -package v1 +package acctest import ( "context" @@ -7,18 +7,17 @@ import ( "testing" "github.com/filipowm/go-unifi/unifi" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataUser_default(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ + AcceptanceTest(t, AcceptanceTestCase{ PreCheck: func() { - //preCheck(t) - _, err := testClient.CreateUser(context.Background(), "default", &unifi.User{ MAC: mac, Name: name, @@ -28,8 +27,6 @@ func TestAccDataUser_default(t *testing.T) { t.Fatal(err) } }, - //PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: testAccDataUserConfig_default(mac), diff --git a/internal/provider/acctest/datasource_dns_record_test.go b/internal/provider/acctest/datasource_dns_record_test.go new file mode 100644 index 0000000..28d230a --- /dev/null +++ b/internal/provider/acctest/datasource_dns_record_test.go @@ -0,0 +1,106 @@ +package acctest + +import ( + "fmt" + "regexp" + "testing" + + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const testDnsRecordDataSourceName = "data.unifi_dns_record.test" + +func TestDNSRecordDataSource_basic(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + record string + recordType string + filterByName bool + }{ + { + name: "filter by name", + record: "192.168.1.100", + recordType: "A", + filterByName: true, + }, + { + name: "filter by record", + record: "192.168.1.200", + recordType: "A", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + recordName := pt.RandHostname() + r := dnsRecordTestCase{ + recordName: recordName, + record: tc.record, + recordType: tc.recordType, + } + + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + + Steps: Steps{ + { + Config: pt.ComposeConfig(testAccDnsRecordConfig(r), testAccDnsRecordDataSourceConfig(r, tc.filterByName)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testDnsRecordDataSourceName, "name", recordName), + resource.TestCheckResourceAttr(testDnsRecordDataSourceName, "record", tc.record), + resource.TestCheckResourceAttr(testDnsRecordDataSourceName, "type", tc.recordType), + ), + }, + }, + }) + }) + } +} + +var ( + dnsDataSourceFilterErrorRegex = regexp.MustCompile(`[filter.name,filter.record]`) +) + +func TestDNSRecordDataSource_failWithoutFilter(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + + Steps: Steps{ + { + Config: testAccDnsRecordDataSourceWithoutFilter(), + ExpectError: dnsDataSourceFilterErrorRegex, + }, + }, + }) +} + +func testAccDnsRecordDataSourceConfig(tc dnsRecordTestCase, filterByName bool) string { + filter := "" + if filterByName { + filter = "name = \"" + tc.recordName + "\"" + } else { + filter = "record = \"" + tc.record + "\"" + } + + return fmt.Sprintf(` +data "unifi_dns_record" "test" { + filter { + %s + } + depends_on = [unifi_dns_record.test] +}`, filter) +} + +func testAccDnsRecordDataSourceWithoutFilter() string { + return ` +data "unifi_dns_record" "test" { + filter { + + } +}` +} diff --git a/internal/provider/acctest/datasource_dns_records_test.go b/internal/provider/acctest/datasource_dns_records_test.go new file mode 100644 index 0000000..221c3f3 --- /dev/null +++ b/internal/provider/acctest/datasource_dns_records_test.go @@ -0,0 +1,88 @@ +package acctest + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "strings" + "testing" + + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const testDnsRecordsDataSourceName = "data.unifi_dns_records.test" + +func TestDNSRecordsDataSource_basic(t *testing.T) { + records := []*dnsRecordTestCase{ + { + name: "test1", + record: "192.168.1.100", + recordType: "A", + }, + { + name: "test2", + record: "192.168.1.200", + recordType: "A", + }, + { + name: "mail", + record: "mail.example.com", + recordType: "MX", + priority: intPtr(10), + }, + } + + var configs []string + var dependencies []string + for _, record := range records { + record.recordName = pt.RandHostname() + resourceName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + configs = append(configs, testAccDnsRecordConfigWithResourceName(resourceName, *record)) + dependencies = append(dependencies, fmt.Sprintf("unifi_dns_record.%s", resourceName)) + } + configs = append(configs, testAccDnsRecordsDataSourceConfig(dependencies)) + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + Steps: Steps{ + { + Config: pt.ComposeConfig(configs...), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testDnsRecordsDataSourceName, "result.#", "3"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.0.name"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.0.record"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.0.type"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.1.name"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.1.record"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.1.type"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.2.name"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.2.record"), + resource.TestCheckResourceAttrSet(testDnsRecordsDataSourceName, "result.2.type"), + ), + }, + }, + }) +} + +func TestDNSRecordsDataSource_noRecords(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + Steps: Steps{ + { + Config: testAccDnsRecordsDataSourceConfig(nil), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testDnsRecordsDataSourceName, "result.#", "0"), + ), + }, + }, + }) +} + +func testAccDnsRecordsDataSourceConfig(deps []string) string { + return ` +data "unifi_dns_records" "test" { + depends_on = [ + ` + strings.Join(deps, ",") + ` + ] +}` +} diff --git a/internal/provider/acctest/provider_test.go b/internal/provider/acctest/provider_test.go new file mode 100644 index 0000000..953eb32 --- /dev/null +++ b/internal/provider/acctest/provider_test.go @@ -0,0 +1,94 @@ +package acctest + +import ( + "context" + "fmt" + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/go-version" + "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-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "os" + "testing" +) + +type providersMap map[string]func() (tfprotov6.ProviderServer, error) + +var ( + providers = createProviders() + testClient unifi.Client +) + +type Steps []resource.TestStep + +type AcceptanceTestCase struct { + CheckDestroy resource.TestCheckFunc + VersionConstraint string + MinVersion *version.Version + PreCheck func() + Steps Steps +} + +func AcceptanceTest(t *testing.T, testCase AcceptanceTestCase) { + t.Helper() + if len(testCase.Steps) == 0 { + t.Fatal("missing test steps") + } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + pt.PreCheck(t) + if testCase.VersionConstraint != "" { + PreCheckVersionConstraint(t, testCase.VersionConstraint) + } + if testCase.MinVersion != nil { + PreCheckMinVersion(t, testCase.MinVersion) + } + if testCase.PreCheck != nil { + testCase.PreCheck() + } + }, + ProtoV6ProviderFactories: providers, + CheckDestroy: testCase.CheckDestroy, + Steps: testCase.Steps, + }) +} + +func TestMain(m *testing.M) { + providers = createProviders() + os.Exit(pt.Run(m, func(env *pt.TestEnvironment) { + testClient = env.Client + })) +} + +func createProviders() providersMap { + ctx := context.Background() + // Init mux servers + return map[string]func() (tfprotov6.ProviderServer, error){ + "unifi": func() (tfprotov6.ProviderServer, error) { + return tf6muxserver.NewMuxServer(ctx, + providerserver.NewProtocol6(provider.NewV2("acctestv2")()), + func() tfprotov6.ProviderServer { + sdkV2Provider, err := tf5to6server.UpgradeServer( + ctx, + func() tfprotov5.ProviderServer { + return schema.NewGRPCProviderServer( + provider.New("acctestv1")(), + ) + }, + ) + if err != nil { + panic(fmt.Errorf("failed to create test providers: %w", err)) + } + + return sdkV2Provider + }, + ) + }, + } +} diff --git a/internal/provider/v1/resource_account_test.go b/internal/provider/acctest/resource_account_test.go similarity index 74% rename from internal/provider/v1/resource_account_test.go rename to internal/provider/acctest/resource_account_test.go index e028e92..3f01985 100644 --- a/internal/provider/v1/resource_account_test.go +++ b/internal/provider/acctest/resource_account_test.go @@ -1,18 +1,17 @@ -package v1 +package acctest import ( "fmt" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "testing" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccAccount_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -22,17 +21,15 @@ func TestAccAccount_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_account.test", "name", name), ), }, - importStep("unifi_account.test"), + pt.ImportStep("unifi_account.test"), }, }) } func TestAccAccount_mac(t *testing.T) { - mac, unallocateMac := allocateTestMac(t) + mac, unallocateMac := pt.AllocateTestMac(t) defer unallocateMac() - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -43,7 +40,7 @@ func TestAccAccount_mac(t *testing.T) { resource.TestCheckResourceAttr("unifi_account.test", "password", mac), ), }, - importStep("unifi_account.test"), + pt.ImportStep("unifi_account.test"), }, }) } diff --git a/internal/provider/v1/resource_device_test.go b/internal/provider/acctest/resource_device_test.go similarity index 94% rename from internal/provider/v1/resource_device_test.go rename to internal/provider/acctest/resource_device_test.go index 271ae16..21fcbf1 100644 --- a/internal/provider/v1/resource_device_test.go +++ b/internal/provider/acctest/resource_device_test.go @@ -1,4 +1,4 @@ -package v1 +package acctest import ( "context" @@ -11,6 +11,7 @@ import ( mapset "github.com/deckarep/golang-set/v2" "github.com/filipowm/go-unifi/unifi" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -22,6 +23,7 @@ var ( ) func allocateDevice(t *testing.T) (*unifi.Device, func()) { + pt.MarkAccTest(t) ctx := context.Background() deviceInit.Do(func() { @@ -175,10 +177,8 @@ func preCheckDeviceExists(t *testing.T, site, mac string) { } func TestAccDevice_empty(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckDeviceDestroy, + AcceptanceTest(t, AcceptanceTestCase{ + CheckDestroy: testAccCheckDeviceDestroy, Steps: []resource.TestStep{ { Config: testAccDeviceConfigEmpty(), @@ -198,13 +198,11 @@ func TestAccDevice_switch_basic(t *testing.T) { importStateVerifyIgnore := []string{"allow_adoption", "forget_on_destroy", "name"} - resource.ParallelTest(t, resource.TestCase{ + AcceptanceTest(t, AcceptanceTestCase{ PreCheck: func() { - preCheck(t) preCheckDeviceExists(t, site, device.MAC) }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckDeviceDestroy, + CheckDestroy: testAccCheckDeviceDestroy, Steps: []resource.TestStep{ { Config: testAccDeviceConfig(device.MAC), @@ -253,14 +251,12 @@ func TestAccDevice_switch_portOverrides(t *testing.T) { device, unallocateDevice := allocateDevice(t) defer unallocateDevice() - resource.ParallelTest(t, resource.TestCase{ + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: "< 7.4", PreCheck: func() { - preCheck(t) preCheckDeviceExists(t, site, device.MAC) - preCheckVersionConstraint(t, "< 7.4") }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckDeviceDestroy, + CheckDestroy: testAccCheckDeviceDestroy, Steps: []resource.TestStep{ { Config: testAccDeviceConfig_withPortOverrides(device.MAC), diff --git a/internal/provider/acctest/resource_dns_record_test.go b/internal/provider/acctest/resource_dns_record_test.go new file mode 100644 index 0000000..5fb1589 --- /dev/null +++ b/internal/provider/acctest/resource_dns_record_test.go @@ -0,0 +1,318 @@ +package acctest + +import ( + "context" + "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "strconv" + "strings" + "testing" + + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const testDnsRecordResourceName = "unifi_dns_record.test" + +type dnsRecordTestCase struct { + name string + recordName string + record string + recordType string + ttl *int + enabled *bool + priority *int + port *int + weight *int +} + +func TestDNSRecord_basic(t *testing.T) { + t.Parallel() + testCases := []dnsRecordTestCase{ + { + name: "A record", + recordName: "test.com", + record: "192.168.0.128", + recordType: "A", + }, + { + name: "AAAA record", + recordName: "ipv6.test.com", + record: "2001:db8::1", + recordType: "AAAA", + }, + { + name: "CNAME record", + recordName: "alias.test.com", + record: "target.test.com", + recordType: "CNAME", + }, + { + name: "NS record", + recordName: "ns.test.com", + record: "127.0.0.1", + recordType: "NS", + }, + { + name: "MX record with priority", + recordName: "mail.test.com", + record: "mx.test.com", + recordType: "MX", + priority: intPtr(10), + }, + { + name: "disabled A record", + recordName: "disabled.test.com", + record: "192.168.1.100", + recordType: "A", + enabled: boolPtr(false), + }, + { + name: "A record with TTL", + recordName: "ttl.test.com", + record: "192.168.1.100", + recordType: "A", + ttl: intPtr(3600), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + steps := []resource.TestStep{ + { + Config: testAccDnsRecordConfig(tc), + Check: testAccDnsRecordCheckAttrs(tc), + }, + pt.ImportStep(testDnsRecordResourceName), + } + + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + Steps: steps, + CheckDestroy: testAccCheckDNSRecordDestroy, + }) + }) + } +} + +func TestDNSRecord_SRV(t *testing.T) { + t.Parallel() + testCases := []dnsRecordTestCase{ + { + name: "SRV record with all fields", + recordName: "_sip._tcp.test.com", + record: "sip.test.com", + recordType: "SRV", + port: intPtr(5060), + priority: intPtr(10), + weight: intPtr(20), + }, + { + name: "SRV record with minimal fields", + recordName: "_ldap._tcp.test.com", + record: "ldap.test.com", + recordType: "SRV", + port: intPtr(389), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + CheckDestroy: testAccCheckDNSRecordDestroy, + Steps: Steps{ + { + Config: testAccDnsRecordConfig(tc), + Check: testAccDnsRecordCheckAttrs(tc), + }, + }, + }) + }) + } +} + +func TestDNSRecord_Update(t *testing.T) { + initial := dnsRecordTestCase{ + name: "initial", + recordName: "update.test.com", + record: "192.168.1.100", + recordType: "A", + ttl: intPtr(3600), + } + + updated := dnsRecordTestCase{ + name: "updated", + recordName: "update.test.com", + record: "192.168.1.200", + recordType: "A", + ttl: intPtr(7200), + } + + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + CheckDestroy: testAccCheckDNSRecordDestroy, + Steps: Steps{ + { + Config: testAccDnsRecordConfig(initial), + Check: testAccDnsRecordCheckAttrs(initial), + }, + { + Config: testAccDnsRecordConfig(updated), + Check: testAccDnsRecordCheckAttrs(updated), + ConfigPlanChecks: pt.CheckResourceAction(testDnsRecordResourceName, plancheck.ResourceActionUpdate), + }, + }, + }) +} + +func TestDNSRecord_MissingAttributes(t *testing.T) { + t.Parallel() + testCases := map[string]func() string{ + "name": testAccDnsRecordConfigMissingName, + "record": testAccDnsRecordConfigMissingRecord, + "type": testAccDnsRecordConfigMissingType, + } + for k, v := range testCases { + t.Run(fmt.Sprintf("missing %s", k), func(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionDnsRecords, + Steps: Steps{ + { + Config: v(), + ExpectError: pt.MissingArgumentErrorRegex(k), + }, + }, + }) + }) + } +} + +func testAccCheckDNSRecordDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "unifi_dns_record" { + continue + } + + _, err := testClient.GetDNSRecord(context.Background(), "default", rs.Primary.ID) + if err == nil { + return fmt.Errorf("DNS Record %s still exists", rs.Primary.ID) + } + // If we get a 404 error, that means the resource was deleted + if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { + continue + } + // For any other error, return it + return err + } + + return nil +} + +func testAccDnsRecordConfig(tc dnsRecordTestCase) string { + return testAccDnsRecordConfigWithResourceName("test", tc) +} + +func testAccDnsRecordConfigMissingName() string { + return ` +resource "unifi_dns_record" "test" { + record = "127.0.0.1" + type = "A" +} +` +} + +func testAccDnsRecordConfigMissingRecord() string { + return ` +resource "unifi_dns_record" "test" { + name = "test.com" + type = "A" +} +` +} + +func testAccDnsRecordConfigMissingType() string { + return ` +resource "unifi_dns_record" "test" { + name = "test.com" + record = "127.0.0.1" +} +` +} + +func testAccDnsRecordConfigWithResourceName(resourceName string, tc dnsRecordTestCase) string { + var attrs string + + if tc.ttl != nil { + attrs += fmt.Sprintf("\tttl = %d\n", *tc.ttl) + } + if tc.enabled != nil { + attrs += fmt.Sprintf("\tenabled = %t\n", *tc.enabled) + } + if tc.priority != nil { + attrs += fmt.Sprintf("\tpriority = %d\n", *tc.priority) + } + if tc.port != nil { + attrs += fmt.Sprintf("\tport = %d\n", *tc.port) + } + if tc.weight != nil { + attrs += fmt.Sprintf("\tweight = %d\n", *tc.weight) + } + + return fmt.Sprintf(` +resource "unifi_dns_record" "%s" { + name = "%s" + record = "%s" + type = "%s" +%s} +`, resourceName, tc.recordName, tc.record, tc.recordType, attrs) +} + +func testAccDnsRecordCheckAttrs(tc dnsRecordTestCase) resource.TestCheckFunc { + // expected default values + var ( + ttl = 0 + enabled = true + priority = 0 + port = 0 + weight = 0 + ) + + if tc.ttl != nil { + ttl = *tc.ttl + } + if tc.enabled != nil { + enabled = *tc.enabled + } + if tc.priority != nil { + priority = *tc.priority + } + if tc.port != nil { + port = *tc.port + } + if tc.weight != nil { + weight = *tc.weight + } + + checks := []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(testDnsRecordResourceName, "name", tc.recordName), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "record", tc.record), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "type", tc.recordType), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "ttl", strconv.Itoa(ttl)), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "enabled", strconv.FormatBool(enabled)), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "priority", strconv.Itoa(priority)), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "port", strconv.Itoa(port)), + resource.TestCheckResourceAttr(testDnsRecordResourceName, "weight", strconv.Itoa(weight)), + } + return resource.ComposeTestCheckFunc(checks...) +} + +func intPtr(i int) *int { + return &i +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/internal/provider/v1/resource_dynamic_dns_test.go b/internal/provider/acctest/resource_dynamic_dns_test.go similarity index 75% rename from internal/provider/v1/resource_dynamic_dns_test.go rename to internal/provider/acctest/resource_dynamic_dns_test.go index 9ae47c2..c47dc7f 100644 --- a/internal/provider/v1/resource_dynamic_dns_test.go +++ b/internal/provider/acctest/resource_dynamic_dns_test.go @@ -1,15 +1,14 @@ -package v1 +package acctest import ( "testing" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDynamicDNS_dyndns(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -18,7 +17,7 @@ func TestAccDynamicDNS_dyndns(t *testing.T) { // // testCheckFirewallGroupExists(t, "name"), // ), }, - importStep("unifi_dynamic_dns.test"), + pt.ImportStep("unifi_dynamic_dns.test"), }, }) } diff --git a/internal/provider/v1/resource_firewall_group_test.go b/internal/provider/acctest/resource_firewall_group_test.go similarity index 76% rename from internal/provider/v1/resource_firewall_group_test.go rename to internal/provider/acctest/resource_firewall_group_test.go index ec5f31c..248da68 100644 --- a/internal/provider/v1/resource_firewall_group_test.go +++ b/internal/provider/acctest/resource_firewall_group_test.go @@ -1,4 +1,4 @@ -package v1 +package acctest import ( "fmt" @@ -7,14 +7,13 @@ import ( "strings" "testing" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccFirewallGroup_port_group(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -23,20 +22,18 @@ func TestAccFirewallGroup_port_group(t *testing.T) { // // testCheckFirewallGroupExists(t, "name"), // ), }, - importStep("unifi_firewall_group.test"), + pt.ImportStep("unifi_firewall_group.test"), { Config: testAccFirewallGroupConfig(name, "port-group", []string{"80", "443"}), }, - importStep("unifi_firewall_group.test"), + pt.ImportStep("unifi_firewall_group.test"), }, }) } func TestAccFirewallGroup_address_group(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -45,23 +42,21 @@ func TestAccFirewallGroup_address_group(t *testing.T) { // // testCheckFirewallGroupExists(t, "name"), // ), }, - importStep("unifi_firewall_group.test"), + pt.ImportStep("unifi_firewall_group.test"), { Config: testAccFirewallGroupConfig(name, "address-group", []string{"10.0.0.1", "10.0.0.2"}), }, - importStep("unifi_firewall_group.test"), + pt.ImportStep("unifi_firewall_group.test"), { Config: testAccFirewallGroupConfig(name, "address-group", []string{"10.0.0.0/24"}), }, - importStep("unifi_firewall_group.test"), + pt.ImportStep("unifi_firewall_group.test"), }, }) } func TestAccFirewallGroup_same_name(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { diff --git a/internal/provider/v1/resource_firewall_rule_test.go b/internal/provider/acctest/resource_firewall_rule_test.go similarity index 83% rename from internal/provider/v1/resource_firewall_rule_test.go rename to internal/provider/acctest/resource_firewall_rule_test.go index fff6bd6..f535943 100644 --- a/internal/provider/v1/resource_firewall_rule_test.go +++ b/internal/provider/acctest/resource_firewall_rule_test.go @@ -1,10 +1,11 @@ -package v1 +package acctest import ( "fmt" "regexp" "testing" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -12,9 +13,7 @@ import ( func TestAccFirewallRule_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -24,14 +23,14 @@ func TestAccFirewallRule_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_firewall_rule.test", "enabled", "true"), ), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), { Config: testAccFirewallRuleConfig(name, false), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_firewall_rule.test", "enabled", "false"), ), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } @@ -39,9 +38,7 @@ func TestAccFirewallRule_basic(t *testing.T) { func TestAccFirewallRule_port(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccFirewallRuleConfigWithPort(name), @@ -50,7 +47,7 @@ func TestAccFirewallRule_port(t *testing.T) { resource.TestCheckResourceAttr("unifi_firewall_rule.test", "dst_port", "53"), ), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } @@ -58,14 +55,12 @@ func TestAccFirewallRule_port(t *testing.T) { func TestAccFirewallRule_icmp(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccFirewallRuleConfigWithICMP(name), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } @@ -73,9 +68,7 @@ func TestAccFirewallRule_icmp(t *testing.T) { func TestAccFirewallRule_multiple_address_groups(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -89,9 +82,7 @@ func TestAccFirewallRule_multiple_address_groups(t *testing.T) { func TestAccFirewallRule_multiple_port_groups(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -105,9 +96,7 @@ func TestAccFirewallRule_multiple_port_groups(t *testing.T) { func TestAccFirewallRule_address_and_port_group(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -116,7 +105,7 @@ func TestAccFirewallRule_address_and_port_group(t *testing.T) { // // testCheckFirewallGroupExists(t, "name"), // ), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } @@ -124,15 +113,13 @@ func TestAccFirewallRule_address_and_port_group(t *testing.T) { func TestAccFirewallRule_IPv6_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { Config: testAccFirewallRuleConfigIPv6(name), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } @@ -140,14 +127,12 @@ func TestAccFirewallRule_IPv6_basic(t *testing.T) { func TestAccFirewallRule_IPv6_dst_port(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccFirewallRuleConfigIPv6WithPort(name), }, - importStep("unifi_firewall_rule.test"), + pt.ImportStep("unifi_firewall_rule.test"), }, }) } diff --git a/internal/provider/v1/resource_network_test.go b/internal/provider/acctest/resource_network_test.go similarity index 86% rename from internal/provider/v1/resource_network_test.go rename to internal/provider/acctest/resource_network_test.go index ce457da..6ec0653 100644 --- a/internal/provider/v1/resource_network_test.go +++ b/internal/provider/acctest/resource_network_test.go @@ -1,8 +1,8 @@ -package v1 +package acctest import ( "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "net" "regexp" "strconv" @@ -15,12 +15,10 @@ import ( func TestAccNetwork_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet1, vlan1 := getTestVLAN(t) - subnet2, vlan2 := getTestVLAN(t) + subnet1, vlan1 := pt.GetTestVLAN(t) + subnet2, vlan2 := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -31,7 +29,7 @@ func TestAccNetwork_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "igmp_snooping", "true"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfig(name, subnet2, vlan2, false, nil), Check: resource.ComposeTestCheckFunc( @@ -39,12 +37,12 @@ func TestAccNetwork_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "igmp_snooping", "false"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), // re-test import here with default site, but full ID string { ResourceName: "unifi_network.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_network.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_network.test"), ImportStateVerify: true, }, }, @@ -53,11 +51,9 @@ func TestAccNetwork_basic(t *testing.T) { func TestAccNetwork_weird_cidr(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -66,18 +62,16 @@ func TestAccNetwork_weird_cidr(t *testing.T) { // TODO: ... ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), }, }) } func TestAccNetwork_dhcp_dns(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -86,7 +80,7 @@ func TestAccNetwork_dhcp_dns(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.0", "192.168.1.101"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfig(name, subnet, vlan, true, []string{"192.168.1.101", "192.168.1.102"}), Check: resource.ComposeTestCheckFunc( @@ -94,7 +88,7 @@ func TestAccNetwork_dhcp_dns(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "dhcp_dns.1", "192.168.1.102"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfig(name, subnet, vlan, true, nil), Check: resource.ComposeTestCheckFunc( @@ -113,11 +107,9 @@ func TestAccNetwork_dhcp_dns(t *testing.T) { func TestAccNetwork_dhcp_boot(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -126,7 +118,7 @@ func TestAccNetwork_dhcp_boot(t *testing.T) { // TODO: ... ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), }, }) } @@ -135,13 +127,11 @@ func TestAccNetwork_v6(t *testing.T) { t.Skip("FIXME") name := acctest.RandomWithPrefix("tfacc") - subnet1, vlan1 := getTestVLAN(t) - subnet2, vlan2 := getTestVLAN(t) - subnet3, vlan3 := getTestVLAN(t) + subnet1, vlan1 := pt.GetTestVLAN(t) + subnet2, vlan2 := pt.GetTestVLAN(t) + subnet3, vlan3 := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -152,7 +142,7 @@ func TestAccNetwork_v6(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "ipv6_static_subnet", "fd6a:37be:e362::1/64"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfigV6(name, subnet2, vlan2, "static", "fd6a:37be:e363::1/64"), Check: resource.ComposeTestCheckFunc( @@ -160,7 +150,7 @@ func TestAccNetwork_v6(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "ipv6_static_subnet", "fd6a:37be:e363::1/64"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfigDhcpV6( name, @@ -195,9 +185,7 @@ func TestAccNetwork_v6(t *testing.T) { func TestAccNetwork_wan(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -214,7 +202,7 @@ func TestAccNetwork_wan(t *testing.T) { resource.TestCheckOutput("wan_dns2", "4.4.4.4"), ), }, - importStep("unifi_network.wan_test"), + pt.ImportStep("unifi_network.wan_test"), // remove qos { Config: testWanNetworkConfig(name, "WAN", "pppoe", "192.168.1.1", 0, "username", "password", "8.8.8.8", "4.4.4.4"), @@ -230,7 +218,7 @@ func TestAccNetwork_wan(t *testing.T) { resource.TestCheckOutput("wan_dns2", "4.4.4.4"), ), }, - importStep("unifi_network.wan_test"), + pt.ImportStep("unifi_network.wan_test"), { Config: testWanNetworkConfig(name, "WAN", "pppoe", "192.168.1.1", 1, "username", "password", "8.8.8.8", "4.4.4.4"), Check: resource.ComposeTestCheckFunc( @@ -245,7 +233,7 @@ func TestAccNetwork_wan(t *testing.T) { resource.TestCheckOutput("wan_dns2", "4.4.4.4"), ), }, - importStep("unifi_network.wan_test"), + pt.ImportStep("unifi_network.wan_test"), { Config: testWanV6NetworkConfig(name, "dhcpv6", 47), ExpectError: regexp.MustCompile(regexp.QuoteMeta("expected wan_dhcp_v6_pd_size to be in the range (48 - 64)")), @@ -267,12 +255,10 @@ func TestAccNetwork_wan(t *testing.T) { func TestAccNetwork_differentSite(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet1, vlan1 := getTestVLAN(t) - subnet2, vlan2 := getTestVLAN(t) + subnet1, vlan1 := pt.GetTestVLAN(t) + subnet2, vlan2 := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -284,7 +270,7 @@ func TestAccNetwork_differentSite(t *testing.T) { { ResourceName: "unifi_network.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_network.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_network.test"), ImportStateVerify: true, }, { @@ -296,7 +282,7 @@ func TestAccNetwork_differentSite(t *testing.T) { { ResourceName: "unifi_network.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_network.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_network.test"), ImportStateVerify: true, }, }, @@ -305,13 +291,11 @@ func TestAccNetwork_differentSite(t *testing.T) { func TestAccNetwork_importByName(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet1, vlan1 := getTestVLAN(t) - subnet2, vlan2 := getTestVLAN(t) - subnet3, vlan3 := getTestVLAN(t) + subnet1, vlan1 := pt.GetTestVLAN(t) + subnet2, vlan2 := pt.GetTestVLAN(t) + subnet3, vlan3 := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ // Apply and import network by name. { @@ -352,13 +336,9 @@ func TestAccNetwork_importByName(t *testing.T) { func TestAccNetwork_dhcpRelay(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -367,27 +347,23 @@ func TestAccNetwork_dhcpRelay(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "dhcp_relay_enabled", "true"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfigDHCPRelay(name, subnet, vlan, false), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "dhcp_relay_enabled", "false"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), }, }) } func TestAccNetwork_vlanOnly(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - _, vlan := getTestVLAN(t) + _, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -399,7 +375,7 @@ func TestAccNetwork_vlanOnly(t *testing.T) { { ResourceName: "unifi_network.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_network.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_network.test"), ImportStateVerify: true, }, }, @@ -408,14 +384,10 @@ func TestAccNetwork_vlanOnly(t *testing.T) { func TestAccNetwork_mdns(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckMinVersion(t, provider.ControllerV7) - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: "> 7.0", // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -424,14 +396,14 @@ func TestAccNetwork_mdns(t *testing.T) { resource.TestCheckResourceAttr("unifi_network.test", "multicast_dns", "true"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), { Config: testAccNetworkConfigMDNS(name, subnet, vlan, false), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_network.test", "multicast_dns", "false"), ), }, - importStep("unifi_network.test"), + pt.ImportStep("unifi_network.test"), }, }) } diff --git a/internal/provider/v1/resource_port_forward_test.go b/internal/provider/acctest/resource_port_forward_test.go similarity index 81% rename from internal/provider/v1/resource_port_forward_test.go rename to internal/provider/acctest/resource_port_forward_test.go index 12d1201..fcf0906 100644 --- a/internal/provider/v1/resource_port_forward_test.go +++ b/internal/provider/acctest/resource_port_forward_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "testing" @@ -12,9 +13,7 @@ func TestAccPortForward_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") name2 := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -24,7 +23,7 @@ func TestAccPortForward_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"), ), }, - importStep("unifi_port_forward.test"), + pt.ImportStep("unifi_port_forward.test"), { Config: testAccPortForwardConfig("22", false, "10.1.1.2", "8022", name), Check: resource.ComposeTestCheckFunc( @@ -32,23 +31,21 @@ func TestAccPortForward_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_port_forward.test", "fwd_ip", "10.1.1.2"), ), }, - importStep("unifi_port_forward.test"), + pt.ImportStep("unifi_port_forward.test"), { Config: testAccPortForwardConfig("22", false, "10.1.1.1", "22", name2), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_port_forward.test", "name", name2), ), }, - importStep("unifi_port_forward.test"), + pt.ImportStep("unifi_port_forward.test"), }, }) } func TestAccPortForward_src_ip(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -58,16 +55,14 @@ func TestAccPortForward_src_ip(t *testing.T) { resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"), ), }, - importStep("unifi_port_forward.test"), + pt.ImportStep("unifi_port_forward.test"), }, }) } func TestAccPortForward_src_cidr(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -77,7 +72,7 @@ func TestAccPortForward_src_cidr(t *testing.T) { resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"), ), }, - importStep("unifi_port_forward.test"), + pt.ImportStep("unifi_port_forward.test"), }, }) } diff --git a/internal/provider/v1/resource_port_profile_test.go b/internal/provider/acctest/resource_port_profile_test.go similarity index 78% rename from internal/provider/v1/resource_port_profile_test.go rename to internal/provider/acctest/resource_port_profile_test.go index fe37895..61499bd 100644 --- a/internal/provider/v1/resource_port_profile_test.go +++ b/internal/provider/acctest/resource_port_profile_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "testing" @@ -10,12 +11,8 @@ import ( func TestAccPortProfile_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, "< 7.4") - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: "< 7.4", // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -25,7 +22,7 @@ func TestAccPortProfile_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_port_profile.test", "name", name), ), }, - importStep("unifi_port_profile.test"), + pt.ImportStep("unifi_port_profile.test"), }, }) } diff --git a/internal/provider/v1/resource_radius_profile_test.go b/internal/provider/acctest/resource_radius_profile_test.go similarity index 81% rename from internal/provider/v1/resource_radius_profile_test.go rename to internal/provider/acctest/resource_radius_profile_test.go index d0e8d6f..d5d5f36 100644 --- a/internal/provider/v1/resource_radius_profile_test.go +++ b/internal/provider/acctest/resource_radius_profile_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "testing" @@ -10,9 +11,7 @@ import ( func TestAccRadiusProfile_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -21,16 +20,14 @@ func TestAccRadiusProfile_basic(t *testing.T) { resource.TestCheckResourceAttr("unifi_radius_profile.test", "name", name), ), }, - importStep("unifi_radius_profile.test"), + pt.ImportStep("unifi_radius_profile.test"), }, }) } func TestAccRadiusProfile_servers(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -39,15 +36,13 @@ func TestAccRadiusProfile_servers(t *testing.T) { resource.TestCheckResourceAttr("unifi_radius_profile.test", "name", name), ), }, - importStep("unifi_radius_profile.test"), + pt.ImportStep("unifi_radius_profile.test"), }, }) } func TestAccRadiusProfile_importByName(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ // Apply and import network by name. { diff --git a/internal/provider/v1/resource_setting_mgmt_test.go b/internal/provider/acctest/resource_setting_mgmt_test.go similarity index 85% rename from internal/provider/v1/resource_setting_mgmt_test.go rename to internal/provider/acctest/resource_setting_mgmt_test.go index 7b68386..10655b9 100644 --- a/internal/provider/v1/resource_setting_mgmt_test.go +++ b/internal/provider/acctest/resource_setting_mgmt_test.go @@ -1,6 +1,7 @@ -package v1 +package acctest import ( + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "sync" "testing" @@ -12,19 +13,19 @@ var settingMgmtLock = sync.Mutex{} func TestAccSettingMgmt_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingMgmtLock.Lock() t.Cleanup(func() { settingMgmtLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_basic(), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_mgmt.test"), + pt.ImportStep("unifi_setting_mgmt.test"), }, }) } @@ -32,13 +33,13 @@ func TestAccSettingMgmt_basic(t *testing.T) { func TestAccSettingMgmt_site(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingMgmtLock.Lock() t.Cleanup(func() { settingMgmtLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_site(), @@ -47,7 +48,7 @@ func TestAccSettingMgmt_site(t *testing.T) { { ResourceName: "unifi_setting_mgmt.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_mgmt.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_setting_mgmt.test"), ImportStateVerify: true, }, }, @@ -57,13 +58,13 @@ func TestAccSettingMgmt_site(t *testing.T) { func TestAccSettingMgmt_sshKeys(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingMgmtLock.Lock() t.Cleanup(func() { settingMgmtLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_sshKeys(), @@ -72,7 +73,7 @@ func TestAccSettingMgmt_sshKeys(t *testing.T) { { ResourceName: "unifi_setting_mgmt.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_mgmt.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_setting_mgmt.test"), ImportStateVerify: true, }, }, diff --git a/internal/provider/v1/resource_setting_radius_test.go b/internal/provider/acctest/resource_setting_radius_test.go similarity index 81% rename from internal/provider/v1/resource_setting_radius_test.go rename to internal/provider/acctest/resource_setting_radius_test.go index 97e6aca..8d0171b 100644 --- a/internal/provider/v1/resource_setting_radius_test.go +++ b/internal/provider/acctest/resource_setting_radius_test.go @@ -1,6 +1,7 @@ -package v1 +package acctest import ( + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "sync" "testing" @@ -12,19 +13,19 @@ var settingRadiusLock = sync.Mutex{} func TestAccSettingRadius_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingRadiusLock.Lock() t.Cleanup(func() { settingRadiusLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingRadiusConfig_basic(), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_radius.test"), + pt.ImportStep("unifi_setting_radius.test"), }, }) } @@ -32,13 +33,13 @@ func TestAccSettingRadius_basic(t *testing.T) { func TestAccSettingRadius_site(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingRadiusLock.Lock() t.Cleanup(func() { settingRadiusLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingRadiusConfig_site(), @@ -47,7 +48,7 @@ func TestAccSettingRadius_site(t *testing.T) { { ResourceName: "unifi_setting_radius.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_radius.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_setting_radius.test"), ImportStateVerify: true, }, }, @@ -57,13 +58,13 @@ func TestAccSettingRadius_site(t *testing.T) { func TestAccSettingRadius_full(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingRadiusLock.Lock() t.Cleanup(func() { settingRadiusLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingRadiusConfig_full(), @@ -72,7 +73,7 @@ func TestAccSettingRadius_full(t *testing.T) { { ResourceName: "unifi_setting_radius.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_radius.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_setting_radius.test"), ImportStateVerify: true, }, }, @@ -82,19 +83,19 @@ func TestAccSettingRadius_full(t *testing.T) { func TestAccSettingRadius_vlan(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingRadiusLock.Lock() t.Cleanup(func() { settingRadiusLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingRadiusConfig_vlan(), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_radius.test"), + pt.ImportStep("unifi_setting_radius.test"), }, }) } diff --git a/internal/provider/v1/resource_setting_usg_test.go b/internal/provider/acctest/resource_setting_usg_test.go similarity index 79% rename from internal/provider/v1/resource_setting_usg_test.go rename to internal/provider/acctest/resource_setting_usg_test.go index 2ccaae1..3be8332 100644 --- a/internal/provider/v1/resource_setting_usg_test.go +++ b/internal/provider/acctest/resource_setting_usg_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "regexp" "sync" "testing" @@ -15,30 +16,30 @@ var settingUsgLock = sync.Mutex{} func TestAccSettingUsg_mdns_v6(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, "< 7") + pt.PreCheck(t) + PreCheckVersionConstraint(t, "< 7") settingUsgLock.Lock() t.Cleanup(func() { settingUsgLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingUsgConfig_mdns(true), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_usg.test"), + pt.ImportStep("unifi_setting_usg.test"), { Config: testAccSettingUsgConfig_mdns(false), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_usg.test"), + pt.ImportStep("unifi_setting_usg.test"), { Config: testAccSettingUsgConfig_mdns(true), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_usg.test"), + pt.ImportStep("unifi_setting_usg.test"), }, }) } @@ -46,14 +47,14 @@ func TestAccSettingUsg_mdns_v6(t *testing.T) { func TestAccSettingUsg_mdns_v7(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, ">= 7") + pt.PreCheck(t) + PreCheckVersionConstraint(t, ">= 7") settingUsgLock.Lock() t.Cleanup(func() { settingUsgLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingUsgConfig_mdns(true), @@ -66,19 +67,19 @@ func TestAccSettingUsg_mdns_v7(t *testing.T) { func TestAccSettingUsg_dhcpRelay(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingUsgLock.Lock() t.Cleanup(func() { settingUsgLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingUsgConfig_dhcpRelay(), Check: resource.ComposeTestCheckFunc(), }, - importStep("unifi_setting_usg.test"), + pt.ImportStep("unifi_setting_usg.test"), }, }) } @@ -86,13 +87,13 @@ func TestAccSettingUsg_dhcpRelay(t *testing.T) { func TestAccSettingUsg_site(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - preCheck(t) + pt.PreCheck(t) settingUsgLock.Lock() t.Cleanup(func() { settingUsgLock.Unlock() }) }, - ProviderFactories: providerFactories, + ProtoV6ProviderFactories: providers, Steps: []resource.TestStep{ { Config: testAccSettingUsgConfig_site(), @@ -101,7 +102,7 @@ func TestAccSettingUsg_site(t *testing.T) { { ResourceName: "unifi_setting_usg.test", ImportState: true, - ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_usg.test"), + ImportStateIdFunc: pt.SiteAndIDImportStateIDFunc("unifi_setting_usg.test"), ImportStateVerify: true, }, }, diff --git a/internal/provider/v1/resource_site_test.go b/internal/provider/acctest/resource_site_test.go similarity index 89% rename from internal/provider/v1/resource_site_test.go rename to internal/provider/acctest/resource_site_test.go index 82e2265..a953683 100644 --- a/internal/provider/v1/resource_site_test.go +++ b/internal/provider/acctest/resource_site_test.go @@ -1,8 +1,9 @@ -package v1 +package acctest import ( "context" "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "strings" "testing" @@ -13,9 +14,7 @@ import ( func TestAccSite_basic(t *testing.T) { var siteName string - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // FIXME causes flaky tests. See: https://github.com/paultyng/terraform-provider-unifi/issues/480 //CheckDestroy: testAccCheckSiteResourceDestroy, Steps: []resource.TestStep{ @@ -31,14 +30,14 @@ func TestAccSite_basic(t *testing.T) { }, ), }, - importStep("unifi_site.test"), + pt.ImportStep("unifi_site.test"), { Config: testAccSiteConfig("tfacc-desc2"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("unifi_site.test", "description", "tfacc-desc2"), ), }, - importStep("unifi_site.test"), + pt.ImportStep("unifi_site.test"), // test importing from name, not id { diff --git a/internal/provider/v1/resource_static_route_test.go b/internal/provider/acctest/resource_static_route_test.go similarity index 86% rename from internal/provider/v1/resource_static_route_test.go rename to internal/provider/acctest/resource_static_route_test.go index f42afb4..0b76e53 100644 --- a/internal/provider/v1/resource_static_route_test.go +++ b/internal/provider/acctest/resource_static_route_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "net" "strconv" "testing" @@ -19,9 +20,7 @@ func TestAccStaticRoute_nextHop(t *testing.T) { distance := 1 nextHop := net.IPv4(172, 16, 0, 1).To4() - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -34,7 +33,7 @@ func TestAccStaticRoute_nextHop(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "next_hop", nextHop.String()), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } @@ -48,9 +47,7 @@ func TestAccStaticRoute_nextHop_ipv6(t *testing.T) { distance := 1 nextHop := net.IP{0xfd, 0x6a, 0x37, 0xbe, 0xe3, 0x62, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -63,7 +60,7 @@ func TestAccStaticRoute_nextHop_ipv6(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "next_hop", nextHop.String()), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } @@ -76,9 +73,7 @@ func TestAccStaticRoute_blackhole(t *testing.T) { } distance := 1 - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -90,7 +85,7 @@ func TestAccStaticRoute_blackhole(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "distance", strconv.Itoa(distance)), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } @@ -103,9 +98,7 @@ func TestAccStaticRoute_blackhole_ipv6(t *testing.T) { } distance := 1 - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -117,7 +110,7 @@ func TestAccStaticRoute_blackhole_ipv6(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "distance", strconv.Itoa(distance)), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } @@ -131,9 +124,7 @@ func TestAccStaticRoute_interface(t *testing.T) { distance := 1 networkInterface := "WAN2" - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -146,7 +137,7 @@ func TestAccStaticRoute_interface(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "interface", networkInterface), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } @@ -160,9 +151,7 @@ func TestAccStaticRoute_interface_ipv6(t *testing.T) { distance := 1 networkInterface := "WAN2" - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -175,7 +164,7 @@ func TestAccStaticRoute_interface_ipv6(t *testing.T) { resource.TestCheckResourceAttr("unifi_static_route.test", "interface", networkInterface), ), }, - importStep("unifi_static_route.test"), + pt.ImportStep("unifi_static_route.test"), }, }) } diff --git a/internal/provider/v1/resource_user_group_test.go b/internal/provider/acctest/resource_user_group_test.go similarity index 80% rename from internal/provider/v1/resource_user_group_test.go rename to internal/provider/acctest/resource_user_group_test.go index e1b611f..ed4a86a 100644 --- a/internal/provider/v1/resource_user_group_test.go +++ b/internal/provider/acctest/resource_user_group_test.go @@ -1,7 +1,8 @@ -package v1 +package acctest import ( "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "testing" @@ -10,9 +11,7 @@ import ( func TestAccUserGroup_basic(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -24,11 +23,11 @@ func TestAccUserGroup_basic(t *testing.T) { { Config: testAccUserGroupConfig_qos(name), }, - importStep("unifi_user_group.test"), + pt.ImportStep("unifi_user_group.test"), { Config: testAccUserGroupConfig(name), }, - importStep("unifi_user_group.test"), + pt.ImportStep("unifi_user_group.test"), }, }) } diff --git a/internal/provider/v1/resource_user_test.go b/internal/provider/acctest/resource_user_test.go similarity index 86% rename from internal/provider/v1/resource_user_test.go rename to internal/provider/acctest/resource_user_test.go index 84caee0..ca3acb1 100644 --- a/internal/provider/v1/resource_user_test.go +++ b/internal/provider/acctest/resource_user_test.go @@ -1,9 +1,10 @@ -package v1 +package acctest import ( "context" "errors" "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "net" "regexp" @@ -17,16 +18,14 @@ import ( ) func userImportStep(name string) resource.TestStep { - return importStep(name, "allow_existing", "skip_forget_on_destroy") + return pt.ImportStep(name, "allow_existing", "skip_forget_on_destroy") } func TestAccUser_basic(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -56,19 +55,17 @@ func TestAccUser_basic(t *testing.T) { } func TestAccUser_fixed_ip(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) ip, err := cidr.Host(subnet, 1) if err != nil { t.Error(err) } - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -103,13 +100,11 @@ func TestAccUser_fixed_ip(t *testing.T) { } func TestAccUser_blocking(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ // TODO: CheckDestroy: , Steps: []resource.TestStep{ { @@ -141,14 +136,12 @@ func TestAccUser_blocking(t *testing.T) { } func TestAccUser_existing_mac_allow(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ + AcceptanceTest(t, AcceptanceTestCase{ PreCheck: func() { - preCheck(t) - _, err := testClient.CreateUser(context.Background(), "default", &unifi.User{ MAC: mac, Name: name, @@ -158,10 +151,7 @@ func TestAccUser_existing_mac_allow(t *testing.T) { t.Fatal(err) } }, - ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { - // TODO: CheckDestroy: , - return testClient.DeleteUserByMAC(context.Background(), "default", mac) }, Steps: []resource.TestStep{ @@ -178,7 +168,7 @@ func TestAccUser_existing_mac_allow(t *testing.T) { } func TestAccUser_existing_mac_deny(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) name := acctest.RandomWithPrefix("tfacc") _, err := testClient.CreateUser(context.Background(), "default", &unifi.User{ @@ -199,9 +189,7 @@ func TestAccUser_existing_mac_deny(t *testing.T) { unallocateTestMac() }() - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccUserConfig_existing(mac, name, "tfacc note", false, false), @@ -212,13 +200,12 @@ func TestAccUser_existing_mac_deny(t *testing.T) { } func TestAccUser_fingerprint(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - resource.ParallelTest(t, resource.TestCase{ - ProviderFactories: providerFactories, - CheckDestroy: testCheckUserDestroy, + AcceptanceTest(t, AcceptanceTestCase{ + CheckDestroy: testCheckUserDestroy, Steps: []resource.TestStep{ { Config: testAccUserConfig_fingerprint(mac, name, 123), @@ -246,23 +233,19 @@ func TestAccUser_fingerprint(t *testing.T) { } func TestAccUser_localdns(t *testing.T) { - mac, unallocateTestMac := allocateTestMac(t) + mac, unallocateTestMac := pt.AllocateTestMac(t) defer unallocateTestMac() name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) ip, err := cidr.Host(subnet, 1) if err != nil { t.Error(err) } - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, ">= 7.2.91") - }, - ProviderFactories: providerFactories, + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: ">= 7.2.91", CheckDestroy: testCheckUserDestroy, Steps: []resource.TestStep{ { diff --git a/internal/provider/v1/resource_wlan_test.go b/internal/provider/acctest/resource_wlan_test.go similarity index 75% rename from internal/provider/v1/resource_wlan_test.go rename to internal/provider/acctest/resource_wlan_test.go index a328be6..e9ef717 100644 --- a/internal/provider/v1/resource_wlan_test.go +++ b/internal/provider/acctest/resource_wlan_test.go @@ -1,28 +1,21 @@ -package v1 +package acctest import ( "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "net" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccWLAN_wpapsk(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_wpapsk(name, subnet, vlan, "disabled"), @@ -30,23 +23,16 @@ func TestAccWLAN_wpapsk(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_open(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_open(name, subnet, vlan), @@ -54,37 +40,30 @@ func TestAccWLAN_open(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_open_mac_filter(name, subnet, vlan), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_open(name, subnet, vlan), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_change_security_and_pmf(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_wpapsk(name, subnet, vlan, "disabled"), @@ -92,51 +71,44 @@ func TestAccWLAN_change_security_and_pmf(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_open(name, subnet, vlan), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_wpapsk(name, subnet, vlan, "optional"), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_wpapsk(name, subnet, vlan, "required"), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_wpapsk(name, subnet, vlan, "disabled"), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_schedule(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_schedule(name, subnet, vlan), @@ -144,7 +116,7 @@ func TestAccWLAN_schedule(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), // remove schedule { Config: testAccWLANConfig_open(name, subnet, vlan), @@ -152,23 +124,16 @@ func TestAccWLAN_schedule(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_wpaeap(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_wpaeap(name, subnet, vlan), @@ -176,23 +141,16 @@ func TestAccWLAN_wpaeap(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_wlan_band(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_wlan_band(name, subnet, vlan), @@ -200,23 +158,16 @@ func TestAccWLAN_wlan_band(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_no2ghz_oui(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_no2ghz_oui(name, subnet, vlan), @@ -224,23 +175,16 @@ func TestAccWLAN_no2ghz_oui(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_proxy_arp(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_proxy_arp(name, subnet, vlan, true), @@ -248,23 +192,16 @@ func TestAccWLAN_proxy_arp(t *testing.T) { resource.TestCheckResourceAttr("unifi_wlan.test", "proxy_arp", "true"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_bss_transition(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_bss_transition(name, subnet, vlan, false), @@ -272,23 +209,16 @@ func TestAccWLAN_bss_transition(t *testing.T) { resource.TestCheckResourceAttr("unifi_wlan.test", "bss_transition", "false"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_uapsd(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_uapsd(name, subnet, vlan), @@ -296,23 +226,16 @@ func TestAccWLAN_uapsd(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_fast_roaming_enabled(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_fast_roaming_enabled(name, subnet, vlan, true), @@ -320,26 +243,17 @@ func TestAccWLAN_fast_roaming_enabled(t *testing.T) { resource.TestCheckResourceAttr("unifi_wlan.test", "fast_roaming_enabled", "true"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_wpa3(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckMinVersion(t, provider.ControllerVersionWPA3) - }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: base.ControllerVersionWPA3, Steps: []resource.TestStep{ { Config: testAccWLANConfig_wpa3(name, subnet, vlan, false, "required"), @@ -347,37 +261,30 @@ func TestAccWLAN_wpa3(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_wpa3(name, subnet, vlan, true, "optional"), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_wpa3(name, subnet, vlan, false, "required"), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } func TestAccWLAN_minimum_data_rate(t *testing.T) { name := acctest.RandomWithPrefix("tfacc") - subnet, vlan := getTestVLAN(t) + subnet, vlan := pt.GetTestVLAN(t) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: func(*terraform.State) error { - // TODO: actual CheckDestroy - - return nil - }, + AcceptanceTest(t, AcceptanceTestCase{ Steps: []resource.TestStep{ { Config: testAccWLANConfig_minimum_data_rate(name, subnet, vlan, 5500, 18000), @@ -385,35 +292,35 @@ func TestAccWLAN_minimum_data_rate(t *testing.T) { // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_minimum_data_rate(name, subnet, vlan, 1000, 18000), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_minimum_data_rate(name, subnet, vlan, 0, 0), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_minimum_data_rate(name, subnet, vlan, 6000, 9000), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), { Config: testAccWLANConfig_minimum_data_rate(name, subnet, vlan, 18000, 6000), Check: resource.ComposeTestCheckFunc( // testCheckNetworkExists(t, "name"), ), }, - importStep("unifi_wlan.test"), + pt.ImportStep("unifi_wlan.test"), }, }) } diff --git a/internal/provider/v1/data_ap_group.go b/internal/provider/apgroup/data_ap_group.go similarity index 87% rename from internal/provider/v1/data_ap_group.go rename to internal/provider/apgroup/data_ap_group.go index ece2922..7d522e9 100644 --- a/internal/provider/v1/data_ap_group.go +++ b/internal/provider/apgroup/data_ap_group.go @@ -1,18 +1,17 @@ -package v1 +package apgroup import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataAPGroup() *schema.Resource { +func DataAPGroup() *schema.Resource { return &schema.Resource{ Description: "`unifi_ap_group` data source can be used to retrieve the ID for an AP group by name.", - ReadContext: dataAPGroupRead, + ReadContext: DataAPGroupRead, Schema: map[string]*schema.Schema{ "id": { @@ -35,8 +34,8 @@ func dataAPGroup() *schema.Resource { } } -func dataAPGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) +func DataAPGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/base/base.go b/internal/provider/base/base.go new file mode 100644 index 0000000..bee7859 --- /dev/null +++ b/internal/provider/base/base.go @@ -0,0 +1,45 @@ +package base + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +type BaseData interface { + SetClient(client *Client) +} + +func ConfigureDatasource(base BaseData, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + cfg, ok := req.ProviderData.(*Client) + if !ok { + resp.Diagnostics.AddError("Unexpected Datasource Configure Type", fmt.Sprintf("Expected provider.Client, got: %T", req.ProviderData)) + return + } + if cfg == nil { + resp.Diagnostics.AddError("Empty configuration", "provider.Client is nil") + return + } + base.SetClient(cfg) +} + +func ConfigureResource(base BaseData, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + cfg, ok := req.ProviderData.(*Client) + if !ok { + resp.Diagnostics.AddError("Unexpected Resource Configure Type", fmt.Sprintf("Expected provider.Client, got: %T", req.ProviderData)) + return + } + if cfg == nil { + resp.Diagnostics.AddError("Empty configuration", "provider.Client is nil") + return + } + base.SetClient(cfg) +} diff --git a/internal/provider/client.go b/internal/provider/base/client.go similarity index 78% rename from internal/provider/client.go rename to internal/provider/base/client.go index 9672f38..937923e 100644 --- a/internal/provider/client.go +++ b/internal/provider/base/client.go @@ -1,13 +1,16 @@ -package provider +package base import ( + "crypto/tls" "errors" "fmt" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/go-version" "log" + "net" "net/http" "strings" + "time" ) func IsServerErrorContains(err error, messageContains string) bool { @@ -76,3 +79,21 @@ type Client struct { Site string Version *version.Version } + +func CreateHttpTransport(insecure bool) http.RoundTripper { + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecure, + }, + } +} diff --git a/internal/provider/controller_versions.go b/internal/provider/base/controller_versions.go similarity index 73% rename from internal/provider/controller_versions.go rename to internal/provider/base/controller_versions.go index e5c6d3e..1c9a5c0 100644 --- a/internal/provider/controller_versions.go +++ b/internal/provider/base/controller_versions.go @@ -1,8 +1,7 @@ -package provider +package base import ( "fmt" - "github.com/hashicorp/go-version" ) @@ -13,7 +12,10 @@ func asVersion(versionString string) *version.Version { var ( ControllerV6 = asVersion("6.0.0") ControllerV7 = asVersion("7.0.0") + ControllerV9 = asVersion("9.0.0") ControllerVersionApiKeyAuth = asVersion("9.0.108") + // https://community.ui.com/releases/UniFi-Network-Application-8-2-93/fce86dc6-897a-4944-9c53-1eec7e37e738 + ControllerVersionDnsRecords = asVersion("8.2.93") // https://community.ui.com/releases/UniFi-Network-Controller-6-1-61/62f1ad38-1ac5-430c-94b0-becbb8f71d7d ControllerVersionWPA3 = asVersion("6.1.61") @@ -27,6 +29,10 @@ func (c *Client) IsControllerV7() bool { return c.Version.GreaterThanOrEqual(ControllerV7) } +func (c *Client) IsControllerV9() bool { + return c.Version.GreaterThanOrEqual(ControllerV9) +} + func (c *Client) SupportsApiKeyAuthentication() bool { return c.Version.GreaterThanOrEqual(ControllerVersionApiKeyAuth) } @@ -35,6 +41,10 @@ func (c *Client) SupportsWPA3() bool { return c.Version.GreaterThanOrEqual(ControllerVersionWPA3) } +func (c *Client) SupportsDnsRecords() bool { + return c.Version.GreaterThanOrEqual(ControllerVersionDnsRecords) +} + func CheckMinimumControllerVersion(versionString string) error { v, err := version.NewVersion(versionString) if err != nil { diff --git a/internal/provider/v1/resource_device.go b/internal/provider/device/resource_device.go similarity index 95% rename from internal/provider/v1/resource_device.go rename to internal/provider/device/resource_device.go index df3c2ef..ea37af7 100644 --- a/internal/provider/v1/resource_device.go +++ b/internal/provider/device/resource_device.go @@ -1,10 +1,11 @@ -package v1 +package device import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "strconv" "strings" "time" @@ -16,7 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceDevice() *schema.Resource { +func ResourceDevice() *schema.Resource { return &schema.Resource{ Description: "`unifi_device` manages a device of the network.\n\n" + "Devices are adopted by the controller, so it is not possible for this resource to be created through " + @@ -50,8 +51,8 @@ func resourceDevice() *schema.Resource { Optional: true, Computed: true, ForceNew: true, - DiffSuppressFunc: macDiffSuppressFunc, - ValidateFunc: validation.StringMatch(macAddressRegexp, "Mac address is invalid"), + DiffSuppressFunc: utils.MacDiffSuppressFunc, + ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"), }, "name": { Description: "The name of the device.", @@ -139,7 +140,7 @@ func resourceDevice() *schema.Resource { } func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() site := d.Get("site").(string) if site == "" { @@ -152,9 +153,9 @@ func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta inte id = importParts[1] } - if macAddressRegexp.MatchString(id) { + if utils.MacAddressRegexp.MatchString(id) { // look up id by mac - mac := cleanMAC(id) + mac := utils.CleanMAC(id) device, err := c.GetDeviceByMAC(ctx, site, mac) if err != nil { @@ -175,7 +176,7 @@ func resourceDeviceImport(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -187,7 +188,7 @@ func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("no MAC address specified, please import the device using terraform import") } - mac = cleanMAC(mac) + mac = utils.CleanMAC(mac) device, err := c.GetDeviceByMAC(ctx, site, mac) if device == nil { @@ -218,7 +219,7 @@ func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -247,7 +248,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.(*provider.Client) + c := meta.(*base.Client) if !d.Get("forget_on_destroy").(bool) { return nil @@ -274,7 +275,7 @@ func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta inte } func resourceDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -389,7 +390,7 @@ 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.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) mac := d.Get("mac").(string) diff --git a/internal/provider/dns/datasource_dns_record.go b/internal/provider/dns/datasource_dns_record.go new file mode 100644 index 0000000..821f546 --- /dev/null +++ b/internal/provider/dns/datasource_dns_record.go @@ -0,0 +1,131 @@ +package dns + +import ( + "context" + "fmt" + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var ( + _ datasource.DataSource = &dnsRecordDatasource{} + _ datasource.DataSourceWithConfigure = &dnsRecordDatasource{} + _ base.BaseData = &dnsRecordDatasource{} + _ datasource.DataSourceWithConfigValidators = &dnsRecordDatasource{} +) + +type dnsRecordDatasource struct { + client *base.Client +} + +func NewDnsRecordDatasource() datasource.DataSource { + return &dnsRecordDatasource{} +} + +func (d *dnsRecordDatasource) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("filter").AtName("name"), + path.MatchRoot("filter").AtName("record"), + ), + } +} + +func (d *dnsRecordDatasource) SetClient(client *base.Client) { + d.client = client +} + +func (d *dnsRecordDatasource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + base.ConfigureDatasource(d, req, resp) +} + +func (d *dnsRecordDatasource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, resourceName) +} + +func (d *dnsRecordDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a specific DNS record.", + Attributes: dnsRecordDatasourceAttributes, + Blocks: map[string]schema.Block{ + "filter": schema.SingleNestedBlock{ + Description: "Filter to apply to the DNS record.", + Validators: []validator.Object{ + objectvalidator.IsRequired(), + }, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "DNS record name.", + Optional: true, + }, + "record": schema.StringAttribute{ + Description: "DNS record content.", + Optional: true, + }, + }, + }, + }, + } +} + +func (d *dnsRecordDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + if !d.client.SupportsDnsRecords() { + resp.Diagnostics.AddError("DNS Records are not supported", fmt.Sprintf("The Unifi controller in version %q does not support DNS records. Required controller version: %q", d.client.Version, base.ControllerVersionDnsRecords)) + } + var state dnsRecordDatasourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + if state.Filter == nil { + // TODO remove after testing validation + resp.Diagnostics.AddError("Filter is required", "Filter is required. Validation should prevent this from happening.") + return + } + list, err := d.client.ListDNSRecord(ctx, d.client.Site) + if err != nil { + resp.Diagnostics.AddError("Failed to list DNS records", err.Error()) + return + } + if len(list) == 0 { + resp.Diagnostics.AddError("DNS record not found", "No DNS record found") + return + } + var nameFilter, recordFilter string + if utils.IsStringValueNotEmpty(state.Filter.Name) { + nameFilter = state.Filter.Name.ValueString() + } + if utils.IsStringValueNotEmpty(state.Filter.Record) { + recordFilter = state.Filter.Record.ValueString() + } + if nameFilter != "" && recordFilter != "" { + // TODO remove after testing validation + resp.Diagnostics.AddError("Filter is invalid", "Only one of 'name' or 'record' can be specified. Validation should prevent this from happening.") + return + } + var found *unifi.DNSRecord + for _, record := range list { + if nameFilter != "" && record.Key == nameFilter { + found = &record + break + } + if recordFilter != "" && record.Value == recordFilter { + found = &record + break + } + } + if found == nil { + resp.Diagnostics.AddError("DNS record not found", "No DNS record found") + return + } + (&state.dnsRecordModel).merge(found) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} diff --git a/internal/provider/dns/datasource_dns_records.go b/internal/provider/dns/datasource_dns_records.go new file mode 100644 index 0000000..4f06928 --- /dev/null +++ b/internal/provider/dns/datasource_dns_records.go @@ -0,0 +1,81 @@ +package dns + +import ( + "context" + "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &dnsRecordsDatasource{} + _ datasource.DataSourceWithConfigure = &dnsRecordsDatasource{} + _ base.BaseData = &dnsRecordsDatasource{} +) + +type dnsRecordsDatasource struct { + client *base.Client +} + +func NewDnsRecordsDatasource() datasource.DataSource { + return &dnsRecordsDatasource{} +} + +func (d *dnsRecordsDatasource) SetClient(client *base.Client) { + d.client = client +} + +func (d *dnsRecordsDatasource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + base.ConfigureDatasource(d, req, resp) +} + +func (d *dnsRecordsDatasource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_%ss", req.ProviderTypeName, resourceName) +} + +func (d *dnsRecordsDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieves information about a all DNS records.", + Attributes: map[string]schema.Attribute{ + "result": schema.ListNestedAttribute{ + Description: "The list of DNS records.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: dnsRecordDatasourceAttributes, + }, + }, + }, + } +} + +func (d *dnsRecordsDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state dnsRecordsDatasourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + records, err := d.client.ListDNSRecord(ctx, d.client.Site) + if err != nil { + resp.Diagnostics.AddError("Failed to list DNS records", err.Error()) + return + } + for _, record := range records { + state.Records = append(state.Records, &dnsRecordModel{ + ID: types.StringValue(record.ID), + SiteID: types.StringValue(record.SiteID), + Name: types.StringValue(record.Key), + Record: types.StringValue(record.Value), + Enabled: types.BoolValue(record.Enabled), + Port: types.Int32Value(int32(record.Port)), + Priority: types.Int32Value(int32(record.Priority)), + Type: types.StringValue(record.RecordType), + TTL: types.Int32Value(int32(record.Ttl)), + Weight: types.Int32Value(int32(record.Weight)), + }) + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} diff --git a/internal/provider/dns/dns_record_model.go b/internal/provider/dns/dns_record_model.go new file mode 100644 index 0000000..1944de5 --- /dev/null +++ b/internal/provider/dns/dns_record_model.go @@ -0,0 +1,102 @@ +package dns + +import ( + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const resourceName = "dns_record" + +type dnsRecordModel struct { + ID types.String `tfsdk:"id"` + SiteID types.String `tfsdk:"site_id"` + Name types.String `tfsdk:"name"` + Record types.String `tfsdk:"record"` + Enabled types.Bool `tfsdk:"enabled"` + Port types.Int32 `tfsdk:"port"` + Priority types.Int32 `tfsdk:"priority"` + Type types.String `tfsdk:"type"` + TTL types.Int32 `tfsdk:"ttl"` + Weight types.Int32 `tfsdk:"weight"` +} + +type dnsRecordDatasourceModel struct { + dnsRecordModel + Filter *dnsRecordFilterModel `tfsdk:"filter"` +} + +type dnsRecordsDatasourceModel struct { + Records []*dnsRecordModel `tfsdk:"result"` +} + +var dnsRecordDatasourceAttributes = map[string]schema.Attribute{ + "id": utils.ID(), + "site_id": utils.ID("The site ID where the DNS record is located."), + "name": schema.StringAttribute{ + Description: "DNS record name.", + Computed: true, + }, + "record": schema.StringAttribute{ + Description: "DNS record content.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the DNS record is enabled.", + Computed: true, + }, + "port": schema.Int32Attribute{ + Description: "The port of the DNS record.", + Computed: true, + }, + "priority": schema.Int32Attribute{ + Description: "Priority of the DNS records. Present only for MX and SRV records; unused by other record types.", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "The type of the DNS record.", + Computed: true, + }, + "ttl": schema.Int32Attribute{ + Description: "Time To Live (TTL) of the DNS record in seconds. Setting to 0 means 'automatic'.", + Computed: true, + }, + "weight": schema.Int32Attribute{ + Description: "A numeric value indicating the relative weight of the record.", + Computed: true, + }, +} + +type dnsRecordFilterModel struct { + Name types.String `tfsdk:"name"` + Record types.String `tfsdk:"record"` +} + +func (d *dnsRecordModel) asUnifiModel() *unifi.DNSRecord { + return &unifi.DNSRecord{ + ID: d.ID.ValueString(), + SiteID: d.SiteID.ValueString(), + Key: d.Name.ValueString(), + Value: d.Record.ValueString(), + Enabled: d.Enabled.ValueBool(), + Port: int(d.Port.ValueInt32()), + Priority: int(d.Priority.ValueInt32()), + RecordType: d.Type.ValueString(), + Ttl: int(d.TTL.ValueInt32()), + Weight: int(d.Weight.ValueInt32()), + } +} + +func (d *dnsRecordModel) merge(other *unifi.DNSRecord) { + d.ID = types.StringValue(other.ID) + d.SiteID = types.StringValue(other.SiteID) + d.Name = types.StringValue(other.Key) + d.Record = types.StringValue(other.Value) + d.Enabled = types.BoolValue(other.Enabled) + d.Port = types.Int32Value(int32(other.Port)) + d.Priority = types.Int32Value(int32(other.Priority)) + d.Type = types.StringValue(other.RecordType) + d.TTL = types.Int32Value(int32(other.Ttl)) + d.Weight = types.Int32Value(int32(other.Weight)) +} diff --git a/internal/provider/dns/resource_dns_record.go b/internal/provider/dns/resource_dns_record.go new file mode 100644 index 0000000..cbf3d58 --- /dev/null +++ b/internal/provider/dns/resource_dns_record.go @@ -0,0 +1,236 @@ +package dns + +import ( + "context" + "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &dnsRecordResource{} + _ resource.ResourceWithConfigure = &dnsRecordResource{} + _ resource.ResourceWithImportState = &dnsRecordResource{} + _ base.BaseData = &dnsRecordResource{} +) + +type dnsRecordResource struct { + client *base.Client +} + +func (d *dnsRecordResource) SetClient(client *base.Client) { + d.client = client +} + +func NewDnsRecordResource() resource.Resource { + return &dnsRecordResource{} +} + +func (d *dnsRecordResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + base.ConfigureResource(d, req, resp) +} + +func (d *dnsRecordResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, resourceName) +} + +func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a DNS record in the Unifi controller.", + + Attributes: map[string]schema.Attribute{ + "id": utils.ID(), + "site_id": utils.ID("The site ID where the DNS record is located."), + "name": schema.StringAttribute{ + MarkdownDescription: "DNS record name.", + Required: true, + }, + "record": schema.StringAttribute{ + MarkdownDescription: "DNS record content.", + Required: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Whether the DNS record is enabled.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "port": schema.Int32Attribute{ + MarkdownDescription: "The port of the DNS record.", + Optional: true, + Computed: true, + Validators: []validator.Int32{ + int32validator.Between(1, 65535), + }, + }, + "priority": schema.Int32Attribute{ + MarkdownDescription: "Required for MX and SRV records; unused by other record types. Records with lower priorities are preferred", + Optional: true, + Computed: true, + Validators: []validator.Int32{ + int32validator.AtLeast(1), + }, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the DNS record.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("A", "AAAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV", "TXT"), + }, + }, + "ttl": schema.Int32Attribute{ + MarkdownDescription: "Time To Live (TTL) of the DNS record in seconds. Setting to 0 means 'automatic'.", + Optional: true, + Computed: true, + }, + "weight": schema.Int32Attribute{ + MarkdownDescription: "A numeric value indicating the relative weight of the record.", + Optional: true, + Computed: true, + }, + }, + } + +} + +func (d *dnsRecordResource) checkSupportsDnsRecords(diag *diag.Diagnostics) { + if !d.client.SupportsDnsRecords() { + diag.AddError("DNS Records are not supported", fmt.Sprintf("The Unifi controller in version %q does not support DNS records. Required controller version: %q", d.client.Version, base.ControllerVersionDnsRecords)) + } +} + +func (d *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + d.checkSupportsDnsRecords(&resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var plan dnsRecordModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + body := plan.asUnifiModel() + + res, err := d.client.CreateDNSRecord(ctx, d.client.Site, body) + if err != nil { + resp.Diagnostics.AddError("Error creating DNS record", err.Error()) + return + } + plan.merge(res) + + resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (d *dnsRecordResource) read(ctx context.Context, state *dnsRecordModel, diag *diag.Diagnostics) { + res, err := d.client.GetDNSRecord(ctx, d.client.Site, state.ID.ValueString()) + if err != nil { + diag.AddError("Error reading DNS record", err.Error()) + return + } + state.merge(res) +} + +func (d *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + d.checkSupportsDnsRecords(&resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var state dnsRecordModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + d.read(ctx, &state, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) +} + +func (d *dnsRecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + d.checkSupportsDnsRecords(&resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var plan, state dnsRecordModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + body := plan.asUnifiModel() + + res, err := d.client.UpdateDNSRecord(ctx, d.client.Site, body) + if err != nil { + resp.Diagnostics.AddError("Error updating DNS record", err.Error()) + return + } + state.merge(res) + diags := resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) +} + +func (d *dnsRecordResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + d.checkSupportsDnsRecords(&resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var state dnsRecordModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + err := d.client.DeleteDNSRecord(ctx, d.client.Site, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error deleting DNS record", err.Error()) + return + } +} + +func (d *dnsRecordResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + d.checkSupportsDnsRecords(&resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + id := req.ID + if id == "" { + resp.Diagnostics.AddError("Invalid import ID", "The ID must be set") + return + } + + state := dnsRecordModel{ + ID: types.StringValue(id), + } + d.read(ctx, &state, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + d.read(ctx, &state, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + diags := resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/v1/resource_dynamic_dns.go b/internal/provider/dns/resource_dynamic_dns.go similarity index 94% rename from internal/provider/v1/resource_dynamic_dns.go rename to internal/provider/dns/resource_dynamic_dns.go index 993ecd1..e50a355 100644 --- a/internal/provider/v1/resource_dynamic_dns.go +++ b/internal/provider/dns/resource_dynamic_dns.go @@ -1,16 +1,17 @@ -package v1 +package dns import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceDynamicDNS() *schema.Resource { +func ResourceDynamicDNS() *schema.Resource { return &schema.Resource{ Description: "`unifi_dynamic_dns` manages dynamic DNS settings for different providers.", @@ -19,7 +20,7 @@ func resourceDynamicDNS() *schema.Resource { UpdateContext: resourceDynamicDNSUpdate, DeleteContext: resourceDynamicDNSDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -76,7 +77,7 @@ func resourceDynamicDNS() *schema.Resource { } func resourceDynamicDNSCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceDynamicDNSGetResourceData(d) if err != nil { @@ -127,7 +128,7 @@ func resourceDynamicDNSSetResourceData(resp *unifi.DynamicDNS, d *schema.Resourc } func resourceDynamicDNSRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -149,7 +150,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceDynamicDNSGetResourceData(d) if err != nil { @@ -173,7 +174,7 @@ func resourceDynamicDNSUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourceDynamicDNSDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_firewall_group.go b/internal/provider/firewall/resource_firewall_group.go similarity index 93% rename from internal/provider/v1/resource_firewall_group.go rename to internal/provider/firewall/resource_firewall_group.go index 040ea09..e0b824e 100644 --- a/internal/provider/v1/resource_firewall_group.go +++ b/internal/provider/firewall/resource_firewall_group.go @@ -1,9 +1,9 @@ -package v1 +package firewall import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceFirewallGroup() *schema.Resource { +func ResourceFirewallGroup() *schema.Resource { return &schema.Resource{ Description: "`unifi_firewall_group` manages groups of addresses or ports for use in firewall rules (`unifi_firewall_rule`).", @@ -21,7 +21,7 @@ func resourceFirewallGroup() *schema.Resource { UpdateContext: resourceFirewallGroupUpdate, DeleteContext: resourceFirewallGroupDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -59,7 +59,7 @@ func resourceFirewallGroup() *schema.Resource { } func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceFirewallGroupGetResourceData(d) if err != nil { @@ -73,7 +73,7 @@ func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, me resp, err := c.CreateFirewallGroup(ctx, site, req) if err != nil { - if provider.IsServerErrorContains(err, "api.err.FirewallGroupExisted") { + if base.IsServerErrorContains(err, "api.err.FirewallGroupExisted") { return diag.Errorf("firewall groups must have unique names: %s", err) } return diag.FromErr(err) @@ -107,7 +107,7 @@ func resourceFirewallGroupSetResourceData(resp *unifi.FirewallGroup, d *schema.R } func resourceFirewallGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -129,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceFirewallGroupGetResourceData(d) if err != nil { @@ -153,7 +153,7 @@ func resourceFirewallGroupUpdate(ctx context.Context, d *schema.ResourceData, me } func resourceFirewallGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_firewall_rule.go b/internal/provider/firewall/resource_firewall_rule.go similarity index 97% rename from internal/provider/v1/resource_firewall_rule.go rename to internal/provider/firewall/resource_firewall_rule.go index 540fc00..b2e23a1 100644 --- a/internal/provider/v1/resource_firewall_rule.go +++ b/internal/provider/firewall/resource_firewall_rule.go @@ -1,9 +1,9 @@ -package v1 +package firewall import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "regexp" @@ -17,7 +17,7 @@ var firewallRuleProtocolRegexp = regexp.MustCompile("^$|all|([0-9]|[1-9][0-9]|1[ var firewallRuleProtocolV6Regexp = regexp.MustCompile("^$|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|ah|all|dccp|eigrp|esp|gre|icmpv6|ipcomp|ipv6|ipv6-frag|ipv6-icmp|ipv6-nonxt|ipv6-opts|ipv6-route|isis|l2tp|manet|mobility-header|mpls-in-ip|ospf|pim|rsvp|sctp|shim6|tcp|tcp_udp|udp|vrrp") var firewallRuleICMPv6TypenameRegexp = regexp.MustCompile("^$|address-unreachable|bad-header|beyond-scope|communication-prohibited|destination-unreachable|echo-reply|echo-request|failed-policy|neighbor-advertisement|neighbor-solicitation|no-route|packet-too-big|parameter-problem|port-unreachable|redirect|reject-route|router-advertisement|router-solicitation|time-exceeded|ttl-zero-during-reassembly|ttl-zero-during-transit|unknown-header-type|unknown-option") -func resourceFirewallRule() *schema.Resource { +func ResourceFirewallRule() *schema.Resource { return &schema.Resource{ Description: "`unifi_firewall_rule` manages an individual firewall rule on the gateway.", @@ -26,7 +26,7 @@ func resourceFirewallRule() *schema.Resource { UpdateContext: resourceFirewallRuleUpdate, DeleteContext: resourceFirewallRuleDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -131,7 +131,7 @@ func resourceFirewallRule() *schema.Resource { Description: "The source port of the firewall rule.", Type: schema.TypeString, Optional: true, - ValidateFunc: validatePortRange, + ValidateFunc: utils.ValidatePortRange, }, "src_mac": { Description: "The source MAC address of the firewall rule.", @@ -172,7 +172,7 @@ func resourceFirewallRule() *schema.Resource { Description: "The destination port of the firewall rule.", Type: schema.TypeString, Optional: true, - ValidateFunc: validatePortRange, + ValidateFunc: utils.ValidatePortRange, }, // advanced @@ -212,7 +212,7 @@ func resourceFirewallRule() *schema.Resource { } func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceFirewallRuleGetResourceData(d) if err != nil { @@ -226,7 +226,7 @@ func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, met resp, err := c.CreateFirewallRule(ctx, site, req) if err != nil { - if provider.IsServerErrorContains(err, "api.err.FirewallGroupTypeExists") { + if base.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) } @@ -320,7 +320,7 @@ func resourceFirewallRuleSetResourceData(resp *unifi.FirewallRule, d *schema.Res } func resourceFirewallRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -342,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceFirewallRuleGetResourceData(d) if err != nil { @@ -366,7 +366,7 @@ func resourceFirewallRuleUpdate(ctx context.Context, d *schema.ResourceData, met } func resourceFirewallRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/data_network.go b/internal/provider/network/data_network.go similarity index 98% rename from internal/provider/v1/data_network.go rename to internal/provider/network/data_network.go index b7043d7..a1b6bba 100644 --- a/internal/provider/v1/data_network.go +++ b/internal/provider/network/data_network.go @@ -1,15 +1,15 @@ -package v1 +package network import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "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" ) -func dataNetwork() *schema.Resource { +func DataNetwork() *schema.Resource { return &schema.Resource{ Description: "`unifi_network` data source can be used to retrieve settings for a network by name or ID.", @@ -129,7 +129,7 @@ func dataNetwork() *schema.Resource { Computed: true, }, "dhcp_v6_start": { - Description: "Start address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Description: "start address of the DHCPv6 range. Used in static DHCPv6 configuration.", Type: schema.TypeString, Computed: true, }, @@ -169,7 +169,7 @@ func dataNetwork() *schema.Resource { Computed: true, }, "ipv6_pd_start": { - Description: "Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Description: "start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", Type: schema.TypeString, Computed: true, }, @@ -281,7 +281,7 @@ func dataNetwork() *schema.Resource { } func dataNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/v1/data_port_profile.go b/internal/provider/network/data_port_profile.go similarity index 93% rename from internal/provider/v1/data_port_profile.go rename to internal/provider/network/data_port_profile.go index ba4fc67..e50df2e 100644 --- a/internal/provider/v1/data_port_profile.go +++ b/internal/provider/network/data_port_profile.go @@ -1,14 +1,13 @@ -package v1 +package network import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataPortProfile() *schema.Resource { +func DataPortProfile() *schema.Resource { return &schema.Resource{ Description: "`unifi_port_profile` data source can be used to retrieve the ID for a port profile by name.", @@ -37,7 +36,7 @@ func dataPortProfile() *schema.Resource { } func dataPortProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/v1/resource_network.go b/internal/provider/network/resource_network.go similarity index 98% rename from internal/provider/v1/resource_network.go rename to internal/provider/network/resource_network.go index dd72eaf..f42ec51 100644 --- a/internal/provider/v1/resource_network.go +++ b/internal/provider/network/resource_network.go @@ -1,10 +1,10 @@ -package v1 +package network import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "regexp" "strings" @@ -44,7 +44,7 @@ var ( validateIpV6RAPriority = validation.StringMatch(ipV6RAPriorityRegexp, "invalid IPv6 RA priority") ) -func resourceNetwork() *schema.Resource { +func ResourceNetwork() *schema.Resource { return &schema.Resource{ Description: "`unifi_network` manages WAN/LAN/VLAN networks.", @@ -91,7 +91,7 @@ func resourceNetwork() *schema.Resource { Description: "The subnet of the network. Must be a valid CIDR address.", Type: schema.TypeString, Optional: true, - DiffSuppressFunc: cidrDiffSuppress, + DiffSuppressFunc: utils.CidrDiffSuppress, ValidateFunc: utils.CidrValidate, }, "network_group": { @@ -189,7 +189,7 @@ func resourceNetwork() *schema.Resource { Default: 86400, }, "dhcp_v6_start": { - Description: "Start address of the DHCPv6 range. Used in static DHCPv6 configuration.", + Description: "start address of the DHCPv6 range. Used in static DHCPv6 configuration.", Type: schema.TypeString, Optional: true, ValidateFunc: validation.IsIPv6Address, @@ -240,7 +240,7 @@ func resourceNetwork() *schema.Resource { Optional: true, }, "ipv6_pd_start": { - Description: "Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", + Description: "start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.", Type: schema.TypeString, Optional: true, ValidateFunc: validation.IsIPv6Address, @@ -384,7 +384,7 @@ func resourceNetwork() *schema.Resource { } func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceNetworkGetResourceData(d, meta) if err != nil { @@ -626,7 +626,7 @@ func resourceNetworkSetResourceData(resp *unifi.Network, d *schema.ResourceData, } func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -648,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceNetworkGetResourceData(d, meta) if err != nil { @@ -671,7 +671,7 @@ func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -687,7 +687,7 @@ 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.(*provider.Client) + c := meta.(*base.Client) id := d.Id() site := d.Get("site").(string) if site == "" { diff --git a/internal/provider/v1/resource_port_profile.go b/internal/provider/network/resource_port_profile.go similarity index 98% rename from internal/provider/v1/resource_port_profile.go rename to internal/provider/network/resource_port_profile.go index d39f4ea..86c4ecc 100644 --- a/internal/provider/v1/resource_port_profile.go +++ b/internal/provider/network/resource_port_profile.go @@ -1,9 +1,9 @@ -package v1 +package network import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourcePortProfile() *schema.Resource { +func ResourcePortProfile() *schema.Resource { return &schema.Resource{ Description: "`unifi_port_profile` manages a port profile for use on network switches.", @@ -21,7 +21,7 @@ func resourcePortProfile() *schema.Resource { UpdateContext: resourcePortProfileUpdate, DeleteContext: resourcePortProfileDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -258,7 +258,7 @@ func resourcePortProfile() *schema.Resource { } func resourcePortProfileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourcePortProfileGetResourceData(d) if err != nil { @@ -371,7 +371,7 @@ func resourcePortProfileSetResourceData(resp *unifi.PortProfile, d *schema.Resou } func resourcePortProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -392,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourcePortProfileGetResourceData(d) if err != nil { @@ -416,7 +416,7 @@ func resourcePortProfileUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourcePortProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_wlan.go b/internal/provider/network/resource_wlan.go similarity index 96% rename from internal/provider/v1/resource_wlan.go rename to internal/provider/network/resource_wlan.go index c1fe42e..afcab34 100644 --- a/internal/provider/v1/resource_wlan.go +++ b/internal/provider/network/resource_wlan.go @@ -1,10 +1,10 @@ -package v1 +package network import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" @@ -18,7 +18,7 @@ var ( wlanValidMinimumDataRate5g = []int{6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000} ) -func resourceWLAN() *schema.Resource { +func ResourceWLAN() *schema.Resource { return &schema.Resource{ Description: "`unifi_wlan` manages a WiFi network / SSID.", @@ -27,7 +27,7 @@ func resourceWLAN() *schema.Resource { UpdateContext: resourceWLANUpdate, DeleteContext: resourceWLANDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -110,8 +110,8 @@ func resourceWLAN() *schema.Resource { Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateFunc: validation.StringMatch(macAddressRegexp, "Mac address is invalid"), - DiffSuppressFunc: macDiffSuppressFunc, + ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"), + DiffSuppressFunc: utils.MacDiffSuppressFunc, }, }, "mac_filter_policy": { @@ -128,7 +128,7 @@ func resourceWLAN() *schema.Resource { Optional: true, }, "schedule": { - Description: "Start and stop schedules for the WLAN", + Description: "start and stop schedules for the WLAN", Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ @@ -140,13 +140,13 @@ func resourceWLAN() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"sun", "mon", "tue", "wed", "thu", "fri", "sat", "sun"}, false), }, "start_hour": { - Description: "Start hour for the block (0-23).", + Description: "start hour for the block (0-23).", Type: schema.TypeInt, Required: true, ValidateFunc: validation.IntBetween(0, 23), }, "start_minute": { - Description: "Start minute for the block (0-59).", + Description: "start minute for the block (0-59).", Type: schema.TypeInt, Optional: true, Default: 0, @@ -245,7 +245,7 @@ func resourceWLAN() *schema.Resource { } func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*unifi.WLAN, error) { - c := meta.(*provider.Client) + c := meta.(*base.Client) security := d.Get("security").(string) passphrase := d.Get("passphrase").(string) @@ -267,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, provider.ControllerVersionWPA3) + return nil, fmt.Errorf("WPA 3 support is not available on controller version %q, you must be on %q or higher", c.Version, base.ControllerVersionWPA3) } } @@ -355,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceWLANGetResourceData(d, meta) if err != nil { @@ -443,7 +443,7 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta } func resourceWLANRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() site := d.Get("site").(string) @@ -464,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceWLANGetResourceData(d, meta) if err != nil { @@ -487,7 +487,7 @@ func resourceWLANUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceWLANDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() site := d.Get("site").(string) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 344a7be..7a2f3b4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,5 +1,27 @@ package provider +import ( + "context" + "errors" + "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/apgroup" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/device" + "github.com/filipowm/terraform-provider-unifi/internal/provider/dns" + "github.com/filipowm/terraform-provider-unifi/internal/provider/firewall" + "github.com/filipowm/terraform-provider-unifi/internal/provider/network" + "github.com/filipowm/terraform-provider-unifi/internal/provider/radius" + "github.com/filipowm/terraform-provider-unifi/internal/provider/routing" + "github.com/filipowm/terraform-provider-unifi/internal/provider/settings" + "github.com/filipowm/terraform-provider-unifi/internal/provider/site" + "github.com/filipowm/terraform-provider-unifi/internal/provider/user" + "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" + "net/http" + "strings" +) + 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." @@ -11,3 +33,135 @@ const ( "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: ProviderUsernameDescription, + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_USERNAME", ""), + }, + "password": { + Description: ProviderPasswordDescription, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_PASSWORD", ""), + }, + "api_key": { + Description: ProviderAPIKeyDescription, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_API_KEY", ""), + }, + "api_url": { + Description: ProviderAPIURLDescription, + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_API", ""), + }, + "site": { + Description: ProviderSiteDescription, + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_SITE", "default"), + }, + "allow_insecure": { + Description: ProviderAllowInsecureDescription, + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("UNIFI_INSECURE", false), + }, + }, + DataSourcesMap: map[string]*schema.Resource{ + "unifi_ap_group": apgroup.DataAPGroup(), + "unifi_network": network.DataNetwork(), + "unifi_port_profile": network.DataPortProfile(), + "unifi_radius_profile": radius.DataRADIUSProfile(), + "unifi_user_group": user.DataUserGroup(), + "unifi_user": user.DataUser(), + "unifi_account": radius.DataAccount(), + }, + ResourcesMap: map[string]*schema.Resource{ + // TODO: "unifi_ap_group" + "unifi_device": device.ResourceDevice(), + "unifi_dynamic_dns": dns.ResourceDynamicDNS(), + "unifi_firewall_group": firewall.ResourceFirewallGroup(), + "unifi_firewall_rule": firewall.ResourceFirewallRule(), + "unifi_network": network.ResourceNetwork(), + "unifi_port_forward": routing.ResourcePortForward(), + "unifi_static_route": routing.ResourceStaticRoute(), + "unifi_wlan": network.ResourceWLAN(), + "unifi_port_profile": network.ResourcePortProfile(), + "unifi_site": site.ResourceSite(), + "unifi_account": radius.ResourceAccount(), + "unifi_radius_profile": radius.ResourceRadiusProfile(), + + "unifi_setting_mgmt": settings.ResourceSettingMgmt(), + "unifi_setting_radius": settings.ResourceSettingRadius(), + "unifi_setting_usg": settings.ResourceSettingUsg(), + "unifi_user_group": user.ResourceUserGroup(), + "unifi_user": user.ResourceUser(), + }, + } + + p.ConfigureContextFunc = configure(version, p) + return p + } +} + +func createHTTPTransport(insecure bool, subsystem string) http.RoundTripper { + transport := base.CreateHttpTransport(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 := base.NewClient(&base.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/v2/provider.go b/internal/provider/provider_v2.go similarity index 85% rename from internal/provider/v2/provider.go rename to internal/provider/provider_v2.go index 0d9964b..3cc0325 100644 --- a/internal/provider/v2/provider.go +++ b/internal/provider/provider_v2.go @@ -1,8 +1,9 @@ -package v2 +package provider import ( "context" - up "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/dns" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -15,7 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -func New(version string) func() provider.Provider { +func NewV2(version string) func() provider.Provider { return func() provider.Provider { return &unifiProvider{ version: version, @@ -45,32 +46,32 @@ func (p *unifiProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "username": schema.StringAttribute{ - MarkdownDescription: up.ProviderUsernameDescription, + MarkdownDescription: ProviderUsernameDescription, Optional: true, }, "password": schema.StringAttribute{ - MarkdownDescription: up.ProviderPasswordDescription, + MarkdownDescription: ProviderPasswordDescription, Optional: true, Sensitive: true, }, "api_key": schema.StringAttribute{ - MarkdownDescription: up.ProviderAPIKeyDescription, + MarkdownDescription: ProviderAPIKeyDescription, Optional: true, Sensitive: true, }, "api_url": schema.StringAttribute{ - MarkdownDescription: up.ProviderAPIURLDescription, + MarkdownDescription: 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, + MarkdownDescription: ProviderSiteDescription, Optional: true, }, "allow_insecure": schema.BoolAttribute{ - MarkdownDescription: up.ProviderAllowInsecureDescription, + MarkdownDescription: ProviderAllowInsecureDescription, Optional: true, }, }, @@ -97,7 +98,7 @@ func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureReq "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.", + "the configuration, or use the UNIFI_API environment variable.", ) } @@ -111,7 +112,7 @@ func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureReq username := utils.GetAnyStringEnv("UNIFI_USERNAME") password := utils.GetAnyStringEnv("UNIFI_PASSWORD") apiKey := utils.GetAnyStringEnv("UNIFI_API_KEY") - apiUrl := utils.GetAnyStringEnv("UNIFI_API_URL") + apiUrl := utils.GetAnyStringEnv("UNIFI_API") site := utils.GetAnyStringEnv("UNIFI_SITE") insecure := utils.GetAnyBoolEnv("UNIFI_INSECURE") @@ -147,7 +148,7 @@ func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureReq if site == "" { site = "default" // set default site if not provided } - c, err := up.NewClient(&up.ClientConfig{ + c, err := base.NewClient(&base.ClientConfig{ Username: username, Password: password, ApiKey: apiKey, @@ -164,9 +165,14 @@ func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureReq } func (p *unifiProvider) Resources(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{} + return []func() resource.Resource{ + dns.NewDnsRecordResource, + } } func (p *unifiProvider) DataSources(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + dns.NewDnsRecordsDatasource, + dns.NewDnsRecordDatasource, + } } diff --git a/internal/provider/v1/data_account.go b/internal/provider/radius/data_account.go similarity index 96% rename from internal/provider/v1/data_account.go rename to internal/provider/radius/data_account.go index fe13f5c..c7d1230 100644 --- a/internal/provider/v1/data_account.go +++ b/internal/provider/radius/data_account.go @@ -1,14 +1,13 @@ -package v1 +package radius import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataAccount() *schema.Resource { +func DataAccount() *schema.Resource { return &schema.Resource{ Description: "unifi_account data source can be used to retrieve RADIUS user accounts", @@ -58,7 +57,7 @@ func dataAccount() *schema.Resource { } func dataAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/v1/data_radius_profile.go b/internal/provider/radius/data_radius_profile.go similarity index 93% rename from internal/provider/v1/data_radius_profile.go rename to internal/provider/radius/data_radius_profile.go index 3a04498..5ddc253 100644 --- a/internal/provider/v1/data_radius_profile.go +++ b/internal/provider/radius/data_radius_profile.go @@ -1,14 +1,13 @@ -package v1 +package radius import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataRADIUSProfile() *schema.Resource { +func DataRADIUSProfile() *schema.Resource { return &schema.Resource{ Description: "`unifi_radius_profile` data source can be used to retrieve the ID for a RADIUS profile by name.", @@ -37,7 +36,7 @@ func dataRADIUSProfile() *schema.Resource { } func dataRADIUSProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/v1/resource_account.go b/internal/provider/radius/resource_account.go similarity index 94% rename from internal/provider/v1/resource_account.go rename to internal/provider/radius/resource_account.go index 8b563c8..b6c9c64 100644 --- a/internal/provider/v1/resource_account.go +++ b/internal/provider/radius/resource_account.go @@ -1,9 +1,10 @@ -package v1 +package radius import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -11,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceAccount() *schema.Resource { +func ResourceAccount() *schema.Resource { return &schema.Resource{ Description: "`unifi_account` manages a RADIUS user account\n\n" + "To authenticate devices based on MAC address, use the MAC address as the username and password under client creation. \n" + @@ -24,7 +25,7 @@ func resourceAccount() *schema.Resource { UpdateContext: resourceAccountUpdate, DeleteContext: resourceAccountDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -75,7 +76,7 @@ func resourceAccount() *schema.Resource { } func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceAccountGetResourceData(d) if err != nil { @@ -98,7 +99,7 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -122,7 +123,7 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) //name := d.Get("name").(string) site := d.Get("site").(string) @@ -139,7 +140,7 @@ func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta int } func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_radius_profile.go b/internal/provider/radius/resource_radius_profile.go similarity index 98% rename from internal/provider/v1/resource_radius_profile.go rename to internal/provider/radius/resource_radius_profile.go index eb9d457..f5d9de8 100644 --- a/internal/provider/v1/resource_radius_profile.go +++ b/internal/provider/radius/resource_radius_profile.go @@ -1,10 +1,10 @@ -package v1 +package radius import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "strings" "github.com/filipowm/go-unifi/unifi" @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRadiusProfile() *schema.Resource { +func ResourceRadiusProfile() *schema.Resource { return &schema.Resource{ Description: "`unifi_radius_profile` manages RADIUS profiles.", @@ -235,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceRadiusProfileGetResourceData(d) if err != nil { return diag.FromErr(err) @@ -303,7 +303,7 @@ func resourceRadiusProfileSetResourceData(resp *unifi.RADIUSProfile, d *schema.R } func resourceRadiusProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -324,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceRadiusProfileGetResourceData(d) if err != nil { @@ -348,7 +348,7 @@ func resourceRadiusProfileUpdate(ctx context.Context, d *schema.ResourceData, me } func resourceRadiusProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -362,7 +362,7 @@ func resourceRadiusProfileDelete(ctx context.Context, d *schema.ResourceData, me } func importRadiusProfile(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() site := d.Get("site").(string) if site == "" { diff --git a/internal/provider/v1/resource_port_forward.go b/internal/provider/routing/resource_port_forward.go similarity index 95% rename from internal/provider/v1/resource_port_forward.go rename to internal/provider/routing/resource_port_forward.go index cc4612a..b40967b 100644 --- a/internal/provider/v1/resource_port_forward.go +++ b/internal/provider/routing/resource_port_forward.go @@ -1,9 +1,9 @@ -package v1 +package routing import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourcePortForward() *schema.Resource { +func ResourcePortForward() *schema.Resource { return &schema.Resource{ Description: "`unifi_port_forward` manages a port forwarding rule on the gateway.", @@ -21,7 +21,7 @@ func resourcePortForward() *schema.Resource { UpdateContext: resourcePortForwardUpdate, DeleteContext: resourcePortForwardDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -41,7 +41,7 @@ func resourcePortForward() *schema.Resource { Description: "The destination port for the forwarding.", Type: schema.TypeString, Optional: true, - ValidateFunc: validatePortRange, + ValidateFunc: utils.ValidatePortRange, }, // TODO: remove this, disabled rules should just be deleted. "enabled": { @@ -62,7 +62,7 @@ func resourcePortForward() *schema.Resource { Description: "The port to forward traffic to.", Type: schema.TypeString, Optional: true, - ValidateFunc: validatePortRange, + ValidateFunc: utils.ValidatePortRange, }, "log": { Description: "Specifies whether to log forwarded traffic or not.", @@ -104,7 +104,7 @@ func resourcePortForward() *schema.Resource { } func resourcePortForwardCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourcePortForwardGetResourceData(d) if err != nil { @@ -155,7 +155,7 @@ func resourcePortForwardSetResourceData(resp *unifi.PortForward, d *schema.Resou } func resourcePortForwardRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -176,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourcePortForwardGetResourceData(d) if err != nil { @@ -200,7 +200,7 @@ func resourcePortForwardUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourcePortForwardDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_static_route.go b/internal/provider/routing/resource_static_route.go similarity index 95% rename from internal/provider/v1/resource_static_route.go rename to internal/provider/routing/resource_static_route.go index 44ab540..5de66cb 100644 --- a/internal/provider/v1/resource_static_route.go +++ b/internal/provider/routing/resource_static_route.go @@ -1,10 +1,10 @@ -package v1 +package routing import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceStaticRoute() *schema.Resource { +func ResourceStaticRoute() *schema.Resource { return &schema.Resource{ Description: "`unifi_static_route` manages a static route.", @@ -22,7 +22,7 @@ func resourceStaticRoute() *schema.Resource { UpdateContext: resourceStaticRouteUpdate, DeleteContext: resourceStaticRouteDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -49,7 +49,7 @@ func resourceStaticRoute() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: utils.CidrValidate, - DiffSuppressFunc: cidrDiffSuppress, + DiffSuppressFunc: utils.CidrDiffSuppress, }, "type": { Description: "The type of static route. Can be `interface-route`, `nexthop-route`, or `blackhole`.", @@ -79,7 +79,7 @@ func resourceStaticRoute() *schema.Resource { } func resourceStaticRouteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceStaticRouteGetResourceData(d) if err != nil { @@ -154,7 +154,7 @@ func resourceStaticRouteSetResourceData(resp *unifi.Routing, d *schema.ResourceD } func resourceStaticRouteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -176,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceStaticRouteGetResourceData(d) if err != nil { @@ -200,7 +200,7 @@ func resourceStaticRouteUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourceStaticRouteDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_setting_mgmt.go b/internal/provider/settings/resource_setting_mgmt.go similarity index 95% rename from internal/provider/v1/resource_setting_mgmt.go rename to internal/provider/settings/resource_setting_mgmt.go index 7bb6456..3f30881 100644 --- a/internal/provider/v1/resource_setting_mgmt.go +++ b/internal/provider/settings/resource_setting_mgmt.go @@ -1,10 +1,11 @@ -package v1 +package settings import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -14,7 +15,7 @@ import ( // TODO: probably need to update this to be more like setting_usg, // using locking, and upsert, more computed, etc. -func resourceSettingMgmt() *schema.Resource { +func ResourceSettingMgmt() *schema.Resource { return &schema.Resource{ Description: "`unifi_setting_mgmt` manages settings for a unifi site.", @@ -23,7 +24,7 @@ func resourceSettingMgmt() *schema.Resource { UpdateContext: resourceSettingMgmtUpdate, DeleteContext: resourceSettingMgmtDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -142,7 +143,7 @@ func resourceSettingMgmtGetResourceData(d *schema.ResourceData, meta interface{} } func resourceSettingMgmtCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceSettingMgmtGetResourceData(d, meta) if err != nil { @@ -178,7 +179,7 @@ func resourceSettingMgmtSetResourceData(resp *unifi.SettingMgmt, d *schema.Resou } func resourceSettingMgmtRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -198,7 +199,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceSettingMgmtGetResourceData(d, meta) if err != nil { diff --git a/internal/provider/v1/resource_setting_radius.go b/internal/provider/settings/resource_setting_radius.go similarity index 94% rename from internal/provider/v1/resource_setting_radius.go rename to internal/provider/settings/resource_setting_radius.go index 548da87..79342f7 100644 --- a/internal/provider/v1/resource_setting_radius.go +++ b/internal/provider/settings/resource_setting_radius.go @@ -1,9 +1,10 @@ -package v1 +package settings import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -11,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceSettingRadius() *schema.Resource { +func ResourceSettingRadius() *schema.Resource { return &schema.Resource{ Description: "`unifi_setting_radius` manages settings for the built-in RADIUS server.", @@ -20,7 +21,7 @@ func resourceSettingRadius() *schema.Resource { UpdateContext: resourceSettingRadiusUpdate, DeleteContext: schema.NoopContext, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -99,7 +100,7 @@ func resourceSettingRadiusGetResourceData(d *schema.ResourceData, meta interface } func resourceSettingRadiusCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceSettingRadiusGetResourceData(d, meta) if err != nil { @@ -134,7 +135,7 @@ func resourceSettingRadiusSetResourceData(resp *unifi.SettingRadius, d *schema.R } func resourceSettingRadiusRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -154,7 +155,7 @@ func resourceSettingRadiusRead(ctx context.Context, d *schema.ResourceData, meta } func resourceSettingRadiusUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceSettingRadiusGetResourceData(d, meta) if err != nil { diff --git a/internal/provider/v1/resource_setting_usg.go b/internal/provider/settings/resource_setting_usg.go similarity index 96% rename from internal/provider/v1/resource_setting_usg.go rename to internal/provider/settings/resource_setting_usg.go index dacb261..a6a0f58 100644 --- a/internal/provider/v1/resource_setting_usg.go +++ b/internal/provider/settings/resource_setting_usg.go @@ -1,10 +1,10 @@ -package v1 +package settings import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/filipowm/terraform-provider-unifi/internal/utils" "sync" @@ -24,7 +24,7 @@ func resourceSettingUsgLocker(f func(context.Context, *schema.ResourceData, inte } } -func resourceSettingUsg() *schema.Resource { +func ResourceSettingUsg() *schema.Resource { return &schema.Resource{ Description: "`unifi_setting_usg` manages settings for a Unifi Security Gateway.", @@ -33,7 +33,7 @@ func resourceSettingUsg() *schema.Resource { UpdateContext: resourceSettingUsgLocker(resourceSettingUsgUpsert), DeleteContext: schema.NoopContext, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -75,7 +75,7 @@ func resourceSettingUsg() *schema.Resource { } func resourceSettingUsgUpdateResourceData(d *schema.ResourceData, meta interface{}, setting *unifi.SettingUsg) error { - c := meta.(*provider.Client) + c := meta.(*base.Client) //nolint // GetOkExists is deprecated, but using here: if mdns, hasMdns := d.GetOkExists("multicast_dns_enabled"); hasMdns { @@ -100,7 +100,7 @@ func resourceSettingUsgUpdateResourceData(d *schema.ResourceData, meta interface } func resourceSettingUsgUpsert(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -149,7 +149,7 @@ func resourceSettingUsgSetResourceData(resp *unifi.SettingUsg, d *schema.Resourc } func resourceSettingUsgRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { diff --git a/internal/provider/v1/resource_site.go b/internal/provider/site/resource_site.go similarity index 93% rename from internal/provider/v1/resource_site.go rename to internal/provider/site/resource_site.go index f159adf..950a773 100644 --- a/internal/provider/v1/resource_site.go +++ b/internal/provider/site/resource_site.go @@ -1,17 +1,16 @@ -package v1 +package site import ( "context" "errors" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceSite() *schema.Resource { +func ResourceSite() *schema.Resource { return &schema.Resource{ Description: "`unifi_site` manages Unifi sites", @@ -44,7 +43,7 @@ func resourceSite() *schema.Resource { } func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() _, err := c.GetSite(ctx, id) @@ -74,7 +73,7 @@ func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta interf } func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) description := d.Get("description").(string) @@ -96,7 +95,7 @@ func resourceSiteSetResourceData(resp *unifi.Site, d *schema.ResourceData) diag. } func resourceSiteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -113,7 +112,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.(*provider.Client) + c := meta.(*base.Client) site := &unifi.Site{ ID: d.Id(), @@ -130,7 +129,7 @@ func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func resourceSiteDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() _, err := c.DeleteSite(ctx, id) return diag.FromErr(err) diff --git a/internal/provider/testing/test_environment.go b/internal/provider/testing/test_environment.go new file mode 100644 index 0000000..4d34cf5 --- /dev/null +++ b/internal/provider/testing/test_environment.go @@ -0,0 +1,277 @@ +package testing + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "net/http" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/filipowm/go-unifi/unifi" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/compose" +) + +const ( + TestEnvStarting testEnvironmentStatus = iota + TestEnvReady + TestEnvDown + TestEnvUnknown +) + +type testEnvironmentStatus int + +type TestEnvironment struct { + Client unifi.Client + Endpoint string + Shutdown func() + ctx context.Context + internalClient *http.Client + mutex sync.Mutex + timeout time.Duration +} + +type envStatus struct { + Meta struct { + Up bool `json:"up"` + } `json:"meta"` +} + +func Run(m *testing.M, callback func(env *TestEnvironment)) int { + if os.Getenv(resource.EnvTfAcc) == "" { + // short circuit non-acceptance test runs + os.Exit(m.Run()) + } + env := NewTestEnvironment(5 * time.Minute) + return env.run(m, callback) +} + +func NewTestEnvironment(startupTimeout time.Duration) *TestEnvironment { + c := http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }} + ctx := context.Background() + return &TestEnvironment{ + Endpoint: "https://localhost:8443", // default endpoint, assumed + timeout: startupTimeout, + mutex: sync.Mutex{}, + ctx: ctx, + internalClient: &c, + Shutdown: func() {}, + } +} + +func (te *TestEnvironment) isReady() bool { + if st, _ := te.readStatus(te.ctx); st != TestEnvReady { + return false + } + return true +} + +func (te *TestEnvironment) run(m *testing.M, callback func(env *TestEnvironment)) int { + err := te.Start() + defer func() { + te.Shutdown() + }() + if err != nil { + panic(err) + } + err = te.WaitUntilReady() + if err != nil { + panic(err) + } + c, err := te.newTestClient() + if err != nil { + panic(err) + } + te.Client = c + callback(te) + return m.Run() +} + +func (te *TestEnvironment) readStatus(ctx context.Context) (testEnvironmentStatus, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/status", te.Endpoint), nil) + if err != nil { + return TestEnvUnknown, err + } + req = req.WithContext(ctx) + r, err := te.internalClient.Do(req) + if err != nil { + return TestEnvDown, err + } + resp := envStatus{} + err = json.NewDecoder(r.Body).Decode(&resp) + if err != nil { + return TestEnvUnknown, err + } + if resp.Meta.Up { + return TestEnvReady, nil + } + return TestEnvStarting, nil +} + +func findFileInProject(filename string) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + // Walk up the directory tree until we find a file + for { + path := filepath.Join(wd, filename) + if _, err := os.Stat(path); err == nil { + return path, nil + } + if wd == "/" { + break + } + wd = filepath.Dir(wd) + } + return "", fmt.Errorf("file %s not found in project", filename) +} + +func (te *TestEnvironment) startDockerController(ctx context.Context) error { + composeFile, err := findFileInProject("docker-compose.yaml") + if err != nil { + return fmt.Errorf("failed to find docker-compose.yaml file: %w", err) + } + dc, err := compose.NewDockerCompose(composeFile) + shutdown := func() { + if dc != nil { + if err := dc.Down(context.Background(), compose.RemoveOrphans(true), compose.RemoveImagesLocal); err != nil { + panic(err) + } + } + } + te.Shutdown = shutdown + if err != nil { + return err + } + + if err = dc.WithOsEnv().Up(ctx, compose.Wait(true)); err != nil { + return fmt.Errorf("failed to Start docker-compose. Controller container might be already running or starting: %w", err) + } + container, err := dc.ServiceContainer(ctx, "unifi") + if err != nil { + return err + } + + // Dump the container logs on exit. + // + // TODO: Use https://pkg.go.dev/github.com/testcontainers/testcontainers-go#LogConsumer instead. + te.Shutdown = func() { + shutdown() + + if os.Getenv("UNIFI_STDOUT") == "" { + return + } + + stream, err := container.Logs(ctx) + if err != nil { + fmt.Printf("Failed to get logs from container: %v", err) + return + } + + buffer := new(bytes.Buffer) + buffer.ReadFrom(stream) + testcontainers.Logger.Printf("%s", buffer) + } + endpoint, err := container.PortEndpoint(ctx, "8443/tcp", "https") + if err != nil { + return err + } + te.Endpoint = endpoint + return nil +} + +func (te *TestEnvironment) WaitUntilReady() error { + te.mutex.Lock() + ctx, cancel := context.WithTimeoutCause(te.ctx, te.timeout, fmt.Errorf("controller was not ready within %s", te.timeout)) + defer cancel() + defer te.mutex.Unlock() + if st, _ := te.readStatus(ctx); st == TestEnvDown || st == TestEnvUnknown { + return fmt.Errorf("controller is not starting nor running. Use Start() first to Start the controller") + } + te.waitForController(ctx) + if !te.isReady() { + return fmt.Errorf("controller is not ready within %s", te.timeout) + } + return nil +} + +func (te *TestEnvironment) Start() error { + tflog.Error(te.ctx, "Starting test environment") + if te.isReady() { + tflog.Warn(te.ctx, "Environment is already running at "+te.Endpoint) + if te.Client == nil { + c, err := te.newTestClient() + if err != nil { + return err + } + te.Client = c + } + return nil + } + ctx, cancel := context.WithTimeoutCause(te.ctx, te.timeout, fmt.Errorf("controller did not Start within %s", te.timeout)) + defer cancel() + err := te.startDockerController(ctx) + if err != nil { + return err + } + tflog.Info(te.ctx, "Environment is starting at "+te.Endpoint) + return nil +} + +func (te *TestEnvironment) waitForController(ctx context.Context) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + for { + if st, err := te.readStatus(ctx); err != nil { + return + } else if st == TestEnvReady { + wg.Done() + return + } + time.Sleep(1 * time.Second) + } + }() + wg.Wait() +} + +func (te *TestEnvironment) newTestClient() (unifi.Client, error) { + const user = "admin" + const password = "admin" + var err error + if err = os.Setenv("UNIFI_USERNAME", user); err != nil { + return nil, err + } + + if err = os.Setenv("UNIFI_PASSWORD", password); err != nil { + return nil, err + } + + if err = os.Setenv("UNIFI_INSECURE", "true"); err != nil { + return nil, err + } + + if err = os.Setenv("UNIFI_API", te.Endpoint); err != nil { + return nil, err + } + + return unifi.NewClient(&unifi.ClientConfig{ + URL: te.Endpoint, + User: user, + Password: password, + VerifySSL: false, + ValidationMode: unifi.DisableValidation, + Logger: unifi.NewDefaultLogger(unifi.WarnLevel), + }) +} diff --git a/internal/provider/testing/test_helpers.go b/internal/provider/testing/test_helpers.go new file mode 100644 index 0000000..b2d5bdb --- /dev/null +++ b/internal/provider/testing/test_helpers.go @@ -0,0 +1,78 @@ +package testing + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "os" + "strings" + "testing" +) + +// MarkAccTest marks the test as acceptance test. Useful when executing code before resource.ParallelTest or resource.Test +// to bring acceptance test check earlier when test environment is required +func MarkAccTest(t *testing.T) { + t.Helper() + if os.Getenv(resource.EnvTfAcc) == "" { + t.Skipf("Acceptance tests skipped unless env '%s' set", resource.EnvTfAcc) + return + } +} + +func ImportStep(name string, ignore ...string) resource.TestStep { + step := resource.TestStep{ + ResourceName: name, + ImportState: true, + ImportStateVerify: true, + } + + if len(ignore) > 0 { + step.ImportStateVerifyIgnore = ignore + } + + return step +} + +// SiteAndIDImportStateIDFunc returns a function that can be used to import resources that require site and id. +func SiteAndIDImportStateIDFunc(resourceName string) func(*terraform.State) (string, error) { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } + id := rs.Primary.Attributes["id"] + site := rs.Primary.Attributes["site"] + return site + ":" + id, nil + } +} + +// PreCheck checks if provided environment variables are set. If not, it will fail the test. +func PreCheck(t *testing.T) { + variables := []string{ + "UNIFI_USERNAME", + "UNIFI_PASSWORD", + "UNIFI_API", + } + + for _, variable := range variables { + value := os.Getenv(variable) + if value == "" { + t.Fatalf("`%s` must be set for acceptance tests!", variable) + } + } +} + +func CheckPlanPreApply(checks ...plancheck.PlanCheck) resource.ConfigPlanChecks { + return resource.ConfigPlanChecks{ + PreApply: checks, + } +} + +func CheckResourceAction(resourceAddress string, action plancheck.ResourceActionType) resource.ConfigPlanChecks { + return CheckPlanPreApply(plancheck.ExpectResourceAction(resourceAddress, action)) +} + +func ComposeConfig(configs ...string) string { + return strings.Join(configs, "\n") +} diff --git a/internal/provider/v1/mac.go b/internal/provider/testing/test_helpers_network.go similarity index 54% rename from internal/provider/v1/mac.go rename to internal/provider/testing/test_helpers_network.go index 63dd190..710ea2e 100644 --- a/internal/provider/v1/mac.go +++ b/internal/provider/testing/test_helpers_network.go @@ -1,34 +1,48 @@ -package v1 +package testing import ( + "github.com/apparentlymart/go-cidr/cidr" mapset "github.com/deckarep/golang-set/v2" + "math" "net" - "regexp" - "strings" "sync" "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var macAddressRegexp = regexp.MustCompile("^([0-9a-fA-F][0-9a-fA-F][-:]){5}([0-9a-fA-F][0-9a-fA-F])$") - -func cleanMAC(mac string) string { - return strings.TrimSpace(strings.ReplaceAll(strings.ToLower(mac), "-", ":")) -} - -func macDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { - old = cleanMAC(old) - new = cleanMAC(new) - return old == new -} +const ( + vlanMin = 2 + vlanMax = 4095 +) var ( macInit sync.Once macPool = mapset.NewSet[*net.HardwareAddr]() + network = &net.IPNet{ + IP: net.IPv4(10, 0, 0, 0).To4(), + Mask: net.IPv4Mask(255, 0, 0, 0), + } + + vlanLock sync.Mutex + vlanNext = vlanMin ) -func allocateTestMac(t *testing.T) (string, func()) { +func GetTestVLAN(t *testing.T) (*net.IPNet, int) { + vlanLock.Lock() + defer vlanLock.Unlock() + + vlan := vlanNext + vlanNext++ + + subnet, err := cidr.Subnet(network, int(math.Ceil(math.Log2(vlanMax))), vlan) + if err != nil { + t.Error(err) + } + + return subnet, vlan +} + +func AllocateTestMac(t *testing.T) (string, func()) { + MarkAccTest(t) macInit.Do(func() { // for test MAC addresses, see https://tools.ietf.org/html/rfc7042#section-2.1. for i := 0; i < 512; i++ { diff --git a/internal/provider/testing/test_helpers_random.go b/internal/provider/testing/test_helpers_random.go new file mode 100644 index 0000000..a6178f1 --- /dev/null +++ b/internal/provider/testing/test_helpers_random.go @@ -0,0 +1,23 @@ +package testing + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" +) + +func RandHostname() string { + return RandHostnameWithSuffix("test.com") +} + +func RandHostnameWithSuffix(suffix string) string { + return fmt.Sprintf("%s.%s", RandAlpha(10), suffix) +} + +func RandAlpha(len int) string { + return acctest.RandStringFromCharSet(len, acctest.CharSetAlpha) +} + +func RandIpAddress() string { + ip, _ := acctest.RandIpAddress("192.168.0.1/24") + return ip +} diff --git a/internal/provider/testing/test_helpers_regex.go b/internal/provider/testing/test_helpers_regex.go new file mode 100644 index 0000000..d951cbf --- /dev/null +++ b/internal/provider/testing/test_helpers_regex.go @@ -0,0 +1,10 @@ +package testing + +import ( + "fmt" + "regexp" +) + +func MissingArgumentErrorRegex(arg string) *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`%q is required`, arg)) +} diff --git a/internal/provider/v1/data_user.go b/internal/provider/user/data_user.go similarity index 92% rename from internal/provider/v1/data_user.go rename to internal/provider/user/data_user.go index 4a721af..a90edbd 100644 --- a/internal/provider/v1/data_user.go +++ b/internal/provider/user/data_user.go @@ -1,8 +1,9 @@ -package v1 +package user import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -10,7 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func dataUser() *schema.Resource { +func DataUser() *schema.Resource { return &schema.Resource{ Description: "`unifi_user` retrieves properties of a user (or \"client\" in the UI) of the network by MAC address.", @@ -27,8 +28,8 @@ func dataUser() *schema.Resource { Description: "The MAC address of the user.", Type: schema.TypeString, Required: true, - DiffSuppressFunc: macDiffSuppressFunc, - ValidateFunc: validation.StringMatch(macAddressRegexp, "Mac address is invalid"), + DiffSuppressFunc: utils.MacDiffSuppressFunc, + ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"), }, // read-only / computed @@ -92,7 +93,7 @@ func dataUser() *schema.Resource { } func dataUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { diff --git a/internal/provider/v1/data_user_group.go b/internal/provider/user/data_user_group.go similarity index 94% rename from internal/provider/v1/data_user_group.go rename to internal/provider/user/data_user_group.go index 13b44b1..6ffeb1d 100644 --- a/internal/provider/v1/data_user_group.go +++ b/internal/provider/user/data_user_group.go @@ -1,14 +1,13 @@ -package v1 +package user import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider" - + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataUserGroup() *schema.Resource { +func DataUserGroup() *schema.Resource { return &schema.Resource{ Description: "`unifi_user_group` data source can be used to retrieve the ID for a user group by name.", @@ -46,7 +45,7 @@ func dataUserGroup() *schema.Resource { } func dataUserGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) name := d.Get("name").(string) site := d.Get("site").(string) diff --git a/internal/provider/v1/resource_user.go b/internal/provider/user/resource_user.go similarity index 94% rename from internal/provider/v1/resource_user.go rename to internal/provider/user/resource_user.go index df57332..ffe81e8 100644 --- a/internal/provider/v1/resource_user.go +++ b/internal/provider/user/resource_user.go @@ -1,16 +1,17 @@ -package v1 +package user import ( "context" "errors" "github.com/filipowm/go-unifi/unifi" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "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" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceUser() *schema.Resource { +func ResourceUser() *schema.Resource { return &schema.Resource{ Description: "`unifi_user` manages a user (or \"client\" in the UI) of the network, these are identified " + "by unique MAC addresses.\n\n" + @@ -22,7 +23,7 @@ func resourceUser() *schema.Resource { UpdateContext: resourceUserUpdate, DeleteContext: resourceUserDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -43,8 +44,8 @@ func resourceUser() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DiffSuppressFunc: macDiffSuppressFunc, - ValidateFunc: validation.StringMatch(macAddressRegexp, "Mac address is invalid"), + DiffSuppressFunc: utils.MacDiffSuppressFunc, + ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"), }, "name": { Description: "The name of the user.", @@ -119,7 +120,7 @@ func resourceUser() *schema.Resource { } func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceUserGetResourceData(d) if err != nil { @@ -135,7 +136,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf resp, err := c.CreateUser(ctx, site, req) if err != nil { - if !provider.IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { + if !base.IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { return diag.FromErr(err) } @@ -227,7 +228,7 @@ func resourceUserSetResourceData(resp *unifi.User, d *schema.ResourceData, site } func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -263,7 +264,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac } func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) site := d.Get("site").(string) if site == "" { @@ -316,7 +317,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.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/resource_user_group.go b/internal/provider/user/resource_user_group.go similarity index 93% rename from internal/provider/v1/resource_user_group.go rename to internal/provider/user/resource_user_group.go index 3c4cb58..568e20d 100644 --- a/internal/provider/v1/resource_user_group.go +++ b/internal/provider/user/resource_user_group.go @@ -1,16 +1,17 @@ -package v1 +package user import ( "context" "errors" - "github.com/filipowm/terraform-provider-unifi/internal/provider" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/filipowm/go-unifi/unifi" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceUserGroup() *schema.Resource { +func ResourceUserGroup() *schema.Resource { return &schema.Resource{ Description: "`unifi_user_group` manages a user group (called \"client group\" in the UI), which can be used " + "to limit bandwidth for groups of users.", @@ -20,7 +21,7 @@ func resourceUserGroup() *schema.Resource { UpdateContext: resourceUserGroupUpdate, DeleteContext: resourceUserGroupDelete, Importer: &schema.ResourceImporter{ - StateContext: importSiteAndID, + StateContext: utils.ImportSiteAndID, }, Schema: map[string]*schema.Schema{ @@ -60,7 +61,7 @@ func resourceUserGroup() *schema.Resource { } func resourceUserGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) req, err := resourceUserGroupGetResourceData(d) if err != nil { @@ -99,7 +100,7 @@ func resourceUserGroupSetResourceData(resp *unifi.UserGroup, d *schema.ResourceD } func resourceUserGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() @@ -121,7 +122,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.(*provider.Client) + c := meta.(*base.Client) req, err := resourceUserGroupGetResourceData(d) if err != nil { @@ -145,7 +146,7 @@ func resourceUserGroupUpdate(ctx context.Context, d *schema.ResourceData, meta i } func resourceUserGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*provider.Client) + c := meta.(*base.Client) id := d.Id() diff --git a/internal/provider/v1/data_port_profile_test.go b/internal/provider/v1/data_port_profile_test.go deleted file mode 100644 index e688f03..0000000 --- a/internal/provider/v1/data_port_profile_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package v1 - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccDataPortProfile_default(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, "< 7.4") - }, - ProviderFactories: providerFactories, - // TODO: CheckDestroy: , - Steps: []resource.TestStep{ - { - Config: testAccDataPortProfileConfig_default, - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} - -func TestAccDataPortProfile_multiple_providers(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - preCheck(t) - preCheckVersionConstraint(t, "< 7.4") - }, - ProviderFactories: map[string]func() (*schema.Provider, error){ - "unifi2": func() (*schema.Provider, error) { - return New("acctest")(), nil - }, - "unifi3": func() (*schema.Provider, error) { - return New("acctest")(), nil - }, - }, - // TODO: CheckDestroy: , - Steps: []resource.TestStep{ - { - Config: ` - data "unifi_port_profile" "unifi2" { - provider = "unifi2" - } - data "unifi_port_profile" "unifi3" { - provider = "unifi3" - } - `, - Check: resource.ComposeTestCheckFunc( - // testCheckNetworkExists(t, "name"), - ), - }, - }, - }) -} - -const testAccDataPortProfileConfig_default = ` -data "unifi_port_profile" "default" { -} -` diff --git a/internal/provider/v1/data_user_group_test.go b/internal/provider/v1/data_user_group_test.go deleted file mode 100644 index 4fbe1c1..0000000 --- a/internal/provider/v1/data_user_group_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package v1 - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccDataUserGroup_default(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: providerFactories, - // TODO: CheckDestroy: , - Steps: []resource.TestStep{ - { - Config: testAccDataUserGroupConfig_default, - Check: resource.ComposeTestCheckFunc( - // testCheckNetworkExists(t, "name"), - ), - }, - }, - }) -} - -func TestAccDataUserGroup_multiple_providers(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { preCheck(t) }, - ProviderFactories: map[string]func() (*schema.Provider, error){ - "unifi2": func() (*schema.Provider, error) { - return New("acctest")(), nil - }, - "unifi3": func() (*schema.Provider, error) { - return New("acctest")(), nil - }, - }, - // TODO: CheckDestroy: , - Steps: []resource.TestStep{ - { - Config: ` - data "unifi_user_group" "unifi2" { - provider = "unifi2" - } - data "unifi_user_group" "unifi3" { - provider = "unifi3" - } - `, - Check: resource.ComposeTestCheckFunc( - // testCheckNetworkExists(t, "name"), - ), - }, - }, - }) -} - -const testAccDataUserGroupConfig_default = ` -data "unifi_user_group" "default" { -} -` diff --git a/internal/provider/v1/diff_suppressions.go b/internal/provider/v1/diff_suppressions.go deleted file mode 100644 index f93b5c0..0000000 --- a/internal/provider/v1/diff_suppressions.go +++ /dev/null @@ -1,20 +0,0 @@ -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/v1/provider.go b/internal/provider/v1/provider.go deleted file mode 100644 index f805c5f..0000000 --- a/internal/provider/v1/provider.go +++ /dev/null @@ -1,165 +0,0 @@ -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/v1/provider_test.go b/internal/provider/v1/provider_test.go deleted file mode 100644 index fa0a555..0000000 --- a/internal/provider/v1/provider_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package v1 - -import ( - "bytes" - "context" - "fmt" - "math" - "net" - "net/http" - "os" - "sync" - "testing" - - "github.com/apparentlymart/go-cidr/cidr" - "github.com/filipowm/go-unifi/unifi" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/modules/compose" -) - -var providerFactories = map[string]func() (*schema.Provider, error){ - "unifi": func() (*schema.Provider, error) { - return New("acctest")(), nil - }, -} - -var testClient unifi.Client - -func TestMain(m *testing.M) { - if os.Getenv("TF_ACC") == "" { - // short circuit non-acceptance test runs - os.Exit(m.Run()) - } - - os.Exit(runAcceptanceTests(m)) -} - -func runAcceptanceTests(m *testing.M) int { - dc, err := compose.NewDockerCompose("../../../docker-compose.yaml") - if err != nil { - panic(err) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - if err = dc.WithOsEnv().Up(ctx, compose.Wait(true)); err != nil { - panic(err) - } - - defer func() { - if err := dc.Down(context.Background(), compose.RemoveOrphans(true), compose.RemoveImagesLocal); err != nil { - panic(err) - } - }() - - container, err := dc.ServiceContainer(ctx, "unifi") - if err != nil { - panic(err) - } - - // Dump the container logs on exit. - // - // TODO: Use https://pkg.go.dev/github.com/testcontainers/testcontainers-go#LogConsumer instead. - defer func() { - if os.Getenv("UNIFI_STDOUT") == "" { - return - } - - stream, err := container.Logs(ctx) - if err != nil { - panic(err) - } - - buffer := new(bytes.Buffer) - buffer.ReadFrom(stream) - testcontainers.Logger.Printf("%s", buffer) - }() - - endpoint, err := container.PortEndpoint(ctx, "8443/tcp", "https") - if err != nil { - panic(err) - } - - const user = "admin" - const password = "admin" - - if err = os.Setenv("UNIFI_USERNAME", user); err != nil { - panic(err) - } - - if err = os.Setenv("UNIFI_PASSWORD", password); err != nil { - panic(err) - } - - if err = os.Setenv("UNIFI_INSECURE", "true"); err != nil { - panic(err) - } - - if err = os.Setenv("UNIFI_API", endpoint); err != nil { - panic(err) - } - - testClient, err = unifi.NewClient(&unifi.ClientConfig{ - URL: endpoint, - User: user, - Password: password, - HttpRoundTripperProvider: func() http.RoundTripper { - return createHTTPTransport(true, "unifi") - }, - ValidationMode: unifi.DisableValidation, - Logger: unifi.NewDefaultLogger(unifi.WarnLevel), - }) - if err != nil { - panic(err) - } - - return m.Run() -} - -func importStep(name string, ignore ...string) resource.TestStep { - step := resource.TestStep{ - ResourceName: name, - ImportState: true, - ImportStateVerify: true, - } - - if len(ignore) > 0 { - step.ImportStateVerifyIgnore = ignore - } - - return step -} - -func siteAndIDImportStateIDFunc(resourceName string) func(*terraform.State) (string, error) { - return func(s *terraform.State) (string, error) { - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return "", fmt.Errorf("not found: %s", resourceName) - } - networkID := rs.Primary.Attributes["id"] - site := rs.Primary.Attributes["site"] - return site + ":" + networkID, nil - } -} - -func preCheck(t *testing.T) { - variables := []string{ - "UNIFI_USERNAME", - "UNIFI_PASSWORD", - "UNIFI_API", - } - - for _, variable := range variables { - value := os.Getenv(variable) - if value == "" { - t.Fatalf("`%s` must be set for acceptance tests!", variable) - } - } -} - -const ( - vlanMin = 2 - vlanMax = 4095 -) - -var ( - network = &net.IPNet{ - IP: net.IPv4(10, 0, 0, 0).To4(), - Mask: net.IPv4Mask(255, 0, 0, 0), - } - - vlanLock sync.Mutex - vlanNext = vlanMin -) - -func getTestVLAN(t *testing.T) (*net.IPNet, int) { - vlanLock.Lock() - defer vlanLock.Unlock() - - vlan := vlanNext - vlanNext++ - - subnet, err := cidr.Subnet(network, int(math.Ceil(math.Log2(vlanMax))), vlan) - if err != nil { - t.Error(err) - } - - return subnet, vlan -} diff --git a/internal/utils/attribute.go b/internal/utils/attribute.go new file mode 100644 index 0000000..ad74336 --- /dev/null +++ b/internal/utils/attribute.go @@ -0,0 +1,35 @@ +package utils + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +// ID generates an attribute definition suitable for the always-present `id` attribute. +func ID(desc ...string) schema.StringAttribute { + a := schema.StringAttribute{ + Computed: true, + Description: "The unique identifier of this resource.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + } + + if len(desc) > 0 { + a.Description = desc[0] + } + + return a +} + +// ShouldBeRemoved evaluates if an attribute should be removed from the plan during update. +func ShouldBeRemoved(plan attr.Value, state attr.Value, isClone bool) bool { + return !IsDefined(plan) && IsDefined(state) && !isClone +} + +// IsDefined returns true if attribute is known and not null. +func IsDefined(v attr.Value) bool { + return !v.IsNull() && !v.IsUnknown() +} diff --git a/internal/utils/cidr.go b/internal/utils/cidr.go index 68cad28..da0ce75 100644 --- a/internal/utils/cidr.go +++ b/internal/utils/cidr.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "net" ) @@ -38,3 +39,17 @@ func CidrOneBased(cidr string) string { return cidrNet.String() } + +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/v1/importer.go b/internal/utils/importer.go similarity index 81% rename from internal/provider/v1/importer.go rename to internal/utils/importer.go index 4c044f8..ade775f 100644 --- a/internal/provider/v1/importer.go +++ b/internal/utils/importer.go @@ -1,13 +1,12 @@ -package v1 +package utils import ( "context" - "strings" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "strings" ) -func importSiteAndID(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func ImportSiteAndID(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { if id := d.Id(); strings.Contains(id, ":") { importParts := strings.SplitN(id, ":", 2) d.SetId(importParts[1]) diff --git a/internal/utils/mac.go b/internal/utils/mac.go new file mode 100644 index 0000000..84c4387 --- /dev/null +++ b/internal/utils/mac.go @@ -0,0 +1,19 @@ +package utils + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "regexp" + "strings" +) + +var MacAddressRegexp = regexp.MustCompile("^([0-9a-fA-F][0-9a-fA-F][-:]){5}([0-9a-fA-F][0-9a-fA-F])$") + +func CleanMAC(mac string) string { + return strings.TrimSpace(strings.ReplaceAll(strings.ToLower(mac), "-", ":")) +} + +func MacDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { + old = CleanMAC(old) + new = CleanMAC(new) + return old == new +} diff --git a/internal/provider/v1/port_range.go b/internal/utils/port_range.go similarity index 84% rename from internal/provider/v1/port_range.go rename to internal/utils/port_range.go index 379986d..a07d4af 100644 --- a/internal/provider/v1/port_range.go +++ b/internal/utils/port_range.go @@ -1,4 +1,4 @@ -package v1 +package utils import ( "regexp" @@ -7,6 +7,6 @@ import ( ) var ( - portRangeRegexp = regexp.MustCompile("(([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5]))+(,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])){0,14}") - validatePortRange = validation.StringMatch(portRangeRegexp, "invalid port range") + PortRangeRegexp = regexp.MustCompile("(([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5]))+(,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])){0,14}") + ValidatePortRange = validation.StringMatch(PortRangeRegexp, "invalid port range") ) diff --git a/internal/utils/strings.go b/internal/utils/strings.go index 2d7a163..9fedc33 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -33,3 +34,7 @@ func StringSliceToList(list []string) []interface{} { func StringSliceToSet(src []string) *schema.Set { return schema.NewSet(schema.HashString, StringSliceToList(src)) } + +func IsStringValueNotEmpty(s basetypes.StringValue) bool { + return !s.IsUnknown() && !s.IsNull() && s.ValueString() != "" +} diff --git a/main.go b/main.go index 5bcefda..109204b 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,7 @@ package main // import "github.com/filipowm/terraform-provider-unifi" import ( "context" "flag" - v1 "github.com/filipowm/terraform-provider-unifi/internal/provider/v1" - v2 "github.com/filipowm/terraform-provider-unifi/internal/provider/v2" + "github.com/filipowm/terraform-provider-unifi/internal/provider" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -31,7 +30,7 @@ func main() { flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() - p := v1.New(version) + p := provider.New(version) upgradedSdkServer, err := tf5to6server.UpgradeServer( ctx, func() tfprotov5.ProviderServer { @@ -43,7 +42,7 @@ func main() { } providers := []func() tfprotov6.ProviderServer{ - providerserver.NewProtocol6(v2.New(version)()), + providerserver.NewProtocol6(provider.NewV2(version)()), func() tfprotov6.ProviderServer { return upgradedSdkServer },