From 636ab5fa840890e1daecba7ae964f50fc6563740 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Mon, 10 Nov 2025 17:24:38 +0000 Subject: [PATCH 01/28] fix: cmk drift fix --- go.mod | 2 +- .../resource_rediscloud_pro_subscription.go | 30 +++++++++++-------- ...e_rediscloud_active_active_subscription.go | 28 +++++++++-------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 5b668819..deabd513 100644 --- a/go.mod +++ b/go.mod @@ -71,4 +71,4 @@ require ( ) // for local development, uncomment this -//replace github.com/RedisLabs/rediscloud-go-api => ../rediscloud-go-api +replace github.com/RedisLabs/rediscloud-go-api => ../rediscloud-go-api diff --git a/provider/pro/resource_rediscloud_pro_subscription.go b/provider/pro/resource_rediscloud_pro_subscription.go index c0293f3f..f1d12e22 100644 --- a/provider/pro/resource_rediscloud_pro_subscription.go +++ b/provider/pro/resource_rediscloud_pro_subscription.go @@ -496,25 +496,15 @@ func ResourceRedisCloudProSubscription() *schema.Resource { Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. Defaults to false.", Type: schema.TypeBool, Optional: true, + Computed: true, Default: false, }, "customer_managed_key_deletion_grace_period": { Description: "The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.", Type: schema.TypeString, Optional: true, + Computed: true, Default: "immediate", - // TODO: remove this when customer_managed_key_deletion_grace_period is supported on api side - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Only suppress diff when: - // 1. Old is empty (upgrading from provider version without this field) - // 2. New is the default value "immediate" - // 3. CMK is NOT being enabled (customer_managed_key_enabled is false) - if old == "" && new == "immediate" { - cmkEnabled := d.Get("customer_managed_key_enabled").(bool) - return !cmkEnabled - } - return false - }, }, "customer_managed_key": { Description: "CMK resources used to encrypt the databases in this subscription. Ignored if `customer_managed_key_enabled` set to false. Supply after the database has been put into database pending state. See documentation for CMK flow.", @@ -760,7 +750,14 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } - cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + // Determine CMK status from API response + // CMK is enabled if persistentStorageEncryptionType is "customer-managed-key" + cmkEnabled := subscription.PersistentStorageEncryptionType != nil && + *subscription.PersistentStorageEncryptionType == CMK_ENABLED_STRING + if err := d.Set("customer_managed_key_enabled", cmkEnabled); err != nil { + return diag.FromErr(err) + } + if !cmkEnabled { m, err := api.Client.Maintenance.Get(ctx, subId) if err != nil { @@ -785,6 +782,13 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } + // Set customer_managed_key_deletion_grace_period from API response if available + if subscription.DeletionGracePeriod != nil { + if err := d.Set("customer_managed_key_deletion_grace_period", redis.StringValue(subscription.DeletionGracePeriod)); err != nil { + return diag.FromErr(err) + } + } + // Set public_endpoint_access, default to true if not set by API publicEndpointAccess := true if subscription.PublicEndpointAccess != nil { diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 57dd0c8b..1b400d64 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -300,24 +300,15 @@ func resourceRedisCloudActiveActiveSubscription() *schema.Resource { Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. Defaults to false.", Type: schema.TypeBool, Optional: true, + Computed: true, Default: false, }, "customer_managed_key_deletion_grace_period": { Description: "The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.", Type: schema.TypeString, Optional: true, + Computed: true, Default: "immediate", - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Only suppress diff when: - // 1. Old is empty (upgrading from provider version without this field) - // 2. New is the default value "immediate" - // 3. CMK is NOT being enabled (customer_managed_key_enabled is false) - if old == "" && new == "immediate" { - cmkEnabled := d.Get("customer_managed_key_enabled").(bool) - return !cmkEnabled - } - return false - }, }, "customer_managed_key": { Description: "CMK resources used to encrypt the databases in this subscription. Ignored if `customer_managed_key_enabled` set to false. See documentation for CMK flow.", @@ -523,7 +514,13 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche } } - cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + // Determine CMK status from API response + // CMK is enabled if persistentStorageEncryptionType is "customer-managed-key" + cmkEnabled := subscription.PersistentStorageEncryptionType != nil && + *subscription.PersistentStorageEncryptionType == pro.CMK_ENABLED_STRING + if err := d.Set("customer_managed_key_enabled", cmkEnabled); err != nil { + return diag.FromErr(err) + } if !cmkEnabled { m, err := api.Client.Maintenance.Get(ctx, subId) @@ -549,6 +546,13 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche } } + // Set customer_managed_key_deletion_grace_period from API response if available + if subscription.DeletionGracePeriod != nil { + if err := d.Set("customer_managed_key_deletion_grace_period", redis.StringValue(subscription.DeletionGracePeriod)); err != nil { + return diag.FromErr(err) + } + } + // Set public_endpoint_access, default to true if not set by API publicEndpointAccess := true if subscription.PublicEndpointAccess != nil { From 5801b313cb0000869dbd07a95a633e67bf1875ca Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 11:10:25 +0000 Subject: [PATCH 02/28] fix: remove default from cmk fields as can't have computed and default --- provider/pro/resource_rediscloud_pro_subscription.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/provider/pro/resource_rediscloud_pro_subscription.go b/provider/pro/resource_rediscloud_pro_subscription.go index f1d12e22..41261b73 100644 --- a/provider/pro/resource_rediscloud_pro_subscription.go +++ b/provider/pro/resource_rediscloud_pro_subscription.go @@ -497,14 +497,12 @@ func ResourceRedisCloudProSubscription() *schema.Resource { Type: schema.TypeBool, Optional: true, Computed: true, - Default: false, }, "customer_managed_key_deletion_grace_period": { Description: "The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.", Type: schema.TypeString, Optional: true, Computed: true, - Default: "immediate", }, "customer_managed_key": { Description: "CMK resources used to encrypt the databases in this subscription. Ignored if `customer_managed_key_enabled` set to false. Supply after the database has been put into database pending state. See documentation for CMK flow.", From 33072ce67a93b5f36c5881deb8e79c70ef02bd41 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 11:23:56 +0000 Subject: [PATCH 03/28] fix: also update AA subscription and schema description --- provider/pro/resource_rediscloud_pro_subscription.go | 4 ++-- provider/resource_rediscloud_active_active_subscription.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/provider/pro/resource_rediscloud_pro_subscription.go b/provider/pro/resource_rediscloud_pro_subscription.go index 41261b73..6f5dcdc7 100644 --- a/provider/pro/resource_rediscloud_pro_subscription.go +++ b/provider/pro/resource_rediscloud_pro_subscription.go @@ -493,13 +493,13 @@ func ResourceRedisCloudProSubscription() *schema.Resource { }, }, "customer_managed_key_enabled": { - Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. Defaults to false.", + Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. When not specified, defaults to false.", Type: schema.TypeBool, Optional: true, Computed: true, }, "customer_managed_key_deletion_grace_period": { - Description: "The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.", + Description: "The grace period for deleting the subscription. When not specified, defaults to immediate deletion.", Type: schema.TypeString, Optional: true, Computed: true, diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 1b400d64..e5ad0d75 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -297,18 +297,16 @@ func resourceRedisCloudActiveActiveSubscription() *schema.Resource { }, }, "customer_managed_key_enabled": { - Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. Defaults to false.", + Description: "Whether to enable CMK (customer managed key) for the subscription. If this is true, then the subscription will be put in a pending state until you supply the CMEK. See documentation for further details on this process. When not specified, defaults to false.", Type: schema.TypeBool, Optional: true, Computed: true, - Default: false, }, "customer_managed_key_deletion_grace_period": { - Description: "The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.", + Description: "The grace period for deleting the subscription. When not specified, defaults to immediate deletion.", Type: schema.TypeString, Optional: true, Computed: true, - Default: "immediate", }, "customer_managed_key": { Description: "CMK resources used to encrypt the databases in this subscription. Ignored if `customer_managed_key_enabled` set to false. See documentation for CMK flow.", From a646e031b19891ec4b04c6170fe6d3edca058517 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 12:13:30 +0000 Subject: [PATCH 04/28] docs: fixed incorrect documentation regarding aws_account_id --- docs/data-sources/rediscloud_subscription.md | 2 +- docs/resources/rediscloud_subscription.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/rediscloud_subscription.md b/docs/data-sources/rediscloud_subscription.md index f396d21c..4a91ae12 100644 --- a/docs/data-sources/rediscloud_subscription.md +++ b/docs/data-sources/rediscloud_subscription.md @@ -32,7 +32,6 @@ output "rediscloud_subscription" { `id` is set to the ID of the found subscription. -* `aws_account_id` - AWS account ID that the subscription is deployed in (AWS subscriptions only). * `payment_method_id` - A valid payment method pre-defined in the current account * `memory_storage` - Memory storage preference: either 'ram' or a combination of 'ram-and-flash' * `cloud_provider` - A cloud provider object, documented below @@ -45,6 +44,7 @@ The `cloud_provider` block supports: * `provider` - The cloud provider to use with the subscription, (either `AWS` or `GCP`) * `cloud_account_id` - Cloud account identifier, (A Cloud Account Id = 1 implies using Redis Labs internal cloud account) +* `aws_account_id` - AWS account ID that the subscription is deployed in (AWS subscriptions only). * `region` - Cloud networking details, per region (single region or multiple regions for Active-Active cluster only), documented below The cloud_provider `region` block supports: diff --git a/docs/resources/rediscloud_subscription.md b/docs/resources/rediscloud_subscription.md index d1b1e403..f8e7198a 100644 --- a/docs/resources/rediscloud_subscription.md +++ b/docs/resources/rediscloud_subscription.md @@ -151,9 +151,12 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d ## Attribute reference -* `aws_account_id` - AWS account ID that the subscription is deployed in (AWS subscriptions only). * `customer_managed_key_redis_service_account` - Outputs the id of the service account associated with the subscription. Useful as part of the CMK flow. +The `cloud_provider` block has these attributes: + +* `aws_account_id` - AWS account ID that the subscription is deployed in (AWS subscriptions only). + The `region` block has these attributes: * `networks` - List of generated network configuration From 57a885c4f28e68c6bfa0a886c58cf1bd10406a24 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 11:15:36 +0000 Subject: [PATCH 05/28] fix: removing override for default users so that users do not have to set false explicitly --- .../enable_default_user_all_explicit.tf | 58 +++++ ...e_default_user_global_false_region_true.tf | 56 +++++ ...enable_default_user_global_true_inherit.tf | 54 +++++ ...e_default_user_global_true_region_false.tf | 56 +++++ ...ctive_database_enable_default_user_test.go | 112 ++++++++++ ...ource_rediscloud_active_active_database.go | 203 ++++++++++++++++-- 6 files changed, 527 insertions(+), 12 deletions(-) create mode 100644 provider/activeactive/testdata/enable_default_user_all_explicit.tf create mode 100644 provider/activeactive/testdata/enable_default_user_global_false_region_true.tf create mode 100644 provider/activeactive/testdata/enable_default_user_global_true_inherit.tf create mode 100644 provider/activeactive/testdata/enable_default_user_global_true_region_false.tf create mode 100644 provider/rediscloud_active_active_database_enable_default_user_test.go diff --git a/provider/activeactive/testdata/enable_default_user_all_explicit.tf b/provider/activeactive/testdata/enable_default_user_all_explicit.tf new file mode 100644 index 00000000..a9b934c0 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_all_explicit.tf @@ -0,0 +1,58 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # us-east-1 explicitly set to true (matches global but is EXPLICIT) + # This tests that explicit values are preserved even when matching global + override_region { + name = "us-east-1" + enable_default_user = true + } + + # us-east-2 explicitly set to false (differs from global) + override_region { + name = "us-east-2" + enable_default_user = false + } +} diff --git a/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf b/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf new file mode 100644 index 00000000..a997516d --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf @@ -0,0 +1,56 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is false + global_enable_default_user = false + global_password = local.password + + # us-east-1 explicitly enables default user (differs from global) + override_region { + name = "us-east-1" + enable_default_user = true + } + + # us-east-2 inherits from global (false) - NO enable_default_user specified + override_region { + name = "us-east-2" + } +} diff --git a/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf b/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf new file mode 100644 index 00000000..ea293979 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf @@ -0,0 +1,54 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true (default behavior) + global_enable_default_user = true + global_password = local.password + + # Both regions inherit from global - NO enable_default_user specified + override_region { + name = "us-east-1" + } + + override_region { + name = "us-east-2" + } +} diff --git a/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf b/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf new file mode 100644 index 00000000..8d78e4e5 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf @@ -0,0 +1,56 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # us-east-1 explicitly disables default user (differs from global) + override_region { + name = "us-east-1" + enable_default_user = false + } + + # us-east-2 inherits from global - NO enable_default_user specified + override_region { + name = "us-east-2" + } +} diff --git a/provider/rediscloud_active_active_database_enable_default_user_test.go b/provider/rediscloud_active_active_database_enable_default_user_test.go new file mode 100644 index 00000000..643f8ab6 --- /dev/null +++ b/provider/rediscloud_active_active_database_enable_default_user_test.go @@ -0,0 +1,112 @@ +package provider + +import ( + "fmt" + "os" + "testing" + + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser tests the enable_default_user field +// for global and regional override behavior, specifically testing for drift issues when: +// - Regions inherit from global (field NOT in override_region) +// - Regions explicitly override global (field IS in override_region) +// - User explicitly sets same value as global (field IS in override_region, tests explicit vs inherited) +func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing.T) { + subscriptionName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") + "-enable-default-user" + databaseName := "tf-test-enable-default-user" + databasePassword := "ThisIs!ATestPassword123" + + const databaseResourceName = "rediscloud_active_active_subscription_database.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckActiveActiveSubscriptionDestroy, + Steps: []resource.TestStep{ + // Step 1: global=true, both regions inherit (NO enable_default_user in override_region) + { + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_true_inherit.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + + // Both regions should exist + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // Neither region should have enable_default_user in state (inheriting from global) + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.0.enable_default_user"), + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.1.enable_default_user"), + ), + }, + // Step 2: global=true, us-east-1 explicit false (field SHOULD appear in override_region) + { + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_true_region_false.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit false - need to find which index it is + // We check both indices and one should have enable_default_user=false + // Note: TypeSet ordering is non-deterministic, so we check both indices + ), + }, + // Step 3: global=false, us-east-1 explicit true (field SHOULD appear in override_region) + { + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_false_region_true.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "false"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit true - will appear in one of the indices + ), + }, + // Step 4: global=true, both regions explicit (us-east-1=true, us-east-2=false) + // This tests that explicit values matching global are still preserved + { + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_all_explicit.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // Both regions have explicit enable_default_user + // us-east-1 has true (matches global but explicit) + // us-east-2 has false (differs from global) + // Due to TypeSet non-deterministic ordering, we can't check specific indices + // But the key point is both should have the field in state + ), + }, + }, + }) +} diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index 8d6ed45f..76031ba2 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -12,6 +12,7 @@ import ( "github.com/RedisLabs/terraform-provider-rediscloud/provider/client" "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -260,10 +261,9 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Optional: true, }, "enable_default_user": { - Description: "When 'true', enables connecting to the database with the 'default' user. Default: 'true'", + Description: "When 'true', enables connecting to the database with the 'default' user. If not specified, the region inherits the value from global_enable_default_user.", Type: schema.TypeBool, Optional: true, - Default: true, }, "remote_backup": { Description: "An object that specifies the backup options for the database in this region", @@ -631,9 +631,58 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R regionDbConfig["remote_backup"] = pro.FlattenBackupPlan(regionDb.Backup, getStateRemoteBackup(d, region), "") - // Only set enable_default_user if it was explicitly configured in the override_region - if stateEnableDefaultUser := getStateOverrideRegion(d, region)["enable_default_user"]; stateEnableDefaultUser != nil { - regionDbConfig["enable_default_user"] = redis.BoolValue(regionDb.Security.EnableDefaultUser) + // Handle enable_default_user with hybrid GetRawConfig/GetRawState approach + // to avoid drift issues with TypeSet materialization + if regionDb.Security.EnableDefaultUser != nil { + globalEnableDefaultUser := d.Get("global_enable_default_user").(bool) + regionEnableDefaultUser := redis.BoolValue(regionDb.Security.EnableDefaultUser) + + log.Printf("[DEBUG] Read enable_default_user for region %s: region=%v, global=%v", region, regionEnableDefaultUser, globalEnableDefaultUser) + + // Check if GetRawConfig is available (during Apply/Update) + rawConfig := d.GetRawConfig() + getRawConfigAvailable := !rawConfig.IsNull() && rawConfig.IsKnown() + + shouldInclude := false + var reason string + + if getRawConfigAvailable { + // Config-based mode: Check if explicitly set in config + wasExplicitlySet := isEnableDefaultUserExplicitlySetInConfig(d, region) + log.Printf("[DEBUG] Config-based detection for region %s: wasExplicitlySet=%v", region, wasExplicitlySet) + + if wasExplicitlySet { + shouldInclude = true + reason = "explicitly set in config" + } else if regionEnableDefaultUser != globalEnableDefaultUser { + shouldInclude = true + reason = "differs from global (API override)" + } else { + shouldInclude = false + reason = "not in config and matches global (inherited)" + } + } else { + // State-based mode: Check if was in actual persisted state + fieldWasInActualState := isEnableDefaultUserInActualPersistedState(d, region) + log.Printf("[DEBUG] State-based detection for region %s: fieldWasInActualState=%v", region, fieldWasInActualState) + + if fieldWasInActualState { + shouldInclude = true + reason = "was in state, preserving (user explicit)" + } else if regionEnableDefaultUser != globalEnableDefaultUser { + shouldInclude = true + reason = "not in state but differs from global (API override)" + } else { + shouldInclude = false + reason = "not in state and matches global (inherited)" + } + } + + log.Printf("[DEBUG] enable_default_user decision for region %s: shouldInclude=%v, reason=%s", region, shouldInclude, reason) + + if shouldInclude { + regionDbConfig["enable_default_user"] = regionEnableDefaultUser + } } regionDbConfigs = append(regionDbConfigs, regionDbConfig) @@ -755,9 +804,18 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema Region: redis.String(dbRegion["name"].(string)), } - // Only set EnableDefaultUser if it was explicitly configured in the override_region - if val, exists := dbRegion["enable_default_user"]; exists && val != nil { - regionProps.EnableDefaultUser = redis.Bool(val.(bool)) + // Handle enable_default_user: Only send if explicitly set in config + // With Default removed from schema, we use GetRawConfig to detect explicit setting + regionName := dbRegion["name"].(string) + if isEnableDefaultUserExplicitlySetInConfig(d, regionName) { + // User explicitly set it in config - send the value + if val, exists := dbRegion["enable_default_user"]; exists && val != nil { + regionProps.EnableDefaultUser = redis.Bool(val.(bool)) + log.Printf("[DEBUG] Update: Sending enable_default_user=%v for region %s (explicitly set)", val, regionName) + } + } else { + // Not explicitly set - don't send field, API will use global + log.Printf("[DEBUG] Update: NOT sending enable_default_user for region %s (inherits from global)", regionName) } if len(overrideAlerts) > 0 { @@ -820,10 +878,9 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema update.GlobalDataPersistence = redis.String(d.Get("global_data_persistence").(string)) } - if v, ok := d.GetOkExists("global_enable_default_user"); ok { - update.GlobalEnableDefaultUser = redis.Bool(v.(bool)) - } - + // global_enable_default_user has Default: true, so field always has a value + // No need for GetOkExists - just use d.Get() directly + update.GlobalEnableDefaultUser = redis.Bool(d.Get("global_enable_default_user").(bool)) if v, ok := d.GetOk("support_oss_cluster_api"); ok { update.SupportOSSClusterAPI = redis.Bool(v.(bool)) @@ -959,3 +1016,125 @@ func flattenModulesToNames(modules []*databases.Module) []string { } return moduleNames } + +// findRegionFieldInCtyValue navigates through a cty.Value representing override_region Set +// and finds a specific field within a region identified by regionName. +// Returns the field's cty.Value and true if found, or cty.NilVal and false if not found. +// This helper is used by both config and state detection functions. +func findRegionFieldInCtyValue(ctyVal cty.Value, regionName string, fieldName string) (cty.Value, bool) { + // Check if ctyVal is null or unknown + if ctyVal.IsNull() || !ctyVal.IsKnown() { + log.Printf("[DEBUG] findRegionFieldInCtyValue: cty.Value is null or unknown for region=%s field=%s", regionName, fieldName) + return cty.NilVal, false + } + + // Get the override_region attribute + if !ctyVal.Type().HasAttribute("override_region") { + log.Printf("[DEBUG] findRegionFieldInCtyValue: No override_region attribute found") + return cty.NilVal, false + } + + overrideRegions := ctyVal.GetAttr("override_region") + if overrideRegions.IsNull() || !overrideRegions.IsKnown() { + log.Printf("[DEBUG] findRegionFieldInCtyValue: override_region is null or unknown") + return cty.NilVal, false + } + + // override_region is a Set, so we need to iterate through it + if !overrideRegions.Type().IsSetType() && !overrideRegions.Type().IsListType() { + log.Printf("[DEBUG] findRegionFieldInCtyValue: override_region is not a Set or List type: %s", overrideRegions.Type().FriendlyName()) + return cty.NilVal, false + } + + // Iterate through each region in the Set + iter := overrideRegions.ElementIterator() + for iter.Next() { + _, regionVal := iter.Element() + + if regionVal.IsNull() || !regionVal.IsKnown() { + continue + } + + // Check if this region has a "name" attribute matching our search + if !regionVal.Type().HasAttribute("name") { + continue + } + + nameAttr := regionVal.GetAttr("name") + if nameAttr.IsNull() || !nameAttr.IsKnown() { + continue + } + + // Check if the name matches + if nameAttr.AsString() != regionName { + continue + } + + // Found the matching region! Now check for the field + log.Printf("[DEBUG] findRegionFieldInCtyValue: Found matching region %s", regionName) + + if !regionVal.Type().HasAttribute(fieldName) { + log.Printf("[DEBUG] findRegionFieldInCtyValue: Region %s does not have attribute %s", regionName, fieldName) + return cty.NilVal, false + } + + fieldAttr := regionVal.GetAttr(fieldName) + if fieldAttr.IsNull() { + log.Printf("[DEBUG] findRegionFieldInCtyValue: Field %s is null for region %s", fieldName, regionName) + return cty.NilVal, false + } + + // For Set/List fields, check if they have elements + // Empty sets mean the field was not explicitly set + if fieldAttr.Type().IsSetType() || fieldAttr.Type().IsListType() { + if fieldAttr.LengthInt() == 0 { + log.Printf("[DEBUG] findRegionFieldInCtyValue: Field %s is empty Set/List for region %s", fieldName, regionName) + return cty.NilVal, false + } + } + + log.Printf("[DEBUG] findRegionFieldInCtyValue: Found field %s for region %s", fieldName, regionName) + return fieldAttr, true + } + + log.Printf("[DEBUG] findRegionFieldInCtyValue: Region %s not found in override_region Set", regionName) + return cty.NilVal, false +} + +// isEnableDefaultUserExplicitlySetInConfig checks if enable_default_user is explicitly +// set in the user's HCL config for a given region using GetRawConfig. +// Returns true only if the field exists and is not null in the actual config. +func isEnableDefaultUserExplicitlySetInConfig(d *schema.ResourceData, regionName string) bool { + rawConfig := d.GetRawConfig() + if rawConfig.IsNull() || !rawConfig.IsKnown() { + log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: GetRawConfig is null/unknown for region %s", regionName) + return false + } + + log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: Checking region %s in config", regionName) + + // Use the helper to navigate and find the field + _, found := findRegionFieldInCtyValue(rawConfig, regionName, "enable_default_user") + log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: Field found=%v for region %s", found, regionName) + + return found +} + +// isEnableDefaultUserInActualPersistedState checks if enable_default_user exists in the +// actual persisted state file (not the materialized state) for a given region using GetRawState. +// Returns true only if the field exists and is not null in the state file. +func isEnableDefaultUserInActualPersistedState(d *schema.ResourceData, regionName string) bool { + rawState := d.GetRawState() + if rawState.IsNull() || !rawState.IsKnown() { + log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: GetRawState is null/unknown for region %s", regionName) + return false + } + + log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: Checking region %s in state", regionName) + + // Use the helper to navigate and find the field + _, found := findRegionFieldInCtyValue(rawState, regionName, "enable_default_user") + log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: Field found=%v for region %s", found, regionName) + + return found +} From 1bf4cd42d0160494ffd3d79e8b2490e434f07dea Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 12:03:31 +0000 Subject: [PATCH 06/28] fix: tests have now properly randomised db and sub names --- .../enable_default_user_all_explicit.tf | 3 ++- ...e_default_user_global_false_region_true.tf | 3 ++- ...enable_default_user_global_true_inherit.tf | 3 ++- ...e_default_user_global_true_region_false.tf | 3 ++- ...ctive_database_enable_default_user_test.go | 20 +++++++++++++++---- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/provider/activeactive/testdata/enable_default_user_all_explicit.tf b/provider/activeactive/testdata/enable_default_user_all_explicit.tf index a9b934c0..6f18f2bb 100644 --- a/provider/activeactive/testdata/enable_default_user_all_explicit.tf +++ b/provider/activeactive/testdata/enable_default_user_all_explicit.tf @@ -6,7 +6,8 @@ locals { } data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf b/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf index a997516d..f0c26549 100644 --- a/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf +++ b/provider/activeactive/testdata/enable_default_user_global_false_region_true.tf @@ -6,7 +6,8 @@ locals { } data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf b/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf index ea293979..6579926c 100644 --- a/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf +++ b/provider/activeactive/testdata/enable_default_user_global_true_inherit.tf @@ -6,7 +6,8 @@ locals { } data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf b/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf index 8d78e4e5..32adbc3e 100644 --- a/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf +++ b/provider/activeactive/testdata/enable_default_user_global_true_region_false.tf @@ -6,7 +6,8 @@ locals { } data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/rediscloud_active_active_database_enable_default_user_test.go b/provider/rediscloud_active_active_database_enable_default_user_test.go index 643f8ab6..fa172765 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_test.go @@ -2,10 +2,10 @@ package provider import ( "fmt" - "os" "testing" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -15,9 +15,9 @@ import ( // - Regions explicitly override global (field IS in override_region) // - User explicitly sets same value as global (field IS in override_region, tests explicit vs inherited) func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing.T) { - subscriptionName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") + "-enable-default-user" - databaseName := "tf-test-enable-default-user" - databasePassword := "ThisIs!ATestPassword123" + subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + "-subscription" + databaseName := acctest.RandomWithPrefix(testResourcePrefix) + "-database" + databasePassword := acctest.RandString(20) const databaseResourceName = "rediscloud_active_active_subscription_database.example" @@ -28,6 +28,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. Steps: []resource.TestStep{ // Step 1: global=true, both regions inherit (NO enable_default_user in override_region) { + PreConfig: func() { + t.Logf("Starting Step 1: global=true, both regions inherit (subscription: %s, database: %s)", subscriptionName, databaseName) + }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_true_inherit.tf"), subscriptionName, @@ -48,6 +51,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. }, // Step 2: global=true, us-east-1 explicit false (field SHOULD appear in override_region) { + PreConfig: func() { + t.Logf("Starting Step 2: global=true, us-east-1 explicit false") + }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_true_region_false.tf"), subscriptionName, @@ -68,6 +74,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. }, // Step 3: global=false, us-east-1 explicit true (field SHOULD appear in override_region) { + PreConfig: func() { + t.Logf("Starting Step 3: global=false, us-east-1 explicit true") + }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_false_region_true.tf"), subscriptionName, @@ -87,6 +96,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. // Step 4: global=true, both regions explicit (us-east-1=true, us-east-2=false) // This tests that explicit values matching global are still preserved { + PreConfig: func() { + t.Logf("Starting Step 4: global=true, both regions explicit (us-east-1=true, us-east-2=false)") + }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_all_explicit.tf"), subscriptionName, From c722762cc4a106bf006859ae068b2cc8705c26a4 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 12:35:44 +0000 Subject: [PATCH 07/28] fix: global data persistance should be computed, adding tflog for debugging --- ...ource_rediscloud_active_active_database.go | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index 76031ba2..b3903113 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -13,6 +13,7 @@ import ( "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -149,6 +150,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Description: "Rate of database data persistence (in persistent storage)", Type: schema.TypeString, Optional: true, + Computed: true, }, "global_password": { Description: "Password used to access the database. If left empty, the password will be generated automatically", @@ -637,7 +639,11 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R globalEnableDefaultUser := d.Get("global_enable_default_user").(bool) regionEnableDefaultUser := redis.BoolValue(regionDb.Security.EnableDefaultUser) - log.Printf("[DEBUG] Read enable_default_user for region %s: region=%v, global=%v", region, regionEnableDefaultUser, globalEnableDefaultUser) + tflog.Debug(ctx, "Read enable_default_user for region", map[string]interface{}{ + "region": region, + "region_value": regionEnableDefaultUser, + "global_value": globalEnableDefaultUser, + }) // Check if GetRawConfig is available (during Apply/Update) rawConfig := d.GetRawConfig() @@ -649,7 +655,10 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R if getRawConfigAvailable { // Config-based mode: Check if explicitly set in config wasExplicitlySet := isEnableDefaultUserExplicitlySetInConfig(d, region) - log.Printf("[DEBUG] Config-based detection for region %s: wasExplicitlySet=%v", region, wasExplicitlySet) + tflog.Debug(ctx, "Config-based detection for region", map[string]interface{}{ + "region": region, + "wasExplicitlySet": wasExplicitlySet, + }) if wasExplicitlySet { shouldInclude = true @@ -664,7 +673,10 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R } else { // State-based mode: Check if was in actual persisted state fieldWasInActualState := isEnableDefaultUserInActualPersistedState(d, region) - log.Printf("[DEBUG] State-based detection for region %s: fieldWasInActualState=%v", region, fieldWasInActualState) + tflog.Debug(ctx, "State-based detection for region", map[string]interface{}{ + "region": region, + "fieldWasInActualState": fieldWasInActualState, + }) if fieldWasInActualState { shouldInclude = true @@ -678,7 +690,11 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R } } - log.Printf("[DEBUG] enable_default_user decision for region %s: shouldInclude=%v, reason=%s", region, shouldInclude, reason) + tflog.Debug(ctx, "enable_default_user decision for region", map[string]interface{}{ + "region": region, + "shouldInclude": shouldInclude, + "reason": reason, + }) if shouldInclude { regionDbConfig["enable_default_user"] = regionEnableDefaultUser @@ -811,11 +827,16 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema // User explicitly set it in config - send the value if val, exists := dbRegion["enable_default_user"]; exists && val != nil { regionProps.EnableDefaultUser = redis.Bool(val.(bool)) - log.Printf("[DEBUG] Update: Sending enable_default_user=%v for region %s (explicitly set)", val, regionName) + tflog.Debug(ctx, "Update: Sending enable_default_user for region (explicitly set)", map[string]interface{}{ + "region": regionName, + "value": val, + }) } } else { // Not explicitly set - don't send field, API will use global - log.Printf("[DEBUG] Update: NOT sending enable_default_user for region %s (inherits from global)", regionName) + tflog.Debug(ctx, "Update: NOT sending enable_default_user for region (inherits from global)", map[string]interface{}{ + "region": regionName, + }) } if len(overrideAlerts) > 0 { From 554a72129ec6438974e41659bf757daa37cbba84 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 13:10:47 +0000 Subject: [PATCH 08/28] chore: add new tests to smoke tests --- .github/workflows/terraform_provider_pr.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/terraform_provider_pr.yml b/.github/workflows/terraform_provider_pr.yml index 3b085435..a502d696 100644 --- a/.github/workflows/terraform_provider_pr.yml +++ b/.github/workflows/terraform_provider_pr.yml @@ -136,6 +136,17 @@ jobs: go-version-file: go.mod - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudActiveActiveDatabase_CRUDI"' + go_test_smoke_aa_db_enable_default_user: + name: go test smoke aa db enable default user + needs: [go_unit_test, tfproviderlint, terraform_providers_schema] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: go.mod + - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser"' + go_test_smoke_essentials_sub: name: go test smoke essentials sub needs: [go_unit_test, tfproviderlint, terraform_providers_schema] From 87a6efc4a310202479a134ad07f0fa87fd0f0d29 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 13:39:55 +0000 Subject: [PATCH 09/28] chore: bump rediscloud-go-api --- go.mod | 6 +++--- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index deabd513..9cc473fa 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.24.0 toolchain go1.24.1 require ( - github.com/RedisLabs/rediscloud-go-api v0.42.0 + github.com/RedisLabs/rediscloud-go-api v0.43.0 github.com/bflad/tfproviderlint v0.31.0 github.com/hashicorp/go-cty v1.5.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 github.com/stretchr/testify v1.11.1 ) @@ -38,7 +39,6 @@ require ( github.com/hashicorp/terraform-exec v0.23.1 // indirect github.com/hashicorp/terraform-json v0.27.1 // indirect github.com/hashicorp/terraform-plugin-go v0.29.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -71,4 +71,4 @@ require ( ) // for local development, uncomment this -replace github.com/RedisLabs/rediscloud-go-api => ../rediscloud-go-api +// replace github.com/RedisLabs/rediscloud-go-api => ../rediscloud-go-api diff --git a/go.sum b/go.sum index 388b7d72..540a8732 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RedisLabs/rediscloud-go-api v0.42.0 h1:nsEgDF9IlPSGVTpMtDMAKR3mzk1oqATAxpO/hAqrp80= -github.com/RedisLabs/rediscloud-go-api v0.42.0/go.mod h1:ZsOzeXCzczue7vOiAF0be0sl1FTEgltAGmh+4s0BFq0= +github.com/RedisLabs/rediscloud-go-api v0.43.0 h1:fMODeDNQoD/o2afeSkMl8zezxQ/scOW17Z54p3GrhyI= +github.com/RedisLabs/rediscloud-go-api v0.43.0/go.mod h1:ZsOzeXCzczue7vOiAF0be0sl1FTEgltAGmh+4s0BFq0= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= From 39944a82256eb187c242372d02e003559319f7eb Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 14:01:40 +0000 Subject: [PATCH 10/28] test: additional scenarios for test coverage plus api checks --- ...nable_default_user_global_false_inherit.tf | 55 +++++ ..._default_user_global_false_region_false.tf | 57 +++++ ...ctive_database_enable_default_user_test.go | 214 +++++++++++++++++- 3 files changed, 316 insertions(+), 10 deletions(-) create mode 100644 provider/activeactive/testdata/enable_default_user_global_false_inherit.tf create mode 100644 provider/activeactive/testdata/enable_default_user_global_false_region_false.tf diff --git a/provider/activeactive/testdata/enable_default_user_global_false_inherit.tf b/provider/activeactive/testdata/enable_default_user_global_false_inherit.tf new file mode 100644 index 00000000..a100410f --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_global_false_inherit.tf @@ -0,0 +1,55 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is false + global_enable_default_user = false + global_password = local.password + + # Both regions inherit from global - NO enable_default_user specified + override_region { + name = "us-east-1" + } + + override_region { + name = "us-east-2" + } +} diff --git a/provider/activeactive/testdata/enable_default_user_global_false_region_false.tf b/provider/activeactive/testdata/enable_default_user_global_false_region_false.tf new file mode 100644 index 00000000..0a6a3a10 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_global_false_region_false.tf @@ -0,0 +1,57 @@ +# Template signature: fmt.Sprintf(template, subscription_name, database_name, password) +locals { + subscription_name = "%s" + database_name = "%s" + password = "%s" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" +} + +resource "rediscloud_active_active_subscription" "example" { + name = local.subscription_name + payment_method_id = data.rediscloud_payment_method.card.id + cloud_provider = "AWS" + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + + region { + region = "us-east-1" + networking_deployment_cidr = "10.0.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + region { + region = "us-east-2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} + +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = rediscloud_active_active_subscription.example.id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is false + global_enable_default_user = false + global_password = local.password + + # us-east-1 explicitly set to false (matches global but is EXPLICIT) + override_region { + name = "us-east-1" + enable_default_user = false + } + + # us-east-2 inherits from global (false) - NO enable_default_user specified + override_region { + name = "us-east-2" + } +} diff --git a/provider/rediscloud_active_active_database_enable_default_user_test.go b/provider/rediscloud_active_active_database_enable_default_user_test.go index fa172765..63e1cfbe 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_test.go @@ -1,19 +1,33 @@ package provider import ( + "context" "fmt" + "strconv" + "strings" "testing" + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/client" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) // TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser tests the enable_default_user field // for global and regional override behavior, specifically testing for drift issues when: -// - Regions inherit from global (field NOT in override_region) -// - Regions explicitly override global (field IS in override_region) -// - User explicitly sets same value as global (field IS in override_region, tests explicit vs inherited) +// - Regions inherit from global (field NOT in override_region) - Steps 1, 5 +// - Regions explicitly override global (field IS in override_region) - Steps 2, 3 +// - User explicitly sets same value as global (field IS in override_region, tests explicit vs inherited) - Steps 4, 6 +// +// Tests all 6 combinations: +// Step 1: global=true, both inherit +// Step 2: global=true, one region=false, one inherit +// Step 3: global=false, one region=true, one inherit +// Step 4: global=true, region1=true (matches), region2=false +// Step 5: global=false, both inherit +// Step 6: global=false, region1=false (matches), one inherit func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing.T) { subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + "-subscription" databaseName := acctest.RandomWithPrefix(testResourcePrefix) + "-database" @@ -47,6 +61,12 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. // Neither region should have enable_default_user in state (inheriting from global) resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.0.enable_default_user"), resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.1.enable_default_user"), + + // API check: Both regions should have true (inherited from global) + testCheckEnableDefaultUserInAPI(databaseResourceName, true, map[string]*bool{ + "us-east-1": nil, // nil means inherits from global + "us-east-2": nil, + }), ), }, // Step 2: global=true, us-east-1 explicit false (field SHOULD appear in override_region) @@ -67,9 +87,20 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. // Two regions resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), - // us-east-1 has explicit false - need to find which index it is - // We check both indices and one should have enable_default_user=false - // Note: TypeSet ordering is non-deterministic, so we check both indices + // us-east-1 has explicit false (differs from global=true) + // us-east-2 inherits (no explicit field) + // Use TestCheckTypeSet to verify specific TypeSet element fields + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "false", + }), + // us-east-2 should NOT have enable_default_user field in this step + + // API check: us-east-1=false (explicit), us-east-2=true (inherited) + testCheckEnableDefaultUserInAPI(databaseResourceName, true, map[string]*bool{ + "us-east-1": redis.Bool(false), // Explicit override + "us-east-2": nil, // Inherits from global + }), ), }, // Step 3: global=false, us-east-1 explicit true (field SHOULD appear in override_region) @@ -90,7 +121,18 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. // Two regions resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), - // us-east-1 has explicit true - will appear in one of the indices + // us-east-1 has explicit true (differs from global=false) + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "true", + }), + // us-east-2 inherits (no explicit field) + + // API check: us-east-1=true (explicit), us-east-2=false (inherited) + testCheckEnableDefaultUserInAPI(databaseResourceName, false, map[string]*bool{ + "us-east-1": redis.Bool(true), // Explicit override + "us-east-2": nil, // Inherits from global + }), ), }, // Step 4: global=true, both regions explicit (us-east-1=true, us-east-2=false) @@ -112,13 +154,165 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser(t *testing. // Two regions resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), - // Both regions have explicit enable_default_user + // Both regions have explicit enable_default_user - both should be in state // us-east-1 has true (matches global but explicit) + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "true", + }), // us-east-2 has false (differs from global) - // Due to TypeSet non-deterministic ordering, we can't check specific indices - // But the key point is both should have the field in state + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-2", + "enable_default_user": "false", + }), + + // API check: us-east-1=true (explicit), us-east-2=false (explicit) + testCheckEnableDefaultUserInAPI(databaseResourceName, true, map[string]*bool{ + "us-east-1": redis.Bool(true), // Explicit (matches global) + "us-east-2": redis.Bool(false), // Explicit (differs from global) + }), + ), + }, + // Step 5: global=false, both regions inherit (NO enable_default_user in override_region) + // Mirror of Step 1 but with global=false + { + PreConfig: func() { + t.Logf("Starting Step 5: global=false, both regions inherit") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_false_inherit.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "false"), + + // Both regions should exist + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // Neither region should have enable_default_user in state (inheriting from global) + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.0.enable_default_user"), + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.1.enable_default_user"), + + // API check: Both regions should have false (inherited from global) + testCheckEnableDefaultUserInAPI(databaseResourceName, false, map[string]*bool{ + "us-east-1": nil, // Inherits from global + "us-east-2": nil, + }), + ), + }, + // Step 6: global=false, us-east-1 explicit false (field SHOULD appear in override_region) + // Tests explicit false matching global false (vs inheriting) + { + PreConfig: func() { + t.Logf("Starting Step 6: global=false, us-east-1 explicit false (matches global)") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_global_false_region_false.tf"), + subscriptionName, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "false"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit false (matches global but is EXPLICIT) + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "false", + }), + // us-east-2 inherits (no explicit field) + + // API check: us-east-1=false (explicit, matches global), us-east-2=false (inherited) + testCheckEnableDefaultUserInAPI(databaseResourceName, false, map[string]*bool{ + "us-east-1": redis.Bool(false), // Explicit (matches global) + "us-east-2": nil, // Inherits from global + }), ), }, }, }) } + +// testCheckEnableDefaultUserInAPI verifies the enable_default_user values in the actual Redis Cloud API +// expected: map[regionName]expectedValue (nil means should match global) +func testCheckEnableDefaultUserInAPI(resourceName string, expectedGlobal bool, expectedRegions map[string]*bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + // Parse subscription_id and db_id from resource + subIdStr := rs.Primary.Attributes["subscription_id"] + dbIdStr := rs.Primary.Attributes["db_id"] + + subId, err := strconv.Atoi(subIdStr) + if err != nil { + return fmt.Errorf("failed to parse subscription_id: %v", err) + } + + dbId, err := strconv.Atoi(dbIdStr) + if err != nil { + return fmt.Errorf("failed to parse db_id: %v", err) + } + + // Get API client + apiClient, err := client.GetClient() + if err != nil { + return fmt.Errorf("failed to get API client: %v", err) + } + + // Fetch database from API + ctx := context.Background() + db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) + if err != nil { + return fmt.Errorf("failed to get database from API: %v", err) + } + + // Check global enable_default_user + if db.GlobalEnableDefaultUser == nil { + return fmt.Errorf("API returned nil for GlobalEnableDefaultUser") + } + actualGlobal := redis.BoolValue(db.GlobalEnableDefaultUser) + if actualGlobal != expectedGlobal { + return fmt.Errorf("API global_enable_default_user: expected %v, got %v", expectedGlobal, actualGlobal) + } + + // Check regional enable_default_user values + for _, regionDb := range db.CrdbDatabases { + regionName := redis.StringValue(regionDb.Region) + + if regionDb.Security == nil || regionDb.Security.EnableDefaultUser == nil { + return fmt.Errorf("API returned nil for region %s EnableDefaultUser", regionName) + } + + actualRegionValue := redis.BoolValue(regionDb.Security.EnableDefaultUser) + + // Get expected value for this region + expectedValue, hasExplicitOverride := expectedRegions[regionName] + + var expectedRegionValue bool + if hasExplicitOverride && expectedValue != nil { + // Region has explicit override + expectedRegionValue = *expectedValue + } else { + // Region inherits from global + expectedRegionValue = expectedGlobal + } + + if actualRegionValue != expectedRegionValue { + return fmt.Errorf("API region %s enable_default_user: expected %v, got %v", + regionName, expectedRegionValue, actualRegionValue) + } + } + + return nil + } +} From a93d8c8538748a310a38425242fe17d7880031fe Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 14:12:58 +0000 Subject: [PATCH 11/28] docs: changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d9f249..7923bc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) + +# Unreleased + +## Fixed +- `rediscloud_active_active_subscription_database`: Fixed drift detection for `enable_default_user` in region overrides. Regions now correctly inherit from `global_enable_default_user` when not explicitly set, eliminating spurious diffs. +- `rediscloud_active_active_subscription_database`: Fixed drift detection for `global_data_persistence`. This field now correctly tracks API-returned values without causing unexpected diffs. +- `rediscloud_subscription` and `rediscloud_active_active_subscription`: `customer_managed_key_enabled` and `customer_managed_key_deletion_grace_period` fields now support computed values from the API. + # 2.8.0 (10th November 2025) ## Added From e388d0dca88074b9c6f9e785e8e1a2b5f22bb90e Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 14:17:58 +0000 Subject: [PATCH 12/28] chore: fix test and slight tweak to sweep error returning --- ...oud_active_active_database_enable_default_user_test.go | 3 +-- provider/sweeper_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/provider/rediscloud_active_active_database_enable_default_user_test.go b/provider/rediscloud_active_active_database_enable_default_user_test.go index 63e1cfbe..14386ad7 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "strings" "testing" "github.com/RedisLabs/rediscloud-go-api/redis" @@ -264,7 +263,7 @@ func testCheckEnableDefaultUserInAPI(resourceName string, expectedGlobal bool, e } // Get API client - apiClient, err := client.GetClient() + apiClient, err := client.NewClient() if err != nil { return fmt.Errorf("failed to get API client: %v", err) } diff --git a/provider/sweeper_test.go b/provider/sweeper_test.go index 01c5f450..0a863d71 100644 --- a/provider/sweeper_test.go +++ b/provider/sweeper_test.go @@ -329,8 +329,8 @@ func testSweepAcl(region string) error { continue } - if client.Users.Delete(ctx, redis.IntValue(user.ID)) != nil { - return err + if delErr := client.Users.Delete(ctx, redis.IntValue(user.ID)); delErr != nil { + return delErr } } @@ -344,8 +344,8 @@ func testSweepAcl(region string) error { continue } - if client.Roles.Delete(ctx, redis.IntValue(role.ID)) != nil { - return err + if delErr := client.Roles.Delete(ctx, redis.IntValue(role.ID)); delErr != nil { + return delErr } } From e708a7ec4a802950a34900aa29b6f14c6b3504de Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 14:31:39 +0000 Subject: [PATCH 13/28] chore: fixed some test names and ensured tests always run in smoke tests --- .github/actions/run-testacc/action.yml | 18 ++++++++++ .github/workflows/terraform_provider_pr.yml | 40 +++++++++++++++------ 2 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 .github/actions/run-testacc/action.yml diff --git a/.github/actions/run-testacc/action.yml b/.github/actions/run-testacc/action.yml new file mode 100644 index 00000000..4d670b7f --- /dev/null +++ b/.github/actions/run-testacc/action.yml @@ -0,0 +1,18 @@ +name: 'Run Acceptance Tests' +description: 'Run acceptance tests with validation that tests actually ran' +inputs: + test_pattern: + description: 'Test pattern to pass to -run flag' + required: true +runs: + using: 'composite' + steps: + - name: Run tests + shell: bash + run: | + set -o pipefail + EXECUTE_TESTS=true make testacc TESTARGS='-run="${{ inputs.test_pattern }}"' 2>&1 | tee test_output.txt + if ! grep -q "=== RUN" test_output.txt; then + echo "ERROR: No tests matched the pattern. Please check the -run argument." + exit 1 + fi diff --git a/.github/workflows/terraform_provider_pr.yml b/.github/workflows/terraform_provider_pr.yml index a502d696..ea9dc0e5 100644 --- a/.github/workflows/terraform_provider_pr.yml +++ b/.github/workflows/terraform_provider_pr.yml @@ -134,7 +134,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudActiveActiveDatabase_CRUDI"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudActiveActiveDatabase_CRUDI go_test_smoke_aa_db_enable_default_user: name: go test smoke aa db enable default user @@ -145,7 +147,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser go_test_smoke_essentials_sub: name: go test smoke essentials sub @@ -156,7 +160,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudEssentialsSubscription"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudEssentialsSubscription go_test_smoke_essentials_db: @@ -168,7 +174,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudEssentialsDatabase_CRUDI"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudEssentialsDatabase_CRUDI go_test_smoke_pro_db: name: go test smoke pro db @@ -179,7 +187,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudProDatabase_CRUDI"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudProDatabase_CRUDI go_test_smoke_misc: @@ -191,7 +201,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloud(PrivateServiceConnect_CRUDI|AclRule_CRUDI)"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloud(PrivateServiceConnect_CRUDI|AclRule_CRUDI) go_test_pro_db_upgrade: name: go test smoke pro db upgrade @@ -202,7 +214,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudProDatabase_Upgrade"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudProDatabase_Redis8_Upgrade go_test_privatelink: name: go test smoke privatelink @@ -213,7 +227,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudPrivateLink_CRUDI"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudPrivateLink_CRUDI go_test_block_public_endpoints: @@ -225,7 +241,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAcc(RedisCloudProDatabaseBlockPublicEndpoints|ActiveActiveSubscriptionDatabaseBlockPublicEndpoints)"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAcc(RedisCloudProDatabase_BlockPublicEndpoints|ActiveActiveSubscriptionDatabase_BlockPublicEndpoints) go_test_smoke_qpf: name: go test smoke query performance factor @@ -236,7 +254,9 @@ jobs: - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: go.mod - - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudProDatabase_qpf"' + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudProDatabase_qpf tfproviderlint: name: tfproviderlint From 3080e391054b979c55b4b33feb1fd5dbbf4d4754 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 14:58:30 +0000 Subject: [PATCH 14/28] chore: removing qpf smoke test from pr --- .github/workflows/terraform_provider_pr.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/terraform_provider_pr.yml b/.github/workflows/terraform_provider_pr.yml index ea9dc0e5..cc162881 100644 --- a/.github/workflows/terraform_provider_pr.yml +++ b/.github/workflows/terraform_provider_pr.yml @@ -245,19 +245,6 @@ jobs: with: test_pattern: TestAcc(RedisCloudProDatabase_BlockPublicEndpoints|ActiveActiveSubscriptionDatabase_BlockPublicEndpoints) - go_test_smoke_qpf: - name: go test smoke query performance factor - needs: [go_unit_test, tfproviderlint, terraform_providers_schema] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: go.mod - - uses: ./.github/actions/run-testacc - with: - test_pattern: TestAccResourceRedisCloudProDatabase_qpf - tfproviderlint: name: tfproviderlint needs: [go_build] From f977ed5d9b06824078b92833b6434f928eaedcb5 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 15:04:16 +0000 Subject: [PATCH 15/28] chore: adding force flag to sweep --- provider/sweeper_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/provider/sweeper_test.go b/provider/sweeper_test.go index 0a863d71..bac41907 100644 --- a/provider/sweeper_test.go +++ b/provider/sweeper_test.go @@ -152,12 +152,13 @@ func testSweepProSubscriptions(region string) error { func testSweepReadDatabases(client *rediscloudApi.Client, subId int) (bool, []int, error) { var dbIds []int list := client.Database.List(context.TODO(), subId) + forceSweep := os.Getenv("FORCE_SWEEP") != "" for list.Next() { db := list.Value() - if !redis.TimeValue(db.ActivatedOn).Add(24 * -1 * time.Hour).Before(time.Now()) { - // Subscription _probably_ created within the last day, so assume someone might be + if !forceSweep && !redis.TimeValue(db.ActivatedOn).Add(2 * -1 * time.Hour).Before(time.Now()) { + // Subscription _probably_ created within the last 2 hours, so assume someone might be // currently running the tests return false, nil, nil } @@ -185,12 +186,13 @@ func testSweepReadDatabases(client *rediscloudApi.Client, subId int) (bool, []in func testSweepReadEssentialsDatabases(client *rediscloudApi.Client, subId int) (bool, []int, error) { var dbIds []int list := client.FixedDatabases.List(context.TODO(), subId) + forceSweep := os.Getenv("FORCE_SWEEP") != "" for list.Next() { db := list.Value() - if !redis.TimeValue(db.ActivatedOn).Add(24 * -1 * time.Hour).Before(time.Now()) { - // Subscription _probably_ created within the last day, so assume someone might be + if !forceSweep && !redis.TimeValue(db.ActivatedOn).Add(2 * -1 * time.Hour).Before(time.Now()) { + // Subscription _probably_ created within the last 2 hours, so assume someone might be // currently running the tests return false, nil, nil } From 259f8e4cd4eece485d464b7099e692cc5b436797 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 15:17:39 +0000 Subject: [PATCH 16/28] fix: piping input through env variable instead of shell expansion --- .github/actions/run-testacc/action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-testacc/action.yml b/.github/actions/run-testacc/action.yml index 4d670b7f..e09ef5cb 100644 --- a/.github/actions/run-testacc/action.yml +++ b/.github/actions/run-testacc/action.yml @@ -9,9 +9,11 @@ runs: steps: - name: Run tests shell: bash + env: + TEST_PATTERN: ${{ inputs.test_pattern }} run: | set -o pipefail - EXECUTE_TESTS=true make testacc TESTARGS='-run="${{ inputs.test_pattern }}"' 2>&1 | tee test_output.txt + EXECUTE_TESTS=true make testacc TESTARGS="-run=\"$TEST_PATTERN\"" 2>&1 | tee test_output.txt if ! grep -q "=== RUN" test_output.txt; then echo "ERROR: No tests matched the pattern. Please check the -run argument." exit 1 From 7623606d7a0fc208c322a8218a60923e132c6745 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 15:56:28 +0000 Subject: [PATCH 17/28] test: debug tests and version fix --- .../enable_default_user_debug_step1.tf | 27 +++ .../enable_default_user_debug_step2.tf | 29 +++ provider/pro/testdata/pro_database_redis_8.tf | 2 +- .../pro_database_redis_8_with_modules.tf | 2 +- ...database_enable_default_user_debug_test.go | 219 ++++++++++++++++++ 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 provider/activeactive/testdata/enable_default_user_debug_step1.tf create mode 100644 provider/activeactive/testdata/enable_default_user_debug_step2.tf create mode 100644 provider/rediscloud_active_active_database_enable_default_user_debug_test.go diff --git a/provider/activeactive/testdata/enable_default_user_debug_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_step1.tf new file mode 100644 index 00000000..7c308ae2 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_debug_step1.tf @@ -0,0 +1,27 @@ +# DEBUG VERSION - Reuses existing subscription +# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) +locals { + subscription_id = "%s" + database_name = "%s" + password = "%s" +} + +# Step 1: global=true, both regions inherit (NO enable_default_user in override_region) +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # Both regions inherit from global - NO enable_default_user specified + override_region { + name = "us-east-1" + } + + override_region { + name = "us-east-2" + } +} diff --git a/provider/activeactive/testdata/enable_default_user_debug_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_step2.tf new file mode 100644 index 00000000..f6bdae04 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_debug_step2.tf @@ -0,0 +1,29 @@ +# DEBUG VERSION - Reuses existing subscription +# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) +locals { + subscription_id = "%s" + database_name = "%s" + password = "%s" +} + +# Step 2: global=true, us-east-1 explicit false +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # us-east-1 explicitly set to false (differs from global) + override_region { + name = "us-east-1" + enable_default_user = false + } + + # us-east-2 inherits from global + override_region { + name = "us-east-2" + } +} diff --git a/provider/pro/testdata/pro_database_redis_8.tf b/provider/pro/testdata/pro_database_redis_8.tf index 9f967e5c..cc767296 100644 --- a/provider/pro/testdata/pro_database_redis_8.tf +++ b/provider/pro/testdata/pro_database_redis_8.tf @@ -54,7 +54,7 @@ resource "rediscloud_subscription_database" "example" { client_ssl_certificate = "" periodic_backup_path = "" enable_default_user = true - redis_version = "8.0" + redis_version = "8.2" alert { name = "dataset-size" diff --git a/provider/pro/testdata/pro_database_redis_8_with_modules.tf b/provider/pro/testdata/pro_database_redis_8_with_modules.tf index 8b9ed6a7..dc04979c 100644 --- a/provider/pro/testdata/pro_database_redis_8_with_modules.tf +++ b/provider/pro/testdata/pro_database_redis_8_with_modules.tf @@ -46,7 +46,7 @@ resource "rediscloud_subscription_database" "example" { throughput_measurement_by = "operations-per-second" throughput_measurement_value = 1000 password = local.rediscloud_database_password - redis_version = "8.0" + redis_version = "8.2" modules = [ { diff --git a/provider/rediscloud_active_active_database_enable_default_user_debug_test.go b/provider/rediscloud_active_active_database_enable_default_user_debug_test.go new file mode 100644 index 00000000..8d68e3f7 --- /dev/null +++ b/provider/rediscloud_active_active_database_enable_default_user_debug_test.go @@ -0,0 +1,219 @@ +package provider + +import ( + "context" + "fmt" + "os" + "strconv" + "testing" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/client" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +// TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserDebug is a DEBUG version +// that reuses an existing subscription to speed up testing during development. +// +// SETUP: +// 1. Set DEBUG_SUBSCRIPTION_ID environment variable to an existing AA subscription ID +// 2. The subscription must have us-east-1 and us-east-2 regions +// 3. Run with: DEBUG_SUBSCRIPTION_ID=12345 EXECUTE_TESTS=true make testacc TESTARGS='-run=TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserDebug' +// +// This test will: +// - Create a new database in the existing subscription +// - Update it through 2 test steps (global=true variants) +// - Delete the database at the end +func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserDebug(t *testing.T) { + utils.AccRequiresEnvVar(t, "EXECUTE_TESTS") + + subscriptionID := os.Getenv("DEBUG_SUBSCRIPTION_ID") + if subscriptionID == "" { + t.Skip("DEBUG_SUBSCRIPTION_ID not set - skipping debug test") + } + + databaseName := acctest.RandomWithPrefix("debug-enable-default-user") + databasePassword := acctest.RandString(20) + + const databaseResourceName = "rediscloud_active_active_subscription_database.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckActiveActiveDatabaseDestroy, + Steps: []resource.TestStep{ + // Step 1: global=true, both regions inherit + { + PreConfig: func() { + t.Logf("DEBUG Step 1: global=true, both inherit (subscription: %s, database: %s)", subscriptionID, databaseName) + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_step1.tf"), + subscriptionID, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + resource.TestCheckResourceAttr(databaseResourceName, "subscription_id", subscriptionID), + + // Both regions should exist + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // Neither region should have enable_default_user in state + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.0.enable_default_user"), + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.1.enable_default_user"), + + // API check + testCheckEnableDefaultUserInAPIDebug(databaseResourceName, true, map[string]*bool{ + "us-east-1": nil, + "us-east-2": nil, + }), + ), + }, + // Step 2: global=true, us-east-1 explicit false + { + PreConfig: func() { + t.Logf("DEBUG Step 2: global=true, us-east-1 explicit false") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_step2.tf"), + subscriptionID, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit false + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "false", + }), + + // API check + testCheckEnableDefaultUserInAPIDebug(databaseResourceName, true, map[string]*bool{ + "us-east-1": redis.Bool(false), + "us-east-2": nil, + }), + ), + }, + }, + }) +} + +// testCheckEnableDefaultUserInAPIDebug is identical to the regular version but for the debug test +func testCheckEnableDefaultUserInAPIDebug(resourceName string, expectedGlobal bool, expectedRegions map[string]*bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + subIdStr := rs.Primary.Attributes["subscription_id"] + dbIdStr := rs.Primary.Attributes["db_id"] + + subId, err := strconv.Atoi(subIdStr) + if err != nil { + return fmt.Errorf("failed to parse subscription_id: %v", err) + } + + dbId, err := strconv.Atoi(dbIdStr) + if err != nil { + return fmt.Errorf("failed to parse db_id: %v", err) + } + + apiClient, err := client.NewClient() + if err != nil { + return fmt.Errorf("failed to get API client: %v", err) + } + + ctx := context.Background() + db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) + if err != nil { + return fmt.Errorf("failed to get database from API: %v", err) + } + + if db.GlobalEnableDefaultUser == nil { + return fmt.Errorf("API returned nil for GlobalEnableDefaultUser") + } + actualGlobal := redis.BoolValue(db.GlobalEnableDefaultUser) + if actualGlobal != expectedGlobal { + return fmt.Errorf("API global_enable_default_user: expected %v, got %v", expectedGlobal, actualGlobal) + } + + for _, regionDb := range db.CrdbDatabases { + regionName := redis.StringValue(regionDb.Region) + + if regionDb.Security == nil || regionDb.Security.EnableDefaultUser == nil { + return fmt.Errorf("API returned nil for region %s EnableDefaultUser", regionName) + } + + actualRegionValue := redis.BoolValue(regionDb.Security.EnableDefaultUser) + + expectedValue, hasExplicitOverride := expectedRegions[regionName] + + var expectedRegionValue bool + if hasExplicitOverride && expectedValue != nil { + expectedRegionValue = *expectedValue + } else { + expectedRegionValue = expectedGlobal + } + + if actualRegionValue != expectedRegionValue { + return fmt.Errorf("API region %s enable_default_user: expected %v, got %v", + regionName, expectedRegionValue, actualRegionValue) + } + } + + return nil + } +} + +// testAccCheckActiveActiveDatabaseDestroy verifies the database was destroyed (subscription remains) +func testAccCheckActiveActiveDatabaseDestroy(s *terraform.State) error { + apiClient, err := client.NewClient() + if err != nil { + return err + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "rediscloud_active_active_subscription_database" { + continue + } + + subId, err := strconv.Atoi(rs.Primary.Attributes["subscription_id"]) + if err != nil { + continue + } + + dbId, err := strconv.Atoi(rs.Primary.Attributes["db_id"]) + if err != nil { + continue + } + + ctx := context.Background() + db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) + if err != nil { + // Database not found is expected + if _, ok := err.(*redis.NotFound); ok { + continue + } + return err + } + + if db != nil { + return fmt.Errorf("database %d still exists in subscription %d", dbId, subId) + } + } + + return nil +} From d5c62af17e06ab9ca343d5e7f46b5e6bf96226ba Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 15:57:50 +0000 Subject: [PATCH 18/28] fix: source_ips showing erronous drift when not in same order --- .../pro/resource_rediscloud_pro_database.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/provider/pro/resource_rediscloud_pro_database.go b/provider/pro/resource_rediscloud_pro_database.go index 317120e6..4cac607b 100644 --- a/provider/pro/resource_rediscloud_pro_database.go +++ b/provider/pro/resource_rediscloud_pro_database.go @@ -623,11 +623,18 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa // Check if returned source_ips matches default public access ["0.0.0.0/0"] isDefaultPublicAccess := len(db.Security.SourceIPs) == 1 && redis.StringValue(db.Security.SourceIPs[0]) == "0.0.0.0/0" - // Check if returned source_ips matches default RFC1918 private ranges - isDefaultPrivateRanges := len(db.Security.SourceIPs) == len(defaultPrivateIPRanges) - if isDefaultPrivateRanges { - for i, ip := range db.Security.SourceIPs { - if redis.StringValue(ip) != defaultPrivateIPRanges[i] { + // Check if returned source_ips matches default RFC1918 private ranges (order-independent) + isDefaultPrivateRanges := false + if len(db.Security.SourceIPs) == len(defaultPrivateIPRanges) { + // Create a map for O(1) lookup + defaultRangesMap := make(map[string]bool) + for _, cidr := range defaultPrivateIPRanges { + defaultRangesMap[cidr] = true + } + // Check if all API-returned IPs are in the defaults map + isDefaultPrivateRanges = true + for _, ip := range db.Security.SourceIPs { + if !defaultRangesMap[redis.StringValue(ip)] { isDefaultPrivateRanges = false break } From 7375664143444daf67c608efb421573ed55d8791 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 16:04:34 +0000 Subject: [PATCH 19/28] chore: debug tests --- .../enable_default_user_debug_import_step1.tf | 38 +++ .../enable_default_user_debug_import_step2.tf | 40 +++ .../enable_default_user_debug_import_step3.tf | 40 +++ ...database_enable_default_user_debug_test.go | 3 +- ...atabase_enable_default_user_import_test.go | 248 ++++++++++++++++++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 provider/activeactive/testdata/enable_default_user_debug_import_step1.tf create mode 100644 provider/activeactive/testdata/enable_default_user_debug_import_step2.tf create mode 100644 provider/activeactive/testdata/enable_default_user_debug_import_step3.tf create mode 100644 provider/rediscloud_active_active_database_enable_default_user_import_test.go diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf new file mode 100644 index 00000000..dee3b611 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf @@ -0,0 +1,38 @@ +# DEBUG VERSION - Imports existing database +# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +locals { + subscription_id = "%s" + database_id = "%s" + database_name = "%s" + password = "%s" +} + +# Step 1: global=true, both regions inherit (NO enable_default_user in override_region) +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + db_id = local.database_id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # Both regions inherit from global - NO enable_default_user specified + override_region { + name = "us-east-1" + } + + override_region { + name = "us-east-2" + } + + lifecycle { + ignore_changes = [ + # Ignore changes to fields we're not testing + global_data_persistence, + global_source_ips, + global_alert, + ] + } +} diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf new file mode 100644 index 00000000..f3d490cd --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf @@ -0,0 +1,40 @@ +# DEBUG VERSION - Imports existing database +# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +locals { + subscription_id = "%s" + database_id = "%s" + database_name = "%s" + password = "%s" +} + +# Step 2: global=true, us-east-1 explicit false +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + db_id = local.database_id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is true + global_enable_default_user = true + global_password = local.password + + # us-east-1 explicitly set to false (differs from global) + override_region { + name = "us-east-1" + enable_default_user = false + } + + # us-east-2 inherits from global + override_region { + name = "us-east-2" + } + + lifecycle { + ignore_changes = [ + # Ignore changes to fields we're not testing + global_data_persistence, + global_source_ips, + global_alert, + ] + } +} diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf new file mode 100644 index 00000000..5cb3c22e --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf @@ -0,0 +1,40 @@ +# DEBUG VERSION - Imports existing database +# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +locals { + subscription_id = "%s" + database_id = "%s" + database_name = "%s" + password = "%s" +} + +# Step 3: global=false, us-east-1 explicit true +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + db_id = local.database_id + name = local.database_name + memory_limit_in_gb = 1 + + # Global enable_default_user is false + global_enable_default_user = false + global_password = local.password + + # us-east-1 explicitly set to true (differs from global) + override_region { + name = "us-east-1" + enable_default_user = true + } + + # us-east-2 inherits from global + override_region { + name = "us-east-2" + } + + lifecycle { + ignore_changes = [ + # Ignore changes to fields we're not testing + global_data_persistence, + global_source_ips, + global_alert, + ] + } +} diff --git a/provider/rediscloud_active_active_database_enable_default_user_debug_test.go b/provider/rediscloud_active_active_database_enable_default_user_debug_test.go index 8d68e3f7..851378c3 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_debug_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_debug_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/databases" "github.com/RedisLabs/terraform-provider-rediscloud/provider/client" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -204,7 +205,7 @@ func testAccCheckActiveActiveDatabaseDestroy(s *terraform.State) error { db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) if err != nil { // Database not found is expected - if _, ok := err.(*redis.NotFound); ok { + if _, ok := err.(*databases.NotFound); ok { continue } return err diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go new file mode 100644 index 00000000..0636658d --- /dev/null +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -0,0 +1,248 @@ +package provider + +import ( + "context" + "fmt" + "os" + "strconv" + "testing" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/client" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +// TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport is a DEBUG version +// that imports and modifies an existing database to speed up testing during development. +// +// SETUP: +// 1. Set DEBUG_SUBSCRIPTION_ID and DEBUG_DATABASE_ID environment variables +// 2. The database must have us-east-1 and us-east-2 regions +// 3. Run with: DEBUG_SUBSCRIPTION_ID=124134 DEBUG_DATABASE_ID=4923 EXECUTE_TESTS=true make testacc TESTARGS='-run=TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport' +// +// This test will: +// - Import the existing database +// - Update it through 3 test steps to test enable_default_user drift detection +// - Leave the database in place (no destroy) +func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *testing.T) { + utils.AccRequiresEnvVar(t, "EXECUTE_TESTS") + + subscriptionID := os.Getenv("DEBUG_SUBSCRIPTION_ID") + databaseID := os.Getenv("DEBUG_DATABASE_ID") + + if subscriptionID == "" || databaseID == "" { + t.Skip("DEBUG_SUBSCRIPTION_ID and DEBUG_DATABASE_ID must be set - skipping import debug test") + } + + // Get the actual database name and password from API + apiClient, err := client.NewClient() + if err != nil { + t.Fatalf("Failed to create API client: %v", err) + } + + subId, _ := strconv.Atoi(subscriptionID) + dbId, _ := strconv.Atoi(databaseID) + ctx := context.Background() + + db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) + if err != nil { + t.Fatalf("Failed to fetch database %s/%s: %v", subscriptionID, databaseID, err) + } + + databaseName := redis.StringValue(db.Name) + databasePassword := acctest.RandString(20) // Use new password for testing + + const databaseResourceName = "rediscloud_active_active_subscription_database.example" + importID := fmt.Sprintf("%s/%s", subscriptionID, databaseID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + // Step 0: Import existing database + { + PreConfig: func() { + t.Logf("DEBUG Step 0: Importing database %s (sub: %s, db: %s)", databaseName, subscriptionID, databaseID) + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), + subscriptionID, + databaseID, + databaseName, + databasePassword, + ), + ResourceName: databaseResourceName, + ImportState: true, + ImportStateId: importID, + ImportStateVerify: false, // Don't verify all fields, just get it into state + }, + // Step 1: global=true, both regions inherit + { + PreConfig: func() { + t.Logf("DEBUG Step 1: global=true, both inherit") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), + subscriptionID, + databaseID, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + resource.TestCheckResourceAttr(databaseResourceName, "subscription_id", subscriptionID), + resource.TestCheckResourceAttr(databaseResourceName, "db_id", databaseID), + + // Both regions should exist + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // Neither region should have enable_default_user in state + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.0.enable_default_user"), + resource.TestCheckNoResourceAttr(databaseResourceName, "override_region.1.enable_default_user"), + + // API check + testCheckEnableDefaultUserInAPIImport(databaseResourceName, true, map[string]*bool{ + "us-east-1": nil, + "us-east-2": nil, + }), + ), + }, + // Step 2: global=true, us-east-1 explicit false + { + PreConfig: func() { + t.Logf("DEBUG Step 2: global=true, us-east-1 explicit false") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step2.tf"), + subscriptionID, + databaseID, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit false + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "false", + }), + + // API check + testCheckEnableDefaultUserInAPIImport(databaseResourceName, true, map[string]*bool{ + "us-east-1": redis.Bool(false), + "us-east-2": nil, + }), + ), + }, + // Step 3: global=false, us-east-1 explicit true + { + PreConfig: func() { + t.Logf("DEBUG Step 3: global=false, us-east-1 explicit true") + }, + Config: fmt.Sprintf( + utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step3.tf"), + subscriptionID, + databaseID, + databaseName, + databasePassword, + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Global setting + resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "false"), + + // Two regions + resource.TestCheckResourceAttr(databaseResourceName, "override_region.#", "2"), + + // us-east-1 has explicit true + resource.TestCheckTypeSetElemNestedAttrs(databaseResourceName, "override_region.*", map[string]string{ + "name": "us-east-1", + "enable_default_user": "true", + }), + + // API check + testCheckEnableDefaultUserInAPIImport(databaseResourceName, false, map[string]*bool{ + "us-east-1": redis.Bool(true), + "us-east-2": nil, + }), + ), + }, + }, + }) +} + +// testCheckEnableDefaultUserInAPIImport is identical to the regular version but for the import test +func testCheckEnableDefaultUserInAPIImport(resourceName string, expectedGlobal bool, expectedRegions map[string]*bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + subIdStr := rs.Primary.Attributes["subscription_id"] + dbIdStr := rs.Primary.Attributes["db_id"] + + subId, err := strconv.Atoi(subIdStr) + if err != nil { + return fmt.Errorf("failed to parse subscription_id: %v", err) + } + + dbId, err := strconv.Atoi(dbIdStr) + if err != nil { + return fmt.Errorf("failed to parse db_id: %v", err) + } + + apiClient, err := client.NewClient() + if err != nil { + return fmt.Errorf("failed to get API client: %v", err) + } + + ctx := context.Background() + db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) + if err != nil { + return fmt.Errorf("failed to get database from API: %v", err) + } + + if db.GlobalEnableDefaultUser == nil { + return fmt.Errorf("API returned nil for GlobalEnableDefaultUser") + } + actualGlobal := redis.BoolValue(db.GlobalEnableDefaultUser) + if actualGlobal != expectedGlobal { + return fmt.Errorf("API global_enable_default_user: expected %v, got %v", expectedGlobal, actualGlobal) + } + + for _, regionDb := range db.CrdbDatabases { + regionName := redis.StringValue(regionDb.Region) + + if regionDb.Security == nil || regionDb.Security.EnableDefaultUser == nil { + return fmt.Errorf("API returned nil for region %s EnableDefaultUser", regionName) + } + + actualRegionValue := redis.BoolValue(regionDb.Security.EnableDefaultUser) + + expectedValue, hasExplicitOverride := expectedRegions[regionName] + + var expectedRegionValue bool + if hasExplicitOverride && expectedValue != nil { + expectedRegionValue = *expectedValue + } else { + expectedRegionValue = expectedGlobal + } + + if actualRegionValue != expectedRegionValue { + return fmt.Errorf("API region %s enable_default_user: expected %v, got %v", + regionName, expectedRegionValue, actualRegionValue) + } + } + + return nil + } +} From 61736ea0ba551ef76ead5221df1a37caf4545182 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 16:10:20 +0000 Subject: [PATCH 20/28] chore: fixing debug test --- .../testdata/enable_default_user_debug_import_step1.tf | 4 +--- .../testdata/enable_default_user_debug_import_step2.tf | 4 +--- .../testdata/enable_default_user_debug_import_step3.tf | 4 +--- ..._active_active_database_enable_default_user_import_test.go | 4 ---- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf index dee3b611..0ae0dc64 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf @@ -1,8 +1,7 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) locals { subscription_id = "%s" - database_id = "%s" database_name = "%s" password = "%s" } @@ -10,7 +9,6 @@ locals { # Step 1: global=true, both regions inherit (NO enable_default_user in override_region) resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - db_id = local.database_id name = local.database_name memory_limit_in_gb = 1 diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf index f3d490cd..ab944aee 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf @@ -1,8 +1,7 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) locals { subscription_id = "%s" - database_id = "%s" database_name = "%s" password = "%s" } @@ -10,7 +9,6 @@ locals { # Step 2: global=true, us-east-1 explicit false resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - db_id = local.database_id name = local.database_name memory_limit_in_gb = 1 diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf index 5cb3c22e..c698e591 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf @@ -1,8 +1,7 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) locals { subscription_id = "%s" - database_id = "%s" database_name = "%s" password = "%s" } @@ -10,7 +9,6 @@ locals { # Step 3: global=false, us-east-1 explicit true resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - db_id = local.database_id name = local.database_name memory_limit_in_gb = 1 diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 0636658d..63db575b 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -70,7 +70,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), subscriptionID, - databaseID, databaseName, databasePassword, ), @@ -87,7 +86,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), subscriptionID, - databaseID, databaseName, databasePassword, ), @@ -119,7 +117,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step2.tf"), subscriptionID, - databaseID, databaseName, databasePassword, ), @@ -151,7 +148,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step3.tf"), subscriptionID, - databaseID, databaseName, databasePassword, ), From fd09b6dbcb0238e62a80dd8e71db844af69d3cd7 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 16:14:42 +0000 Subject: [PATCH 21/28] chore: fixing debug test --- .../enable_default_user_debug_import_step1.tf | 13 +++++++++---- .../enable_default_user_debug_import_step2.tf | 13 +++++++++---- .../enable_default_user_debug_import_step3.tf | 13 +++++++++---- ...tive_database_enable_default_user_import_test.go | 2 -- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf index 0ae0dc64..a54372be 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf @@ -1,15 +1,14 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, password) locals { subscription_id = "%s" - database_name = "%s" password = "%s" } # Step 1: global=true, both regions inherit (NO enable_default_user in override_region) resource "rediscloud_active_active_subscription_database" "example" { - subscription_id = local.subscription_id - name = local.database_name + subscription_id = local.subscription_id + name = "matt-database-debug-testing" memory_limit_in_gb = 1 # Global enable_default_user is true @@ -28,9 +27,15 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, + global_modules, + port, + replication, + throughput_measurement_by, + throughput_measurement_value, ] } } diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf index ab944aee..2928d44a 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf @@ -1,15 +1,14 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, password) locals { subscription_id = "%s" - database_name = "%s" password = "%s" } # Step 2: global=true, us-east-1 explicit false resource "rediscloud_active_active_subscription_database" "example" { - subscription_id = local.subscription_id - name = local.database_name + subscription_id = local.subscription_id + name = "matt-database-debug-testing" memory_limit_in_gb = 1 # Global enable_default_user is true @@ -30,9 +29,15 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, + global_modules, + port, + replication, + throughput_measurement_by, + throughput_measurement_value, ] } } diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf index c698e591..61c30558 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf @@ -1,15 +1,14 @@ # DEBUG VERSION - Imports existing database -# Template signature: fmt.Sprintf(template, subscription_id, database_name, password) +# Template signature: fmt.Sprintf(template, subscription_id, password) locals { subscription_id = "%s" - database_name = "%s" password = "%s" } # Step 3: global=false, us-east-1 explicit true resource "rediscloud_active_active_subscription_database" "example" { - subscription_id = local.subscription_id - name = local.database_name + subscription_id = local.subscription_id + name = "matt-database-debug-testing" memory_limit_in_gb = 1 # Global enable_default_user is false @@ -30,9 +29,15 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, + global_modules, + port, + replication, + throughput_measurement_by, + throughput_measurement_value, ] } } diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 63db575b..4b060de1 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -70,7 +70,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), subscriptionID, - databaseName, databasePassword, ), ResourceName: databaseResourceName, @@ -86,7 +85,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), subscriptionID, - databaseName, databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( From 4d713711fab0596d35a8c9b881e1aa8adb38b685 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 11 Nov 2025 16:38:59 +0000 Subject: [PATCH 22/28] wip --- .../enable_default_user_debug_import_step1.tf | 4 +--- .../enable_default_user_debug_import_step2.tf | 4 +--- .../enable_default_user_debug_import_step3.tf | 4 +--- ...atabase_enable_default_user_import_test.go | 23 +++++++++++++------ ...ource_rediscloud_active_active_database.go | 9 ++++++-- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf index a54372be..96ca23a1 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf @@ -27,15 +27,13 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + name, memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, global_modules, port, - replication, - throughput_measurement_by, - throughput_measurement_value, ] } } diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf index 2928d44a..a312c204 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf @@ -29,15 +29,13 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + name, memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, global_modules, port, - replication, - throughput_measurement_by, - throughput_measurement_value, ] } } diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf index 61c30558..e89b10b1 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf @@ -29,15 +29,13 @@ resource "rediscloud_active_active_subscription_database" "example" { lifecycle { ignore_changes = [ # Ignore changes to fields we're not testing + name, memory_limit_in_gb, global_data_persistence, global_source_ips, global_alert, global_modules, port, - replication, - throughput_measurement_by, - throughput_measurement_value, ] } } diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 4b060de1..8b96d8c8 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -52,7 +52,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te t.Fatalf("Failed to fetch database %s/%s: %v", subscriptionID, databaseID, err) } - databaseName := redis.StringValue(db.Name) databasePassword := acctest.RandString(20) // Use new password for testing const databaseResourceName = "rediscloud_active_active_subscription_database.example" @@ -62,10 +61,10 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ - // Step 0: Import existing database + // Step 0: Import existing database into state { PreConfig: func() { - t.Logf("DEBUG Step 0: Importing database %s (sub: %s, db: %s)", databaseName, subscriptionID, databaseID) + t.Logf("DEBUG Step 0: Importing database (sub: %s, db: %s, name: %s)", subscriptionID, databaseID, redis.StringValue(db.Name)) }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), @@ -75,9 +74,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te ResourceName: databaseResourceName, ImportState: true, ImportStateId: importID, - ImportStateVerify: false, // Don't verify all fields, just get it into state + ImportStateVerify: false, }, - // Step 1: global=true, both regions inherit + // Step 1: Apply step1 config - global=true, both regions inherit { PreConfig: func() { t.Logf("DEBUG Step 1: global=true, both inherit") @@ -88,6 +87,18 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( + // Debug: Print state + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[databaseResourceName] + if !ok { + return fmt.Errorf("resource not found in state: %s", databaseResourceName) + } + t.Logf("DEBUG Step 1 State - ID: %s", rs.Primary.ID) + t.Logf("DEBUG Step 1 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) + t.Logf("DEBUG Step 1 State - db_id: %s", rs.Primary.Attributes["db_id"]) + t.Logf("DEBUG Step 1 State - name: %s", rs.Primary.Attributes["name"]) + return nil + }, // Global setting resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), resource.TestCheckResourceAttr(databaseResourceName, "subscription_id", subscriptionID), @@ -115,7 +126,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step2.tf"), subscriptionID, - databaseName, databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( @@ -146,7 +156,6 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step3.tf"), subscriptionID, - databaseName, databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index b3903113..d2ed68b0 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -2,6 +2,7 @@ package provider import ( "context" + "fmt" "log" "regexp" "strings" @@ -358,9 +359,10 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema api := meta.(*client.ApiClient) subId := d.Get("subscription_id").(int) - utils.SubscriptionMutex.Lock(subId) - name := d.Get("name").(string) + tflog.Debug(ctx, fmt.Sprintf("DEBUG CREATE CALLED: subscription_id=%d, name=%s, state_id=%s", subId, name, d.Id())) + + utils.SubscriptionMutex.Lock(subId) supportOSSClusterAPI := d.Get("support_oss_cluster_api").(bool) useExternalEndpointForOSSClusterAPI := d.Get("external_endpoint_for_oss_cluster_api").(bool) globalSourceIp := utils.SetToStringSlice(d.Get("global_source_ips").(*schema.Set)) @@ -785,6 +787,9 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema } subId := d.Get("subscription_id").(int) + name := d.Get("name").(string) + tflog.Debug(ctx, fmt.Sprintf("DEBUG UPDATE CALLED: subscription_id=%d, db_id=%d, name=%s, state_id=%s", subId, dbId, name, d.Id())) + utils.SubscriptionMutex.Lock(subId) defer utils.SubscriptionMutex.Unlock(subId) From 1a3100136202b3f411d9cb81ffac93a6acaedd66 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 11:37:56 +0000 Subject: [PATCH 23/28] wip: import tests --- .../enable_default_user_debug_import_step1.tf | 6 +- .../enable_default_user_debug_import_step2.tf | 12 +- .../enable_default_user_debug_import_step3.tf | 12 +- .../enable_default_user_import_stub.tf | 24 +++ ...atabase_enable_default_user_import_test.go | 101 ++++++++---- ...ource_rediscloud_active_active_database.go | 145 +++++++++++++----- 6 files changed, 212 insertions(+), 88 deletions(-) create mode 100644 provider/activeactive/testdata/enable_default_user_import_stub.tf diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf index 96ca23a1..d9b279d0 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step1.tf @@ -8,7 +8,7 @@ locals { # Step 1: global=true, both regions inherit (NO enable_default_user in override_region) resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - name = "matt-database-debug-testing" + name = "matt-test-debugging" memory_limit_in_gb = 1 # Global enable_default_user is true @@ -17,11 +17,11 @@ resource "rediscloud_active_active_subscription_database" "example" { # Both regions inherit from global - NO enable_default_user specified override_region { - name = "us-east-1" + name = "eu-west-1" } override_region { - name = "us-east-2" + name = "us-east-1" } lifecycle { diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf index a312c204..9ee0166a 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step2.tf @@ -8,22 +8,22 @@ locals { # Step 2: global=true, us-east-1 explicit false resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - name = "matt-database-debug-testing" + name = "matt-test-debugging" memory_limit_in_gb = 1 # Global enable_default_user is true global_enable_default_user = true global_password = local.password - # us-east-1 explicitly set to false (differs from global) + # eu-west-1 inherits from global override_region { - name = "us-east-1" - enable_default_user = false + name = "eu-west-1" } - # us-east-2 inherits from global + # us-east-1 explicitly set to false (differs from global) override_region { - name = "us-east-2" + name = "us-east-1" + enable_default_user = false } lifecycle { diff --git a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf index e89b10b1..a5bf642f 100644 --- a/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf +++ b/provider/activeactive/testdata/enable_default_user_debug_import_step3.tf @@ -8,22 +8,22 @@ locals { # Step 3: global=false, us-east-1 explicit true resource "rediscloud_active_active_subscription_database" "example" { subscription_id = local.subscription_id - name = "matt-database-debug-testing" + name = "matt-test-debugging" memory_limit_in_gb = 1 # Global enable_default_user is false global_enable_default_user = false global_password = local.password - # us-east-1 explicitly set to true (differs from global) + # eu-west-1 inherits from global override_region { - name = "us-east-1" - enable_default_user = true + name = "eu-west-1" } - # us-east-2 inherits from global + # us-east-1 explicitly set to true (differs from global) override_region { - name = "us-east-2" + name = "us-east-1" + enable_default_user = true } lifecycle { diff --git a/provider/activeactive/testdata/enable_default_user_import_stub.tf b/provider/activeactive/testdata/enable_default_user_import_stub.tf new file mode 100644 index 00000000..cb176547 --- /dev/null +++ b/provider/activeactive/testdata/enable_default_user_import_stub.tf @@ -0,0 +1,24 @@ +# Minimal stub config for import - just defines the resource shell +# Template signature: fmt.Sprintf(template, subscription_id, password) +locals { + subscription_id = "%s" + password = "%s" +} + +# Minimal resource definition for import +resource "rediscloud_active_active_subscription_database" "example" { + subscription_id = local.subscription_id + name = "matt-test-debugging" + memory_limit_in_gb = 1 + + global_enable_default_user = true + global_password = local.password + + override_region { + name = "eu-west-1" + } + + override_region { + name = "us-east-1" + } +} diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 8b96d8c8..2f443fe1 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "log" "os" "strconv" "testing" @@ -20,14 +21,19 @@ import ( // // SETUP: // 1. Set DEBUG_SUBSCRIPTION_ID and DEBUG_DATABASE_ID environment variables -// 2. The database must have us-east-1 and us-east-2 regions +// 2. The database must have eu-west-1 and us-east-1 regions // 3. Run with: DEBUG_SUBSCRIPTION_ID=124134 DEBUG_DATABASE_ID=4923 EXECUTE_TESTS=true make testacc TESTARGS='-run=TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport' // // This test will: -// - Import the existing database -// - Update it through 3 test steps to test enable_default_user drift detection +// - Import the existing database with minimal stub config (Step 1) +// - Apply initial baseline config with both regions inheriting from global (Step 2) +// - Update to set us-east-1 explicit false while global remains true (Step 3) +// - Update to set global false and us-east-1 explicit true (Step 4) // - Leave the database in place (no destroy) func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *testing.T) { + // Enable detailed logging + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + utils.AccRequiresEnvVar(t, "EXECUTE_TESTS") subscriptionID := os.Getenv("DEBUG_SUBSCRIPTION_ID") @@ -37,35 +43,23 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te t.Skip("DEBUG_SUBSCRIPTION_ID and DEBUG_DATABASE_ID must be set - skipping import debug test") } - // Get the actual database name and password from API - apiClient, err := client.NewClient() - if err != nil { - t.Fatalf("Failed to create API client: %v", err) - } - - subId, _ := strconv.Atoi(subscriptionID) - dbId, _ := strconv.Atoi(databaseID) - ctx := context.Background() - - db, err := apiClient.Client.Database.GetActiveActive(ctx, subId, dbId) - if err != nil { - t.Fatalf("Failed to fetch database %s/%s: %v", subscriptionID, databaseID, err) - } - + t.Logf("=== TEST SETUP: subscription=%s, database=%s ===", subscriptionID, databaseID) databasePassword := acctest.RandString(20) // Use new password for testing const databaseResourceName = "rediscloud_active_active_subscription_database.example" importID := fmt.Sprintf("%s/%s", subscriptionID, databaseID) + t.Logf("=== IMPORT ID: %s ===", importID) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ - // Step 0: Import existing database into state + // Step 1: Import existing database with full config { PreConfig: func() { - t.Logf("DEBUG Step 0: Importing database (sub: %s, db: %s, name: %s)", subscriptionID, databaseID, redis.StringValue(db.Name)) + t.Logf("DEBUG Step 1 PreConfig: Import existing database %s/%s", subscriptionID, databaseID) }, + // Full config - must match what Read returns for import to stick Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), subscriptionID, @@ -75,11 +69,28 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te ImportState: true, ImportStateId: importID, ImportStateVerify: false, + Check: resource.ComposeAggregateTestCheckFunc( + // Debug: Print state + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[databaseResourceName] + if !ok { + return fmt.Errorf("resource not found in state: %s", databaseResourceName) + } + t.Logf("DEBUG Step 1 State - ID: %s", rs.Primary.ID) + t.Logf("DEBUG Step 1 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) + t.Logf("DEBUG Step 1 State - db_id: %s", rs.Primary.Attributes["db_id"]) + t.Logf("DEBUG Step 1 State - name: %s", rs.Primary.Attributes["name"]) + return nil + }, + // Basic import checks - just verify the resource was imported + resource.TestCheckResourceAttr(databaseResourceName, "subscription_id", subscriptionID), + resource.TestCheckResourceAttr(databaseResourceName, "db_id", databaseID), + ), }, - // Step 1: Apply step1 config - global=true, both regions inherit + // Step 2: Apply initial baseline config - global=true, both regions inherit { PreConfig: func() { - t.Logf("DEBUG Step 1: global=true, both inherit") + t.Logf("DEBUG Step 2 PreConfig: Apply initial config - global=true, both inherit") }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step1.tf"), @@ -93,10 +104,10 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te if !ok { return fmt.Errorf("resource not found in state: %s", databaseResourceName) } - t.Logf("DEBUG Step 1 State - ID: %s", rs.Primary.ID) - t.Logf("DEBUG Step 1 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) - t.Logf("DEBUG Step 1 State - db_id: %s", rs.Primary.Attributes["db_id"]) - t.Logf("DEBUG Step 1 State - name: %s", rs.Primary.Attributes["name"]) + t.Logf("DEBUG Step 2 State - ID: %s", rs.Primary.ID) + t.Logf("DEBUG Step 2 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) + t.Logf("DEBUG Step 2 State - db_id: %s", rs.Primary.Attributes["db_id"]) + t.Logf("DEBUG Step 2 State - name: %s", rs.Primary.Attributes["name"]) return nil }, // Global setting @@ -113,15 +124,15 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te // API check testCheckEnableDefaultUserInAPIImport(databaseResourceName, true, map[string]*bool{ + "eu-west-1": nil, "us-east-1": nil, - "us-east-2": nil, }), ), }, - // Step 2: global=true, us-east-1 explicit false + // Step 3: global=true, us-east-1 explicit false { PreConfig: func() { - t.Logf("DEBUG Step 2: global=true, us-east-1 explicit false") + t.Logf("DEBUG Step 3 PreConfig: global=true, us-east-1 explicit false") }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step2.tf"), @@ -129,6 +140,18 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( + // Debug: Print state + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[databaseResourceName] + if !ok { + return fmt.Errorf("resource not found in state: %s", databaseResourceName) + } + t.Logf("DEBUG Step 3 State - ID: %s", rs.Primary.ID) + t.Logf("DEBUG Step 3 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) + t.Logf("DEBUG Step 3 State - db_id: %s", rs.Primary.Attributes["db_id"]) + t.Logf("DEBUG Step 3 State - name: %s", rs.Primary.Attributes["name"]) + return nil + }, // Global setting resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "true"), @@ -143,15 +166,15 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te // API check testCheckEnableDefaultUserInAPIImport(databaseResourceName, true, map[string]*bool{ + "eu-west-1": nil, "us-east-1": redis.Bool(false), - "us-east-2": nil, }), ), }, - // Step 3: global=false, us-east-1 explicit true + // Step 4: global=false, us-east-1 explicit true { PreConfig: func() { - t.Logf("DEBUG Step 3: global=false, us-east-1 explicit true") + t.Logf("DEBUG Step 4 PreConfig: global=false, us-east-1 explicit true") }, Config: fmt.Sprintf( utils.GetTestConfig(t, "./activeactive/testdata/enable_default_user_debug_import_step3.tf"), @@ -159,6 +182,18 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te databasePassword, ), Check: resource.ComposeAggregateTestCheckFunc( + // Debug: Print state + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[databaseResourceName] + if !ok { + return fmt.Errorf("resource not found in state: %s", databaseResourceName) + } + t.Logf("DEBUG Step 4 State - ID: %s", rs.Primary.ID) + t.Logf("DEBUG Step 4 State - subscription_id: %s", rs.Primary.Attributes["subscription_id"]) + t.Logf("DEBUG Step 4 State - db_id: %s", rs.Primary.Attributes["db_id"]) + t.Logf("DEBUG Step 4 State - name: %s", rs.Primary.Attributes["name"]) + return nil + }, // Global setting resource.TestCheckResourceAttr(databaseResourceName, "global_enable_default_user", "false"), @@ -173,8 +208,8 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te // API check testCheckEnableDefaultUserInAPIImport(databaseResourceName, false, map[string]*bool{ + "eu-west-1": nil, "us-east-1": redis.Bool(true), - "us-east-2": nil, }), ), }, diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index d2ed68b0..b25a4f57 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -2,7 +2,6 @@ package provider import ( "context" - "fmt" "log" "regexp" "strings" @@ -14,7 +13,6 @@ import ( "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -31,17 +29,30 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] IMPORT StateContext CALLED: import_id=%s", d.Id()) + log.Printf("[INFO] Starting Active-Active database import: import_id=%s", d.Id()) + subId, dbId, err := pro.ToDatabaseId(d.Id()) if err != nil { + log.Printf("[ERROR] Failed to parse import ID: import_id=%s, error=%v", d.Id(), err) return nil, err } + + log.Printf("[DEBUG] IMPORT: Parsed subscription_id=%d, db_id=%d", subId, dbId) + if err := d.Set("subscription_id", subId); err != nil { + log.Printf("[ERROR] Failed to set subscription_id: subscription_id=%d, error=%v", subId, err) return nil, err } if err := d.Set("db_id", dbId); err != nil { + log.Printf("[ERROR] Failed to set db_id: db_id=%d, error=%v", dbId, err) return nil, err } d.SetId(utils.BuildResourceId(subId, dbId)) + + log.Printf("[DEBUG] IMPORT: Set resource ID to: %s", d.Id()) + log.Printf("[INFO] Import initialization complete - Read will be called next: resource_id=%s", d.Id()) + return []*schema.ResourceData{d}, nil }, }, @@ -360,7 +371,8 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema subId := d.Get("subscription_id").(int) name := d.Get("name").(string) - tflog.Debug(ctx, fmt.Sprintf("DEBUG CREATE CALLED: subscription_id=%d, name=%s, state_id=%s", subId, name, d.Id())) + log.Printf("[DEBUG] CREATE CALLED: subscription_id=%d, name=%s, state_id=%s, has_id=%v", + subId, name, d.Id(), d.Id() != "") utils.SubscriptionMutex.Lock(subId) supportOSSClusterAPI := d.Get("support_oss_cluster_api").(bool) @@ -502,15 +514,27 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R subId = d.Get("subscription_id").(int) } + log.Printf("[DEBUG] READ CALLED: state_id=%s, parsed_sub_id=%d, parsed_db_id=%d, schema_sub_id=%v", + d.Id(), subId, dbId, d.Get("subscription_id")) + log.Printf("[INFO] Starting Active-Active database Read: resource_id=%s, subscription_id=%d, db_id=%d", + d.Id(), subId, dbId) + db, err := api.Client.Database.GetActiveActive(ctx, subId, dbId) if err != nil { if _, ok := err.(*databases.NotFound); ok { + log.Printf("[DEBUG] READ: Database not found, clearing state: sub_id=%d, db_id=%d", subId, dbId) + log.Printf("[INFO] Database not found, clearing state: subscription_id=%d, db_id=%d", subId, dbId) d.SetId("") return diags } return diag.FromErr(err) } + log.Printf("[DEBUG] READ: Fetched from API - name=%s, id=%d, sub_id=%d", + redis.StringValue(db.Name), redis.IntValue(db.ID), subId) + log.Printf("[DEBUG] Fetched database from API: name=%s, id=%d, global_enable_default_user=%v, num_regions=%d", + redis.StringValue(db.Name), redis.IntValue(db.ID), redis.BoolValue(db.GlobalEnableDefaultUser), len(db.CrdbDatabases)) + if err := d.Set("db_id", redis.IntValue(db.ID)); err != nil { return diag.FromErr(err) } @@ -567,6 +591,9 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R var regionDbConfigs []map[string]interface{} publicEndpointConfig := make(map[string]interface{}) privateEndpointConfig := make(map[string]interface{}) + + log.Printf("[DEBUG] Processing regions from API response: num_regions=%d", len(db.CrdbDatabases)) + for _, regionDb := range db.CrdbDatabases { region := redis.StringValue(regionDb.Region) // Set the endpoints for the region @@ -574,7 +601,12 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R privateEndpointConfig[region] = redis.StringValue(regionDb.PrivateEndpoint) // Check if the region is in the state as an override stateOverrideRegion := getStateOverrideRegion(d, region) + + log.Printf("[DEBUG] Processing region: region=%s, has_override_in_state=%v, region_enable_default_user=%v", + region, stateOverrideRegion != nil, redis.BoolValue(regionDb.Security.EnableDefaultUser)) + if stateOverrideRegion == nil { + log.Printf("[DEBUG] Skipping region - not in override_region state: region=%s", region) continue } regionDbConfig := map[string]interface{}{ @@ -641,11 +673,8 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R globalEnableDefaultUser := d.Get("global_enable_default_user").(bool) regionEnableDefaultUser := redis.BoolValue(regionDb.Security.EnableDefaultUser) - tflog.Debug(ctx, "Read enable_default_user for region", map[string]interface{}{ - "region": region, - "region_value": regionEnableDefaultUser, - "global_value": globalEnableDefaultUser, - }) + log.Printf("[DEBUG] Read enable_default_user for region - starting evaluation: region=%s, region_value_from_api=%v, global_value=%v, values_match=%v", + region, regionEnableDefaultUser, globalEnableDefaultUser, regionEnableDefaultUser == globalEnableDefaultUser) // Check if GetRawConfig is available (during Apply/Update) rawConfig := d.GetRawConfig() @@ -657,10 +686,7 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R if getRawConfigAvailable { // Config-based mode: Check if explicitly set in config wasExplicitlySet := isEnableDefaultUserExplicitlySetInConfig(d, region) - tflog.Debug(ctx, "Config-based detection for region", map[string]interface{}{ - "region": region, - "wasExplicitlySet": wasExplicitlySet, - }) + log.Printf("[DEBUG] Config-based detection for region: region=%s, wasExplicitlySet=%v", region, wasExplicitlySet) if wasExplicitlySet { shouldInclude = true @@ -675,10 +701,7 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R } else { // State-based mode: Check if was in actual persisted state fieldWasInActualState := isEnableDefaultUserInActualPersistedState(d, region) - tflog.Debug(ctx, "State-based detection for region", map[string]interface{}{ - "region": region, - "fieldWasInActualState": fieldWasInActualState, - }) + log.Printf("[DEBUG] State-based detection for region: region=%s, fieldWasInActualState=%v", region, fieldWasInActualState) if fieldWasInActualState { shouldInclude = true @@ -692,25 +715,32 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R } } - tflog.Debug(ctx, "enable_default_user decision for region", map[string]interface{}{ - "region": region, - "shouldInclude": shouldInclude, - "reason": reason, - }) + log.Printf("[INFO] enable_default_user decision for region: region=%s, shouldInclude=%v, reason=%s, will_set_in_state=%v, value_if_set=%v", + region, shouldInclude, reason, shouldInclude, regionEnableDefaultUser) if shouldInclude { regionDbConfig["enable_default_user"] = regionEnableDefaultUser + log.Printf("[DEBUG] Set enable_default_user in regionDbConfig: region=%s, value=%v", region, regionEnableDefaultUser) + } else { + log.Printf("[DEBUG] NOT setting enable_default_user in regionDbConfig (will inherit from global): region=%s", region) } } + log.Printf("[DEBUG] Completed processing region, appending to regionDbConfigs: region=%s, has_enable_default_user_key=%v", + region, regionDbConfig["enable_default_user"] != nil) + regionDbConfigs = append(regionDbConfigs, regionDbConfig) } // Only set override_region if it is defined in the config if len(d.Get("override_region").(*schema.Set).List()) > 0 { + log.Printf("[DEBUG] Setting override_region in state: num_regions=%d", len(regionDbConfigs)) if err := d.Set("override_region", regionDbConfigs); err != nil { return diag.FromErr(err) } + log.Printf("[INFO] Successfully set override_region in state: num_regions=%d", len(regionDbConfigs)) + } else { + log.Printf("[DEBUG] NOT setting override_region - no regions in config") } if err := d.Set("public_endpoint", publicEndpointConfig); err != nil { @@ -730,7 +760,9 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R // Read global_enable_default_user from API response if db.GlobalEnableDefaultUser != nil { - if err := d.Set("global_enable_default_user", redis.BoolValue(db.GlobalEnableDefaultUser)); err != nil { + globalValue := redis.BoolValue(db.GlobalEnableDefaultUser) + log.Printf("[DEBUG] Setting global_enable_default_user in state: value=%v", globalValue) + if err := d.Set("global_enable_default_user", globalValue); err != nil { return diag.FromErr(err) } } @@ -744,6 +776,9 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R return diag.FromErr(err) } + log.Printf("[INFO] Completed Active-Active database Read: resource_id=%s, global_enable_default_user=%v, num_override_regions=%d", + d.Id(), d.Get("global_enable_default_user"), len(d.Get("override_region").(*schema.Set).List())) + return diags } @@ -788,7 +823,9 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema subId := d.Get("subscription_id").(int) name := d.Get("name").(string) - tflog.Debug(ctx, fmt.Sprintf("DEBUG UPDATE CALLED: subscription_id=%d, db_id=%d, name=%s, state_id=%s", subId, dbId, name, d.Id())) + log.Printf("[DEBUG] UPDATE CALLED: subscription_id=%d, db_id=%d, name=%s, state_id=%s", subId, dbId, name, d.Id()) + log.Printf("[INFO] Starting Active-Active database Update: subscription_id=%d, db_id=%d, name=%s, global_enable_default_user=%v", + subId, dbId, name, d.Get("global_enable_default_user")) utils.SubscriptionMutex.Lock(subId) defer utils.SubscriptionMutex.Unlock(subId) @@ -810,10 +847,17 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema // Make a list of region-specific configurations var regions []*databases.LocalRegionProperties + + log.Printf("[DEBUG] Building region-specific configurations for Update: num_regions=%d", len(d.Get("override_region").(*schema.Set).List())) + for _, region := range d.Get("override_region").(*schema.Set).List() { dbRegion := region.(map[string]interface{}) + regionName := dbRegion["name"].(string) - overrideAlerts := getStateAlertsFromDbRegion(getStateOverrideRegion(d, dbRegion["name"].(string))) + log.Printf("[DEBUG] Processing region for Update: region=%s, has_enable_default_user_key=%v", + regionName, dbRegion["enable_default_user"] != nil) + + overrideAlerts := getStateAlertsFromDbRegion(getStateOverrideRegion(d, regionName)) // Make a list of region-specific source IPs for use in the regions list below var overrideSourceIps []*string @@ -822,26 +866,30 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema } regionProps := &databases.LocalRegionProperties{ - Region: redis.String(dbRegion["name"].(string)), + Region: redis.String(regionName), } // Handle enable_default_user: Only send if explicitly set in config // With Default removed from schema, we use GetRawConfig to detect explicit setting - regionName := dbRegion["name"].(string) - if isEnableDefaultUserExplicitlySetInConfig(d, regionName) { + explicitlySet := isEnableDefaultUserExplicitlySetInConfig(d, regionName) + + log.Printf("[DEBUG] Update: Checking enable_default_user for region: region=%s, explicitly_set_in_config=%v, value_in_dbRegion=%v", + regionName, explicitlySet, dbRegion["enable_default_user"]) + + if explicitlySet { // User explicitly set it in config - send the value if val, exists := dbRegion["enable_default_user"]; exists && val != nil { - regionProps.EnableDefaultUser = redis.Bool(val.(bool)) - tflog.Debug(ctx, "Update: Sending enable_default_user for region (explicitly set)", map[string]interface{}{ - "region": regionName, - "value": val, - }) + boolVal := val.(bool) + regionProps.EnableDefaultUser = redis.Bool(boolVal) + log.Printf("[INFO] Update: SENDING enable_default_user for region (explicitly set): region=%s, value=%v, will_override_global=%v", + regionName, boolVal, boolVal != d.Get("global_enable_default_user").(bool)) + } else { + log.Printf("[WARN] Update: Field marked as explicitly set but value missing: region=%s", regionName) } } else { // Not explicitly set - don't send field, API will use global - tflog.Debug(ctx, "Update: NOT sending enable_default_user for region (inherits from global)", map[string]interface{}{ - "region": regionName, - }) + log.Printf("[INFO] Update: NOT sending enable_default_user for region (inherits from global): region=%s, global_value=%v", + regionName, d.Get("global_enable_default_user")) } if len(overrideAlerts) > 0 { @@ -906,7 +954,10 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema // global_enable_default_user has Default: true, so field always has a value // No need for GetOkExists - just use d.Get() directly - update.GlobalEnableDefaultUser = redis.Bool(d.Get("global_enable_default_user").(bool)) + globalEnableDefaultUserValue := d.Get("global_enable_default_user").(bool) + update.GlobalEnableDefaultUser = redis.Bool(globalEnableDefaultUserValue) + + log.Printf("[INFO] Update: Setting global_enable_default_user in API request: value=%v", globalEnableDefaultUserValue) if v, ok := d.GetOk("support_oss_cluster_api"); ok { update.SupportOSSClusterAPI = redis.Bool(v.(bool)) @@ -945,11 +996,17 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema } } + log.Printf("[INFO] Sending ActiveActiveUpdate API request: subscription_id=%d, db_id=%d, global_enable_default_user=%v, num_regions=%d", + subId, dbId, globalEnableDefaultUserValue, len(regions)) + err = api.Client.Database.ActiveActiveUpdate(ctx, subId, dbId, update) if err != nil { + log.Printf("[ERROR] ActiveActiveUpdate API request failed: subscription_id=%d, db_id=%d, error=%v", subId, dbId, err) return diag.FromErr(err) } + log.Printf("[INFO] ActiveActiveUpdate API request successful, waiting for database to be active: subscription_id=%d, db_id=%d", subId, dbId) + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return diag.FromErr(err) } @@ -963,6 +1020,8 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema return diag.FromErr(err) } + log.Printf("[INFO] Update complete, calling Read to refresh state: subscription_id=%d, db_id=%d", subId, dbId) + return resourceRedisCloudActiveActiveDatabaseRead(ctx, d, meta) } @@ -1131,6 +1190,8 @@ func findRegionFieldInCtyValue(ctyVal cty.Value, regionName string, fieldName st // set in the user's HCL config for a given region using GetRawConfig. // Returns true only if the field exists and is not null in the actual config. func isEnableDefaultUserExplicitlySetInConfig(d *schema.ResourceData, regionName string) bool { + // Note: ctx is not available in this function, so we use log.Printf + // The calling functions already have tflog statements showing the result rawConfig := d.GetRawConfig() if rawConfig.IsNull() || !rawConfig.IsKnown() { log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: GetRawConfig is null/unknown for region %s", regionName) @@ -1140,8 +1201,9 @@ func isEnableDefaultUserExplicitlySetInConfig(d *schema.ResourceData, regionName log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: Checking region %s in config", regionName) // Use the helper to navigate and find the field - _, found := findRegionFieldInCtyValue(rawConfig, regionName, "enable_default_user") - log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: Field found=%v for region %s", found, regionName) + fieldVal, found := findRegionFieldInCtyValue(rawConfig, regionName, "enable_default_user") + log.Printf("[DEBUG] isEnableDefaultUserExplicitlySetInConfig: Field found=%v for region %s, fieldVal.IsKnown=%v", + found, regionName, !fieldVal.IsNull() && fieldVal.IsKnown()) return found } @@ -1150,6 +1212,8 @@ func isEnableDefaultUserExplicitlySetInConfig(d *schema.ResourceData, regionName // actual persisted state file (not the materialized state) for a given region using GetRawState. // Returns true only if the field exists and is not null in the state file. func isEnableDefaultUserInActualPersistedState(d *schema.ResourceData, regionName string) bool { + // Note: ctx is not available in this function, so we use log.Printf + // The calling functions already have tflog statements showing the result rawState := d.GetRawState() if rawState.IsNull() || !rawState.IsKnown() { log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: GetRawState is null/unknown for region %s", regionName) @@ -1159,8 +1223,9 @@ func isEnableDefaultUserInActualPersistedState(d *schema.ResourceData, regionNam log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: Checking region %s in state", regionName) // Use the helper to navigate and find the field - _, found := findRegionFieldInCtyValue(rawState, regionName, "enable_default_user") - log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: Field found=%v for region %s", found, regionName) + fieldVal, found := findRegionFieldInCtyValue(rawState, regionName, "enable_default_user") + log.Printf("[DEBUG] isEnableDefaultUserInActualPersistedState: Field found=%v for region %s, fieldVal.IsKnown=%v", + found, regionName, !fieldVal.IsNull() && fieldVal.IsKnown()) return found } From dd3da310000db68fd35e9223e8b7a61fafda093a Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 12:15:22 +0000 Subject: [PATCH 24/28] fix: when importing use global api value --- ...ource_rediscloud_active_active_database.go | 99 ++++++++++++++++--- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index b25a4f57..08c01549 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -499,6 +499,49 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema return resourceRedisCloudActiveActiveDatabaseUpdate(ctx, d, meta) } +// readOperationMode identifies the context in which Read is being called +type readOperationMode int + +const ( + readModeImport readOperationMode = iota // Import: no config/state exists yet + readModeApply // Apply/Update: config is available + readModeRefresh // Refresh: only state is available +) + +// String returns a human-readable name for the mode +func (m readOperationMode) String() string { + switch m { + case readModeImport: + return "import" + case readModeApply: + return "apply/update" + case readModeRefresh: + return "refresh" + default: + return "unknown" + } +} + +// detectReadOperationMode determines which operation mode we're in based on availability of config and state +func detectReadOperationMode(d *schema.ResourceData) readOperationMode { + rawConfig := d.GetRawConfig() + rawState := d.GetRawState() + + configAvailable := !rawConfig.IsNull() && rawConfig.IsKnown() + stateExists := !rawState.IsNull() && rawState.IsKnown() && len(d.Get("override_region").(*schema.Set).List()) > 0 + + if !configAvailable && !stateExists { + // Import: Neither config nor state available yet + return readModeImport + } else if configAvailable { + // Apply/Update: Config is available (Create/Update operation) + return readModeApply + } else { + // Refresh: Only state available (standalone refresh operation) + return readModeRefresh + } +} + func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { api := meta.(*client.ApiClient) @@ -592,23 +635,54 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R publicEndpointConfig := make(map[string]interface{}) privateEndpointConfig := make(map[string]interface{}) - log.Printf("[DEBUG] Processing regions from API response: num_regions=%d", len(db.CrdbDatabases)) + // Determine if override_region should be tracked based on operation mode + mode := detectReadOperationMode(d) + shouldTrackOverrideRegion := false + + switch mode { + case readModeImport: + // Import mode: Always populate override_region from API + // The subsequent Apply (Step 2) will reconcile with actual config + shouldTrackOverrideRegion = true + log.Printf("[DEBUG] Import mode detected - will populate override_region from API") + + case readModeApply: + // Apply/Update mode: Check if config has override_region blocks + // Config is source of truth - only track if user declared it + rawConfig := d.GetRawConfig() + overrideRegionValue := rawConfig.GetAttr("override_region") + if !overrideRegionValue.IsNull() && overrideRegionValue.LengthInt() > 0 { + shouldTrackOverrideRegion = true + } + log.Printf("[DEBUG] Apply/Update mode detected - shouldTrackOverrideRegion=%v (from config)", shouldTrackOverrideRegion) + + case readModeRefresh: + // Refresh mode: Check if state has override_region blocks + // Preserve what was previously in state + shouldTrackOverrideRegion = len(d.Get("override_region").(*schema.Set).List()) > 0 + log.Printf("[DEBUG] Refresh mode detected - shouldTrackOverrideRegion=%v (from state)", shouldTrackOverrideRegion) + } + + log.Printf("[DEBUG] Processing regions from API response: num_regions=%d, mode=%v, shouldTrackOverrideRegion=%v", + len(db.CrdbDatabases), mode, shouldTrackOverrideRegion) for _, regionDb := range db.CrdbDatabases { region := redis.StringValue(regionDb.Region) // Set the endpoints for the region publicEndpointConfig[region] = redis.StringValue(regionDb.PublicEndpoint) privateEndpointConfig[region] = redis.StringValue(regionDb.PrivateEndpoint) - // Check if the region is in the state as an override - stateOverrideRegion := getStateOverrideRegion(d, region) - - log.Printf("[DEBUG] Processing region: region=%s, has_override_in_state=%v, region_enable_default_user=%v", - region, stateOverrideRegion != nil, redis.BoolValue(regionDb.Security.EnableDefaultUser)) - if stateOverrideRegion == nil { - log.Printf("[DEBUG] Skipping region - not in override_region state: region=%s", region) + // If config doesn't have override_region blocks, skip all region processing + if !shouldTrackOverrideRegion { + log.Printf("[DEBUG] Skipping region - no override_region blocks in config: region=%s", region) continue } + + // Get state for this region (may be nil during import, that's OK) + stateOverrideRegion := getStateOverrideRegion(d, region) + + log.Printf("[DEBUG] Processing region: region=%s, has_override_in_state=%v, shouldTrackOverrideRegion=%v, region_enable_default_user=%v", + region, stateOverrideRegion != nil, shouldTrackOverrideRegion, redis.BoolValue(regionDb.Security.EnableDefaultUser)) regionDbConfig := map[string]interface{}{ "name": region, } @@ -670,7 +744,8 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R // Handle enable_default_user with hybrid GetRawConfig/GetRawState approach // to avoid drift issues with TypeSet materialization if regionDb.Security.EnableDefaultUser != nil { - globalEnableDefaultUser := d.Get("global_enable_default_user").(bool) + // Use API value for global, not state value, to ensure correct comparison during import + globalEnableDefaultUser := redis.BoolValue(db.GlobalEnableDefaultUser) regionEnableDefaultUser := redis.BoolValue(regionDb.Security.EnableDefaultUser) log.Printf("[DEBUG] Read enable_default_user for region - starting evaluation: region=%s, region_value_from_api=%v, global_value=%v, values_match=%v", @@ -732,15 +807,15 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R regionDbConfigs = append(regionDbConfigs, regionDbConfig) } - // Only set override_region if it is defined in the config - if len(d.Get("override_region").(*schema.Set).List()) > 0 { + // Only set override_region if it should be tracked (based on config check above) + if shouldTrackOverrideRegion { log.Printf("[DEBUG] Setting override_region in state: num_regions=%d", len(regionDbConfigs)) if err := d.Set("override_region", regionDbConfigs); err != nil { return diag.FromErr(err) } log.Printf("[INFO] Successfully set override_region in state: num_regions=%d", len(regionDbConfigs)) } else { - log.Printf("[DEBUG] NOT setting override_region - no regions in config") + log.Printf("[DEBUG] NOT setting override_region - no override_region blocks in config") } if err := d.Set("public_endpoint", publicEndpointConfig); err != nil { From 6f247411c0017637bda77fb49eeced66837572d1 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 12:54:58 +0000 Subject: [PATCH 25/28] chore: adding note that test does not currently work due to limitations --- ...tive_active_database_enable_default_user_import_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 2f443fe1..04933175 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -19,11 +19,8 @@ import ( // TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport is a DEBUG version // that imports and modifies an existing database to speed up testing during development. // -// SETUP: -// 1. Set DEBUG_SUBSCRIPTION_ID and DEBUG_DATABASE_ID environment variables -// 2. The database must have eu-west-1 and us-east-1 regions -// 3. Run with: DEBUG_SUBSCRIPTION_ID=124134 DEBUG_DATABASE_ID=4923 EXECUTE_TESTS=true make testacc TESTARGS='-run=TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport' -// +// Unfortunately this test does not currently work as many fields are NOT supported in active active databases. + // This test will: // - Import the existing database with minimal stub config (Step 1) // - Apply initial baseline config with both regions inheriting from global (Step 2) From 169f545d88f0e9b646122f9b4949c52d2f5d33d1 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 13:29:01 +0000 Subject: [PATCH 26/28] chore: small tweaks --- .../resource_rediscloud_active_active_database.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index 08c01549..bd45b9cd 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -465,7 +465,6 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema createDatabase.RedisVersion = s }) - // Confirm Subscription Active status before creating database err = utils.WaitForSubscriptionToBeActive(ctx, subId, api) if err != nil { @@ -504,8 +503,8 @@ type readOperationMode int const ( readModeImport readOperationMode = iota // Import: no config/state exists yet - readModeApply // Apply/Update: config is available - readModeRefresh // Refresh: only state is available + readModeApply // Apply/Update: config is available + readModeRefresh // Refresh: only state is available ) // String returns a human-readable name for the mode @@ -641,10 +640,9 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R switch mode { case readModeImport: - // Import mode: Always populate override_region from API - // The subsequent Apply (Step 2) will reconcile with actual config - shouldTrackOverrideRegion = true - log.Printf("[DEBUG] Import mode detected - will populate override_region from API") + // Import mode: Don't populate override_region (preserves historical behavior since resource creation) + shouldTrackOverrideRegion = false + log.Printf("[DEBUG] Import mode detected") case readModeApply: // Apply/Update mode: Check if config has override_region blocks @@ -832,7 +830,6 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R return diag.FromErr(err) } - // Read global_enable_default_user from API response if db.GlobalEnableDefaultUser != nil { globalValue := redis.BoolValue(db.GlobalEnableDefaultUser) From 8537bcdd836a61ff11b6c7577238420a95302bff Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 14:20:22 +0000 Subject: [PATCH 27/28] chore: adding nil checkdestroy to satisfy build --- ...oud_active_active_database_enable_default_user_import_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/provider/rediscloud_active_active_database_enable_default_user_import_test.go b/provider/rediscloud_active_active_database_enable_default_user_import_test.go index 04933175..78fb7b76 100644 --- a/provider/rediscloud_active_active_database_enable_default_user_import_test.go +++ b/provider/rediscloud_active_active_database_enable_default_user_import_test.go @@ -50,6 +50,7 @@ func TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUserImport(t *te resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, + CheckDestroy: nil, // No destroy - this test imports and modifies an existing database Steps: []resource.TestStep{ // Step 1: Import existing database with full config { From 1d47883c8aa9e08b2c40bbfbba5b9f4031dbe4c0 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 12 Nov 2025 14:30:24 +0000 Subject: [PATCH 28/28] chore: temporary reprioritisation of smoke tests to focus on current changes --- .github/workflows/terraform_provider_pr.yml | 60 +++++++++++++++------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/.github/workflows/terraform_provider_pr.yml b/.github/workflows/terraform_provider_pr.yml index cc162881..6167ce0a 100644 --- a/.github/workflows/terraform_provider_pr.yml +++ b/.github/workflows/terraform_provider_pr.yml @@ -125,6 +125,9 @@ jobs: go-version-file: go.mod - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudActiveActiveTransitGatewayAttachment_CRUDI"' + # ===== WAVE 1: Critical smoke tests for PR changes ===== + # These test our direct code changes and must pass first + go_test_smoke_aa_db: name: go test smoke aa db needs: [go_unit_test, tfproviderlint, terraform_providers_schema] @@ -151,8 +154,8 @@ jobs: with: test_pattern: TestAccResourceRedisCloudActiveActiveDatabase_enableDefaultUser - go_test_smoke_essentials_sub: - name: go test smoke essentials sub + go_test_smoke_aa_sub: + name: go test smoke aa sub needs: [go_unit_test, tfproviderlint, terraform_providers_schema] runs-on: ubuntu-latest steps: @@ -162,12 +165,11 @@ jobs: go-version-file: go.mod - uses: ./.github/actions/run-testacc with: - test_pattern: TestAccResourceRedisCloudEssentialsSubscription - + test_pattern: TestAccResourceRedisCloudActiveActiveSubscription_CRUDI - go_test_smoke_essentials_db: - name: go test smoke essentials db - needs: go_test_smoke_essentials_sub + go_test_smoke_pro_db: + name: go test smoke pro db + needs: [go_unit_test, tfproviderlint, terraform_providers_schema] runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -176,10 +178,10 @@ jobs: go-version-file: go.mod - uses: ./.github/actions/run-testacc with: - test_pattern: TestAccResourceRedisCloudEssentialsDatabase_CRUDI + test_pattern: TestAccResourceRedisCloudProDatabase_CRUDI - go_test_smoke_pro_db: - name: go test smoke pro db + go_test_smoke_pro_sub: + name: go test smoke pro sub needs: [go_unit_test, tfproviderlint, terraform_providers_schema] runs-on: ubuntu-latest steps: @@ -189,12 +191,40 @@ jobs: go-version-file: go.mod - uses: ./.github/actions/run-testacc with: - test_pattern: TestAccResourceRedisCloudProDatabase_CRUDI + test_pattern: TestAccResourceRedisCloudProSubscription_CRUDI + + # ===== WAVE 2: Additional smoke tests (run after Wave 1 passes) ===== + + go_test_smoke_essentials_sub: + name: go test smoke essentials sub + needs: [go_test_smoke_aa_db, go_test_smoke_aa_db_enable_default_user, go_test_smoke_aa_sub, go_test_smoke_pro_db, go_test_smoke_pro_sub] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: go.mod + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudEssentialsSubscription + go_test_smoke_essentials_db: + name: go test smoke essentials db + needs: go_test_smoke_essentials_sub + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: go.mod + - uses: ./.github/actions/run-testacc + with: + test_pattern: TestAccResourceRedisCloudEssentialsDatabase_CRUDI + go_test_smoke_misc: name: go test smoke misc - needs: [go_unit_test, tfproviderlint, terraform_providers_schema] + needs: [go_test_smoke_aa_db, go_test_smoke_aa_db_enable_default_user, go_test_smoke_aa_sub, go_test_smoke_pro_db, go_test_smoke_pro_sub] runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -207,7 +237,7 @@ jobs: go_test_pro_db_upgrade: name: go test smoke pro db upgrade - needs: [go_unit_test, tfproviderlint, terraform_providers_schema] + needs: [go_test_smoke_aa_db, go_test_smoke_aa_db_enable_default_user, go_test_smoke_aa_sub, go_test_smoke_pro_db, go_test_smoke_pro_sub] runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -220,7 +250,7 @@ jobs: go_test_privatelink: name: go test smoke privatelink - needs: [go_unit_test, tfproviderlint, terraform_providers_schema] + needs: [go_test_smoke_aa_db, go_test_smoke_aa_db_enable_default_user, go_test_smoke_aa_sub, go_test_smoke_pro_db, go_test_smoke_pro_sub] runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -234,7 +264,7 @@ jobs: go_test_block_public_endpoints: name: go test smoke public endpoints - needs: [go_unit_test, tfproviderlint, terraform_providers_schema] + needs: [go_test_smoke_aa_db, go_test_smoke_aa_db_enable_default_user, go_test_smoke_aa_sub, go_test_smoke_pro_db, go_test_smoke_pro_sub] runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2