diff --git a/go.mod b/go.mod index 902d40e..181ca98 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/filipowm/terraform-provider-unifi go 1.23.5 -// replace github.com/filipowm/go-unifi => ../go-unifi +// replace github.com/filipowm/go-unifi v1.5.3 => ../go-unifi // replace github.com/hashicorp/terraform-plugin-docs => ../../hashicorp/terraform-plugin-docs // replace github.com/hashicorp/terraform-plugin-sdk/v2 => ../../hashicorp/terraform-plugin-sdk @@ -10,7 +10,7 @@ require ( github.com/apparentlymart/go-cidr v1.1.0 github.com/biter777/countries v1.7.5 github.com/deckarep/golang-set/v2 v2.7.0 - github.com/filipowm/go-unifi v1.5.2 + github.com/filipowm/go-unifi v1.5.3 github.com/golangci/golangci-lint v1.64.7 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/terraform-plugin-docs v0.21.0 diff --git a/go.sum b/go.sum index 25ea38f..21b6433 100644 --- a/go.sum +++ b/go.sum @@ -278,6 +278,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/filipowm/go-unifi v1.5.2 h1:S974Fi3BWt7qm0P8G/SwUVOjzgWTSjNJGV0jW5yD6K0= github.com/filipowm/go-unifi v1.5.2/go.mod h1:ldtL5szykvR9fPWB9GlGZqaJGxKd8IWLQY5M8O1PDQ8= +github.com/filipowm/go-unifi v1.5.3 h1:9e+V5xzgDtQ6ZmQvIiz8sCuwcDyM06nzbhBQxzvPBbo= +github.com/filipowm/go-unifi v1.5.3/go.mod h1:NQgqx3ylLVDqxVgY3uJ3ZMIecSl46fvqCCXzwHRuVDc= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/internal/provider/acctest/resource_setting_mgmt_test.go b/internal/provider/acctest/resource_setting_mgmt_test.go index c7d0bca..6781fa5 100644 --- a/internal/provider/acctest/resource_setting_mgmt_test.go +++ b/internal/provider/acctest/resource_setting_mgmt_test.go @@ -1,24 +1,33 @@ package acctest import ( - pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" "sync" "testing" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" ) var settingMgmtLock = sync.Mutex{} +const testSettingMgmtResourceName = "unifi_setting_mgmt.test" + func TestAccSettingMgmt_basic(t *testing.T) { AcceptanceTest(t, AcceptanceTestCase{ Lock: &settingMgmtLock, Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_basic(), - Check: resource.ComposeTestCheckFunc(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "site", "default"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + ), + ConfigPlanChecks: pt.CheckResourceActions(testSettingMgmtResourceName, plancheck.ResourceActionCreate), }, - pt.ImportStepWithSite("unifi_setting_mgmt.test"), + pt.ImportStepWithSite(testSettingMgmtResourceName), }, }) } @@ -29,9 +38,14 @@ func TestAccSettingMgmt_site(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_site(), - Check: resource.ComposeTestCheckFunc(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttrPair(testSettingMgmtResourceName, "site", "unifi_site.test", "name"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + ), + ConfigPlanChecks: pt.CheckResourceActions(testSettingMgmtResourceName, plancheck.ResourceActionCreate), }, - pt.ImportStepWithSite("unifi_setting_mgmt.test"), + pt.ImportStepWithSite(testSettingMgmtResourceName), }, }) } @@ -42,9 +56,298 @@ func TestAccSettingMgmt_sshKeys(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccSettingMgmtConfig_sshKeys(), - Check: resource.ComposeTestCheckFunc(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttrPair(testSettingMgmtResourceName, "site", "unifi_site.test", "name"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "1"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Test key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-rsa"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.comment", "test@example.com"), + ), + ConfigPlanChecks: pt.CheckResourceActions(testSettingMgmtResourceName, plancheck.ResourceActionCreate), }, - pt.ImportStepWithSite("unifi_setting_mgmt.test"), + pt.ImportStepWithSite(testSettingMgmtResourceName), + }, + }) +} + +func TestAccSettingMgmt_fullConfig(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: ">= 7.3", + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + Config: testAccSettingMgmtConfig_fullConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "site", "default"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "3"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "advanced_feature_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "alert_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "boot_sound", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "debug_tools_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "direct_connect_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "outdoor_mode_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "unifi_idp_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "wifiman_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_bind_wildcard", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_username", "admin"), + ), + ConfigPlanChecks: pt.CheckResourceActions(testSettingMgmtResourceName, plancheck.ResourceActionCreate), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + }, + }) +} + +func TestAccSettingMgmt_update(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + Config: testAccSettingMgmtConfig_initialConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "3"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + Config: testAccSettingMgmtConfig_updatedConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "5"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "false"), + ), + }, + }, + }) +} + +func TestAccSettingMgmt_sshCredentials(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + Config: testAccSettingMgmtConfig_sshCredentials(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_username", "admin"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_password", "securepassword"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + }, + }) +} + +func TestAccSettingMgmt_cornerCases(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + // Initial configuration with specific values + Config: testAccSettingMgmtConfig_cornerCasesInitial(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "site", "default"), + // Boolean attributes - initial values + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "alert_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "boot_sound", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "direct_connect_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "outdoor_mode_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "unifi_idp_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "wifiman_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_bind_wildcard", "true"), + // Numeric values - initial + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "3"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Toggle all boolean values and change numeric values + Config: testAccSettingMgmtConfig_cornerCasesToggled(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + // Boolean attributes - toggled values + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "alert_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "boot_sound", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "direct_connect_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "outdoor_mode_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "unifi_idp_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "wifiman_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_bind_wildcard", "false"), + // Numeric values - changed + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "23"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Test boundary values for numeric fields and mixed boolean values + Config: testAccSettingMgmtConfig_cornerCasesBoundary(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + // Mixed boolean values + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "alert_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "boot_sound", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "direct_connect_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "led_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "outdoor_mode_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "unifi_idp_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "wifiman_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_bind_wildcard", "true"), + // Boundary value for auto_upgrade_hour (1 - minimum value) + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "auto_upgrade_hour", "0"), + ), + }, + }, + }) +} + +func TestAccSettingMgmt_sshKeyManagement(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + // Initial configuration with one SSH key + Config: testAccSettingMgmtConfig_sshKeyManagementInitial(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "1"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Initial key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-rsa"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.key", "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.comment", "initial@example.com"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Add a second SSH key and modify the first one + Config: testAccSettingMgmtConfig_sshKeyManagementModified(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "2"), + // First key is modified + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Modified key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-rsa"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.key", "AAAAB3NzaC1yc2EAAAADAQABAAABAQC1"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.comment", "modified@example.com"), + // Second key is added + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.1.name", "Additional key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.1.type", "ssh-ed25519"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.1.key", "AAAAC3NzaC1lZDI1NTE5AAAAIG"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.1.comment", "additional@example.com"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Remove the first key, keep the second key + Config: testAccSettingMgmtConfig_sshKeyManagementRemoved(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "1"), + // Only the second key remains + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Additional key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-ed25519"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.key", "AAAAC3NzaC1lZDI1NTE5AAAAIG"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.comment", "additional@example.com"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Remove all SSH keys + Config: testAccSettingMgmtConfig_sshKeyManagementNoKeys(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "0"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + }, + }) +} + +func TestAccSettingMgmt_sshAuthModes(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + Lock: &settingMgmtLock, + Steps: []resource.TestStep{ + { + // Initial configuration with SSH password authentication enabled + Config: testAccSettingMgmtConfig_sshAuthModesPasswordOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_username", "admin"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_password", "password123"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "0"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Switch to SSH key authentication only + Config: testAccSettingMgmtConfig_sshAuthModesKeyOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "false"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "1"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Auth key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-rsa"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Enable both authentication methods + Config: testAccSettingMgmtConfig_sshAuthModesBoth(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_auth_password_enabled", "true"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_username", "admin"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_password", "newpassword"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.#", "1"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.name", "Auth key"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_key.0.type", "ssh-rsa"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), + { + // Disable SSH entirely + Config: testAccSettingMgmtConfig_sshAuthModesDisabled(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(testSettingMgmtResourceName, "id"), + resource.TestCheckResourceAttr(testSettingMgmtResourceName, "ssh_enabled", "false"), + ), + }, + pt.ImportStepWithSite(testSettingMgmtResourceName), }, }) } @@ -88,3 +391,222 @@ resource "unifi_setting_mgmt" "test" { } ` } + +func testAccSettingMgmtConfig_fullConfig() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = true + auto_upgrade_hour = 3 + advanced_feature_enabled = true + alert_enabled = true + boot_sound = false + debug_tools_enabled = true + direct_connect_enabled = false + led_enabled = true + outdoor_mode_enabled = false + unifi_idp_enabled = false + wifiman_enabled = true + ssh_enabled = true + ssh_auth_password_enabled = true + ssh_bind_wildcard = false + ssh_username = "admin" +} +` +} + +func testAccSettingMgmtConfig_initialConfig() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = true + auto_upgrade_hour = 3 + led_enabled = true + ssh_enabled = true +} +` +} + +func testAccSettingMgmtConfig_updatedConfig() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = false + auto_upgrade_hour = 5 + led_enabled = false + ssh_enabled = false +} +` +} + +func testAccSettingMgmtConfig_sshCredentials() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_auth_password_enabled = true + ssh_username = "admin" + ssh_password = "securepassword" +} +` +} + +func testAccSettingMgmtConfig_cornerCasesInitial() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = true + auto_upgrade_hour = 3 + alert_enabled = true + boot_sound = true + direct_connect_enabled = false + led_enabled = true + outdoor_mode_enabled = true + unifi_idp_enabled = false + wifiman_enabled = true + ssh_enabled = true + ssh_auth_password_enabled = true + ssh_bind_wildcard = true +} +` +} + +func testAccSettingMgmtConfig_cornerCasesToggled() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = false + auto_upgrade_hour = 23 + alert_enabled = false + boot_sound = false + direct_connect_enabled = false + led_enabled = false + outdoor_mode_enabled = false + unifi_idp_enabled = false + wifiman_enabled = false + ssh_enabled = false + ssh_auth_password_enabled = false + ssh_bind_wildcard = false +} +` +} + +func testAccSettingMgmtConfig_cornerCasesBoundary() string { + return ` +resource "unifi_setting_mgmt" "test" { + auto_upgrade = true + auto_upgrade_hour = 0 + alert_enabled = true + boot_sound = false + direct_connect_enabled = false + led_enabled = true + outdoor_mode_enabled = false + unifi_idp_enabled = false + wifiman_enabled = true + ssh_enabled = true + ssh_auth_password_enabled = false + ssh_bind_wildcard = true +} +` +} + +func testAccSettingMgmtConfig_sshKeyManagementInitial() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_key { + name = "Initial key" + type = "ssh-rsa" + key = "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0" + comment = "initial@example.com" + } +} +` +} + +func testAccSettingMgmtConfig_sshKeyManagementModified() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_key { + name = "Modified key" + type = "ssh-rsa" + key = "AAAAB3NzaC1yc2EAAAADAQABAAABAQC1" + comment = "modified@example.com" + } + ssh_key { + name = "Additional key" + type = "ssh-ed25519" + key = "AAAAC3NzaC1lZDI1NTE5AAAAIG" + comment = "additional@example.com" + } +} +` +} + +func testAccSettingMgmtConfig_sshKeyManagementRemoved() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_key { + name = "Additional key" + type = "ssh-ed25519" + key = "AAAAC3NzaC1lZDI1NTE5AAAAIG" + comment = "additional@example.com" + } +} +` +} + +func testAccSettingMgmtConfig_sshKeyManagementNoKeys() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true +} +` +} + +func testAccSettingMgmtConfig_sshAuthModesPasswordOnly() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_auth_password_enabled = true + ssh_username = "admin" + ssh_password = "password123" +} +` +} + +func testAccSettingMgmtConfig_sshAuthModesKeyOnly() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_auth_password_enabled = false + ssh_key { + name = "Auth key" + type = "ssh-rsa" + key = "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0" + comment = "auth@example.com" + } +} +` +} + +func testAccSettingMgmtConfig_sshAuthModesBoth() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = true + ssh_auth_password_enabled = true + ssh_username = "admin" + ssh_password = "newpassword" + ssh_key { + name = "Auth key" + type = "ssh-rsa" + key = "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0" + comment = "auth@example.com" + } +} +` +} + +func testAccSettingMgmtConfig_sshAuthModesDisabled() string { + return ` +resource "unifi_setting_mgmt" "test" { + ssh_enabled = false +} +` +} diff --git a/internal/provider/settings/resource_setting_mgmt.go b/internal/provider/settings/resource_setting_mgmt.go index 388efac..98f317d 100644 --- a/internal/provider/settings/resource_setting_mgmt.go +++ b/internal/provider/settings/resource_setting_mgmt.go @@ -3,6 +3,9 @@ package settings import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/path" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -12,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -37,9 +41,23 @@ func (m *SshKeyModel) AttributeTypes() map[string]attr.Type { // mgmtModel represents the data model for management settings. type mgmtModel struct { base.Model - AutoUpgrade types.Bool `tfsdk:"auto_upgrade"` - SshEnabled types.Bool `tfsdk:"ssh_enabled"` - SshKeys types.List `tfsdk:"ssh_key"` + AdvancedFeatureEnabled types.Bool `tfsdk:"advanced_feature_enabled"` + AlertEnabled types.Bool `tfsdk:"alert_enabled"` + AutoUpgrade types.Bool `tfsdk:"auto_upgrade"` + AutoUpgradeHour types.Int32 `tfsdk:"auto_upgrade_hour"` + BootSound types.Bool `tfsdk:"boot_sound"` + DebugToolsEnabled types.Bool `tfsdk:"debug_tools_enabled"` + DirectConnectEnabled types.Bool `tfsdk:"direct_connect_enabled"` + LedEnabled types.Bool `tfsdk:"led_enabled"` + OutdoorModeEnabled types.Bool `tfsdk:"outdoor_mode_enabled"` + UnifiIdpEnabled types.Bool `tfsdk:"unifi_idp_enabled"` + WifimanEnabled types.Bool `tfsdk:"wifiman_enabled"` + SshAuthPasswordEnabled types.Bool `tfsdk:"ssh_auth_password_enabled"` + SshBindWildcard types.Bool `tfsdk:"ssh_bind_wildcard"` + SshKeys types.List `tfsdk:"ssh_key"` + SshPassword types.String `tfsdk:"ssh_password"` + SshEnabled types.Bool `tfsdk:"ssh_enabled"` + SshUsername types.String `tfsdk:"ssh_username"` } func (m *mgmtModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnostics) { @@ -52,11 +70,25 @@ func (m *mgmtModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnos } return &unifi.SettingMgmt{ - ID: m.ID.ValueString(), - Key: unifi.SettingMgmtKey, - AutoUpgrade: m.AutoUpgrade.ValueBool(), - XSshEnabled: m.SshEnabled.ValueBool(), - XSshKeys: sshKeys, + ID: m.ID.ValueString(), + Key: unifi.SettingMgmtKey, + AutoUpgrade: m.AutoUpgrade.ValueBool(), + AutoUpgradeHour: int(m.AutoUpgradeHour.ValueInt32()), + AdvancedFeatureEnabled: m.AdvancedFeatureEnabled.ValueBool(), + AlertEnabled: m.AlertEnabled.ValueBool(), + BootSound: m.BootSound.ValueBool(), + DebugToolsEnabled: m.DebugToolsEnabled.ValueBool(), + DirectConnectEnabled: m.DirectConnectEnabled.ValueBool(), + LedEnabled: m.LedEnabled.ValueBool(), + OutdoorModeEnabled: m.OutdoorModeEnabled.ValueBool(), + UnifiIDpEnabled: m.UnifiIdpEnabled.ValueBool(), + WifimanEnabled: m.WifimanEnabled.ValueBool(), + XSshEnabled: m.SshEnabled.ValueBool(), + XSshAuthPasswordEnabled: m.SshAuthPasswordEnabled.ValueBool(), + XSshBindWildcard: m.SshBindWildcard.ValueBool(), + XSshUsername: m.SshUsername.ValueString(), + XSshPassword: m.SshPassword.ValueString(), + XSshKeys: sshKeys, }, diags } @@ -96,7 +128,21 @@ func (m *mgmtModel) Merge(ctx context.Context, other interface{}) diag.Diagnosti m.ID = types.StringValue(resp.ID) m.AutoUpgrade = types.BoolValue(resp.AutoUpgrade) + m.AutoUpgradeHour = types.Int32Value(int32(resp.AutoUpgradeHour)) + m.AdvancedFeatureEnabled = types.BoolValue(resp.AdvancedFeatureEnabled) + m.AlertEnabled = types.BoolValue(resp.AlertEnabled) + m.BootSound = types.BoolValue(resp.BootSound) + m.DebugToolsEnabled = types.BoolValue(resp.DebugToolsEnabled) + m.DirectConnectEnabled = types.BoolValue(resp.DirectConnectEnabled) + m.LedEnabled = types.BoolValue(resp.LedEnabled) + m.OutdoorModeEnabled = types.BoolValue(resp.OutdoorModeEnabled) + m.UnifiIdpEnabled = types.BoolValue(resp.UnifiIDpEnabled) + m.WifimanEnabled = types.BoolValue(resp.WifimanEnabled) m.SshEnabled = types.BoolValue(resp.XSshEnabled) + m.SshAuthPasswordEnabled = types.BoolValue(resp.XSshAuthPasswordEnabled) + m.SshBindWildcard = types.BoolValue(resp.XSshBindWildcard) + m.SshUsername = types.StringValue(resp.XSshUsername) + m.SshPassword = types.StringValue(resp.XSshPassword) // Convert SSH keys if len(resp.XSshKeys) > 0 { @@ -138,15 +184,20 @@ func NewMgmtResource() resource.Resource { } var ( - _ base.ResourceModel = &mgmtModel{} - _ resource.Resource = &mgmtResource{} - _ resource.ResourceWithConfigure = &mgmtResource{} + _ base.ResourceModel = &mgmtModel{} + _ resource.Resource = &mgmtResource{} + _ resource.ResourceWithConfigure = &mgmtResource{} + _ resource.ResourceWithModifyPlan = &mgmtResource{} ) type mgmtResource struct { *base.GenericResource[*mgmtModel] } +func (r *mgmtResource) ModifyPlan(_ context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + resp.Diagnostics.Append(r.RequireMinVersionForPath("7.3", path.Root("debug_tools_enabled"), req.Config)...) +} + func (r *mgmtResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "The `unifi_setting_mgmt` resource manages site-wide management settings in the UniFi controller.\n\n" + @@ -166,6 +217,90 @@ func (r *mgmtResource) Schema(_ context.Context, _ resource.SchemaRequest, resp MarkdownDescription: "Enable automatic firmware upgrades for all UniFi devices at this site. When enabled, devices will automatically " + "update to the latest stable firmware version approved for your controller version.", Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "auto_upgrade_hour": schema.Int32Attribute{ + MarkdownDescription: "The hour of the day (0-23) when automatic firmware upgrades will occur.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), + }, + Validators: []validator.Int32{ + int32validator.Between(0, 23), + }, + }, + "advanced_feature_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable advanced features for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "alert_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable alerts for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "boot_sound": schema.BoolAttribute{ + MarkdownDescription: "Enable the boot sound for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "debug_tools_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable debug tools for UniFi devices at this site. Requires controller version 7.3 or later.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "direct_connect_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable direct connect for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "led_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable the LED light for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "outdoor_mode_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable outdoor mode for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "unifi_idp_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable UniFi IDP for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "wifiman_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable WiFiman for UniFi devices at this site.", + Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{ boolplanmodifier.UseStateForUnknown(), }, @@ -174,10 +309,44 @@ func (r *mgmtResource) Schema(_ context.Context, _ resource.SchemaRequest, resp MarkdownDescription: "Enable SSH access to UniFi devices at this site. When enabled, you can connect to devices using SSH for advanced " + "configuration and troubleshooting. It's recommended to only enable this temporarily when needed.", Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{ boolplanmodifier.UseStateForUnknown(), }, }, + "ssh_auth_password_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable SSH password authentication for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "ssh_bind_wildcard": schema.BoolAttribute{ + MarkdownDescription: "Enable SSH bind wildcard for UniFi devices at this site.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "ssh_username": schema.StringAttribute{ + MarkdownDescription: "The SSH username for UniFi devices at this site.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "ssh_password": schema.StringAttribute{ + MarkdownDescription: "The SSH password for UniFi devices at this site.", + Optional: true, + Computed: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, }, Blocks: map[string]schema.Block{ "ssh_key": schema.ListNestedBlock{