From ba89624a32dc689931a28fe4592a728c2613bb2f Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 15 Jul 2025 14:50:40 +0100 Subject: [PATCH 01/27] chore: updating schema and go modules --- go.mod | 2 +- go.sum | 4 ++-- provider/resource_rediscloud_pro_subscription.go | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3a6ceee..312b9ad5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/RedisLabs/terraform-provider-rediscloud go 1.22.4 require ( - github.com/RedisLabs/rediscloud-go-api v0.29.0 + github.com/RedisLabs/rediscloud-go-api v0.30.0 github.com/bflad/tfproviderlint v0.31.0 github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 diff --git a/go.sum b/go.sum index a065a2d8..961c1a7b 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RedisLabs/rediscloud-go-api v0.29.0 h1:XLVBMSgHwaaHFmf+TXrsU2veQ67J+e5Xrz54FggnwTY= -github.com/RedisLabs/rediscloud-go-api v0.29.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= +github.com/RedisLabs/rediscloud-go-api v0.30.0 h1:G5d3xaBNa1+nkQY9x7uAk1nV31y09k6qN1gq1ofWXYs= +github.com/RedisLabs/rediscloud-go-api v0.30.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= 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= diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index d5ced2cd..b3cb1853 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -476,6 +476,20 @@ func resourceRedisCloudProSubscription() *schema.Resource { }, }, }, + "cmek_enabled": { + Description: "Whether to enable CMEK (customer managed encryption 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. Do not supply a creation plan if this set as true. Defaults to false.", + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + "cmek_id": { + Description: "ID of the CMEK (customer managed encryption key) used to encrypt the databases in this subscription. Ignored if `cmek_enabled` set to false. Supply after the database has been put into database pending state. See documentation for CMEK flow.", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: nil, + }, }, } } From e2a015ba754a1d6effd9f694539575f8143cea2a Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 15 Jul 2025 15:42:31 +0100 Subject: [PATCH 02/27] feat: routing through bool for cmk and writing tests --- .../resource_rediscloud_pro_subscription.go | 8 ++ ...source_rediscloud_pro_subscription_test.go | 96 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index b3cb1853..d38e194d 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -21,6 +21,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +const CMEK_ENABLED_STRING = "cloud-provider-managed-key" + func containsModule(modules []interface{}, requiredModule string) bool { for _, m := range modules { if mod, ok := m.(string); ok && mod == requiredModule { @@ -539,6 +541,12 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso createSubscriptionRequest.RedisVersion = redis.String(redisVersion) } + cmekEnabled := d.Get("cmek_enabled").(bool) + + if cmekEnabled == true { + createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMEK_ENABLED_STRING) + } + subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) diff --git a/provider/resource_rediscloud_pro_subscription_test.go b/provider/resource_rediscloud_pro_subscription_test.go index d16e0311..5ae9f8c3 100644 --- a/provider/resource_rediscloud_pro_subscription_test.go +++ b/provider/resource_rediscloud_pro_subscription_test.go @@ -722,6 +722,30 @@ func testAccCheckProSubscriptionDestroy(s *terraform.State) error { return nil } +func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { + + //testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + name := acctest.RandomWithPrefix(testResourcePrefix) + const resourceName = "rediscloud_subscription.example" + testCloudAccountName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckProSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_part1, testCloudAccountName, name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), + ), + }, + }, + }) +} + // TF config for provisioning a new subscription. const testAccResourceRedisCloudProSubscription = ` data "rediscloud_payment_method" "card" { @@ -770,6 +794,78 @@ resource "rediscloud_subscription" "example" { } ` +const testAccResourceRedisCloudProSubscriptionCmekEnabled_part1 = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_cloud_account" "account" { + exclude_internal_account = true + provider_type = "AWS" + name = "%s" +} + +resource "rediscloud_subscription" "example" { + + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + cmek_enabled = true + + allowlist { + cidrs = ["192.168.0.0/16"] + security_group_ids = [] + } + + cloud_provider { + provider = data.rediscloud_cloud_account.account.provider_type + cloud_account_id = data.rediscloud_cloud_account.account.id + region { + region = "eu-west-1" + networking_deployment_cidr = "10.0.0.0/24" + preferred_availability_zones = ["eu-west-1a"] + } + } +} +` + +const testAccResourceRedisCloudProSubscriptionCmekEnabled_update = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_cloud_account" "account" { + exclude_internal_account = true + provider_type = "AWS" + name = "%s" +} + +resource "rediscloud_subscription" "example" { + + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + cmek_enabled = true + + allowlist { + cidrs = ["192.168.0.0/16"] + security_group_ids = [] + } + + cloud_provider { + provider = data.rediscloud_cloud_account.account.provider_type + cloud_account_id = data.rediscloud_cloud_account.account.id + region { + region = "eu-west-1" + networking_deployment_cidr = "10.0.0.0/24" + preferred_availability_zones = ["eu-west-1a"] + } + } +} +` + const testAccResourceRedisCloudProSubscriptionWithRedisVersion = ` data "rediscloud_payment_method" "card" { card_type = "Visa" From 1df6bf484235ea487d79141f55eb7b6787f31f77 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Mon, 21 Jul 2025 15:34:57 +0100 Subject: [PATCH 03/27] test: cmek tests --- .../resource_rediscloud_pro_subscription.go | 4 + ...e_rediscloud_pro_subscription_cmek_test.go | 132 ++++++++++++++++++ ...source_rediscloud_pro_subscription_test.go | 96 ------------- 3 files changed, 136 insertions(+), 96 deletions(-) create mode 100644 provider/resource_rediscloud_pro_subscription_cmek_test.go diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index d38e194d..f5b711b8 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -545,6 +545,10 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso if cmekEnabled == true { createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMEK_ENABLED_STRING) + + // because of CMEK flow requires a sub to be in a non-active state first. + // when it first creates the subscription, no creation plan is required + createSubscriptionRequest.Databases = nil } subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go new file mode 100644 index 00000000..c67038a2 --- /dev/null +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -0,0 +1,132 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "os" + "testing" +) + +func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { + + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + name := acctest.RandomWithPrefix(testResourcePrefix) + const resourceName = "rediscloud_subscription.example" + testCloudAccountName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckProSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_part1, testCloudAccountName, name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), + ), + }, + }, + }) +} + +const testAccResourceRedisCloudProSubscriptionCmekEnabled_part1 = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_cloud_account" "account" { + exclude_internal_account = true + provider_type = "AWS" + name = "%s" +} + +resource "rediscloud_subscription" "example" { + + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + cmek_enabled = true + + allowlist { + cidrs = ["192.168.0.0/16"] + security_group_ids = [] + } + + cloud_provider { + provider = data.rediscloud_cloud_account.account.provider_type + cloud_account_id = data.rediscloud_cloud_account.account.id + region { + region = "eu-west-1" + networking_deployment_cidr = "10.0.0.0/24" + preferred_availability_zones = ["eu-west-1a"] + } + } + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + replication = false + support_oss_cluster_api = false + query_performance_factor = "4x" + + throughput_measurement_by = "operations-per-second" + throughput_measurement_value = 10000 + modules = ["RedisJSON", "RedisBloom", "RediSearch"] + } +} +` + +const testAccResourceRedisCloudProSubscriptionCmekEnabled_update = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_cloud_account" "account" { + exclude_internal_account = true + provider_type = "AWS" + name = "%s" +} + +resource "rediscloud_subscription" "example" { + + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + cmek_enabled = true + + allowlist { + cidrs = ["192.168.0.0/16"] + security_group_ids = [] + } + + cloud_provider { + provider = data.rediscloud_cloud_account.account.provider_type + cloud_account_id = data.rediscloud_cloud_account.account.id + region { + region = "eu-west-1" + networking_deployment_cidr = "10.0.0.0/24" + preferred_availability_zones = ["eu-west-1a"] + } + } + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + replication = false + support_oss_cluster_api = false + query_performance_factor = "4x" + + throughput_measurement_by = "operations-per-second" + throughput_measurement_value = 10000 + modules = ["RedisJSON", "RedisBloom", "RediSearch"] + } + + cmek_id = "???" + + +} +` diff --git a/provider/resource_rediscloud_pro_subscription_test.go b/provider/resource_rediscloud_pro_subscription_test.go index 5ae9f8c3..d16e0311 100644 --- a/provider/resource_rediscloud_pro_subscription_test.go +++ b/provider/resource_rediscloud_pro_subscription_test.go @@ -722,30 +722,6 @@ func testAccCheckProSubscriptionDestroy(s *terraform.State) error { return nil } -func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { - - //testAccRequiresEnvVar(t, "EXECUTE_TESTS") - - name := acctest.RandomWithPrefix(testResourcePrefix) - const resourceName = "rediscloud_subscription.example" - testCloudAccountName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, - ProviderFactories: providerFactories, - CheckDestroy: testAccCheckProSubscriptionDestroy, - Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_part1, testCloudAccountName, name), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), - ), - }, - }, - }) -} - // TF config for provisioning a new subscription. const testAccResourceRedisCloudProSubscription = ` data "rediscloud_payment_method" "card" { @@ -794,78 +770,6 @@ resource "rediscloud_subscription" "example" { } ` -const testAccResourceRedisCloudProSubscriptionCmekEnabled_part1 = ` -data "rediscloud_payment_method" "card" { - card_type = "Visa" -} - -data "rediscloud_cloud_account" "account" { - exclude_internal_account = true - provider_type = "AWS" - name = "%s" -} - -resource "rediscloud_subscription" "example" { - - name = "%s" - payment_method = "credit-card" - payment_method_id = data.rediscloud_payment_method.card.id - memory_storage = "ram" - cmek_enabled = true - - allowlist { - cidrs = ["192.168.0.0/16"] - security_group_ids = [] - } - - cloud_provider { - provider = data.rediscloud_cloud_account.account.provider_type - cloud_account_id = data.rediscloud_cloud_account.account.id - region { - region = "eu-west-1" - networking_deployment_cidr = "10.0.0.0/24" - preferred_availability_zones = ["eu-west-1a"] - } - } -} -` - -const testAccResourceRedisCloudProSubscriptionCmekEnabled_update = ` -data "rediscloud_payment_method" "card" { - card_type = "Visa" -} - -data "rediscloud_cloud_account" "account" { - exclude_internal_account = true - provider_type = "AWS" - name = "%s" -} - -resource "rediscloud_subscription" "example" { - - name = "%s" - payment_method = "credit-card" - payment_method_id = data.rediscloud_payment_method.card.id - memory_storage = "ram" - cmek_enabled = true - - allowlist { - cidrs = ["192.168.0.0/16"] - security_group_ids = [] - } - - cloud_provider { - provider = data.rediscloud_cloud_account.account.provider_type - cloud_account_id = data.rediscloud_cloud_account.account.id - region { - region = "eu-west-1" - networking_deployment_cidr = "10.0.0.0/24" - preferred_availability_zones = ["eu-west-1a"] - } - } -} -` - const testAccResourceRedisCloudProSubscriptionWithRedisVersion = ` data "rediscloud_payment_method" "card" { card_type = "Visa" From 1a8e4595473e997277d810cd31f27cfeaf249f76 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Mon, 28 Jul 2025 11:58:52 +0100 Subject: [PATCH 04/27] feat: new pending status incorporated --- go.mod | 2 +- .../resource_rediscloud_pro_subscription.go | 89 ++++++++++++++----- ...e_rediscloud_pro_subscription_cmek_test.go | 81 ++++++++--------- 3 files changed, 107 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index 312b9ad5..f990c1d8 100644 --- a/go.mod +++ b/go.mod @@ -68,4 +68,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/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index f5b711b8..57a90f53 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -21,7 +21,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -const CMEK_ENABLED_STRING = "cloud-provider-managed-key" +const CMK_ENABLED_STRING = "customer-managed-key" func containsModule(modules []interface{}, requiredModule string) bool { for _, m := range modules { @@ -478,15 +478,15 @@ func resourceRedisCloudProSubscription() *schema.Resource { }, }, }, - "cmek_enabled": { - Description: "Whether to enable CMEK (customer managed encryption 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. Do not supply a creation plan if this set as true. Defaults to false.", + "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. Do not supply a creation plan if this set as true. Defaults to false.", Type: schema.TypeBool, Optional: true, ForceNew: true, Default: false, }, - "cmek_id": { - Description: "ID of the CMEK (customer managed encryption key) used to encrypt the databases in this subscription. Ignored if `cmek_enabled` set to false. Supply after the database has been put into database pending state. See documentation for CMEK flow.", + "customer_managed_key_id": { + Description: "ID of the CMK (customer managed key) 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.", Type: schema.TypeString, Optional: true, ForceNew: true, @@ -541,16 +541,14 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso createSubscriptionRequest.RedisVersion = redis.String(redisVersion) } - cmekEnabled := d.Get("cmek_enabled").(bool) + isCmk := d.Get("customer_managed_key_enabled").(bool) - if cmekEnabled == true { - createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMEK_ENABLED_STRING) - - // because of CMEK flow requires a sub to be in a non-active state first. - // when it first creates the subscription, no creation plan is required - createSubscriptionRequest.Databases = nil + if isCmk == true { + createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) } + log.Printf("createSubscriptionRequest: %v", createSubscriptionRequest) + subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) @@ -558,8 +556,18 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso d.SetId(strconv.Itoa(subId)) + // If we are in a CMK flow then we need to verify a specific state. + if isCmk == true { + err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + return diags + } + // Confirm Subscription Active status err = waitForSubscriptionToBeActive(ctx, subId, api) + if err != nil { return append(diags, diag.FromErr(err)...) } @@ -768,17 +776,24 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) - // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - return diag.FromErr(err) - } + if cmkEnabled, ok := d.GetOk("customer_managed_key"); ok && cmkEnabled.(bool) == true { + if err := waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api); err != nil { + return diag.FromErr(err) + } + } else { + // Wait for the subscription to be active before deleting it. + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } - // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. - // This additional wait ensures that the databases are deleted before the subscription is deleted. - time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - return diag.FromErr(err) + // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. + // This additional wait ensures that the databases are deleted before the subscription is deleted. + time.Sleep(30 * time.Second) //lintignore:R018 + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } } + // Delete subscription once all databases are deleted err = api.client.Subscription.Delete(ctx, subId) if err != nil { @@ -990,7 +1005,33 @@ func waitForSubscriptionToBeActive(ctx context.Context, id int, api *apiClient) PollInterval: 30 * time.Second, Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for subscription %d to be active", id) + log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusActive) + + subscription, err := api.client.Subscription.Get(ctx, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func waitForSubscriptionToBeEncryptionKeyPending(ctx context.Context, id int, api *apiClient) error { + wait := &retry.StateChangeConf{ + Pending: []string{subscriptions.SubscriptionStatusPending}, + Target: []string{subscriptions.SubscriptionStatusEncryptionKeyPending}, + Timeout: safetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusEncryptionKeyPending) subscription, err := api.client.Subscription.Get(ctx, id) if err != nil { @@ -1010,7 +1051,7 @@ func waitForSubscriptionToBeActive(ctx context.Context, id int, api *apiClient) func waitForSubscriptionToBeDeleted(ctx context.Context, id int, api *apiClient) error { wait := &retry.StateChangeConf{ Pending: []string{subscriptions.SubscriptionStatusDeleting}, - Target: []string{"deleted"}, + Target: []string{"deleted"}, // TODO: update this with deleted field in SDK Timeout: safetyTimeout, Delay: 10 * time.Second, PollInterval: 30 * time.Second, @@ -1022,7 +1063,7 @@ func waitForSubscriptionToBeDeleted(ctx context.Context, id int, api *apiClient) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { return "deleted", "deleted", nil - } + } // TODO: update this with deleted field in SDK return nil, "", err } diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index c67038a2..5fe51464 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" "testing" ) @@ -14,7 +13,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_subscription.example" - testCloudAccountName := os.Getenv("AWS_TEST_CLOUD_ACCOUNT_NAME") + //gcpProjectId := os.Getenv("GCP_PROJECT_ID") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, @@ -22,60 +21,60 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { CheckDestroy: testAccCheckProSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_part1, testCloudAccountName, name), + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_create, name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "0"), ), }, + //{ + // Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_update, gcpProjectId, name), + // Check: resource.ComposeAggregateTestCheckFunc( + // resource.TestCheckResourceAttr(resourceName, "name", name), + // resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), + // ), + //}, }, }) } -const testAccResourceRedisCloudProSubscriptionCmekEnabled_part1 = ` +const testAccResourceRedisCloudProSubscriptionCmekEnabled_create = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } -data "rediscloud_cloud_account" "account" { - exclude_internal_account = true - provider_type = "AWS" - name = "%s" -} - resource "rediscloud_subscription" "example" { - name = "%s" - payment_method = "credit-card" - payment_method_id = data.rediscloud_payment_method.card.id - memory_storage = "ram" - cmek_enabled = true + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + customer_managed_key_enabled = true allowlist { - cidrs = ["192.168.0.0/16"] - security_group_ids = [] + cidrs = ["192.168.0.0/16"] + security_group_ids = [] } - cloud_provider { - provider = data.rediscloud_cloud_account.account.provider_type - cloud_account_id = data.rediscloud_cloud_account.account.id - region { - region = "eu-west-1" - networking_deployment_cidr = "10.0.0.0/24" - preferred_availability_zones = ["eu-west-1a"] - } - } creation_plan { - dataset_size_in_gb = 1 - quantity = 1 - replication = false - support_oss_cluster_api = false - query_performance_factor = "4x" + dataset_size_in_gb = 1 + quantity = 1 + replication = false + support_oss_cluster_api = false + query_performance_factor = "4x" - throughput_measurement_by = "operations-per-second" + throughput_measurement_by = "operations-per-second" throughput_measurement_value = 10000 modules = ["RedisJSON", "RedisBloom", "RediSearch"] } + + cloud_provider { + provider = "GCP" + region { + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" + } + } } ` @@ -84,19 +83,21 @@ data "rediscloud_payment_method" "card" { card_type = "Visa" } -data "rediscloud_cloud_account" "account" { - exclude_internal_account = true - provider_type = "AWS" - name = "%s" -} - resource "rediscloud_subscription" "example" { + cloud_provider { + provider = "GCP" + cloud_account_id = %s + region { + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" + } + name = "%s" payment_method = "credit-card" payment_method_id = data.rediscloud_payment_method.card.id memory_storage = "ram" - cmek_enabled = true + customer_managed_key_enabled = true allowlist { cidrs = ["192.168.0.0/16"] From d3b53b367dd5c1fd2da77e4f7f614bf3d4fcfc1d Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 09:08:27 +0100 Subject: [PATCH 05/27] test: starting to update resource so that it can have the cmk key updated --- .../resource_rediscloud_pro_subscription.go | 97 +++++++++++++--- ...e_rediscloud_pro_subscription_cmek_test.go | 108 ++++++++---------- 2 files changed, 126 insertions(+), 79 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 57a90f53..708da097 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -482,15 +482,27 @@ 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. Do not supply a creation plan if this set as true. Defaults to false.", Type: schema.TypeBool, Optional: true, - ForceNew: true, Default: false, }, - "customer_managed_key_id": { - Description: "ID of the CMK (customer managed key) 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.", - Type: schema.TypeString, + "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.", + Type: schema.TypeList, Optional: true, - ForceNew: true, Default: nil, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_name": { + Description: "Resource name of the customer managed key as defined by the cloud provider.", + Type: schema.TypeString, + Required: true, + }, + "region": { + Description: "Name of region to for the customer managed key as defined by the cloud provider.", + Type: schema.TypeString, + Optional: true, + }, + }, + }, }, }, } @@ -613,6 +625,8 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour return diag.FromErr(err) } + cmkEnabled := d.Get("customer_managed_key_enabled") + subscription, err := api.client.Subscription.Get(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { @@ -659,22 +673,26 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } - m, err := api.client.Maintenance.Get(ctx, subId) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { - return diag.FromErr(err) - } + if cmkEnabled == false { + m, err := api.client.Maintenance.Get(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + return diag.FromErr(err) + } - pricingList, err := api.client.Pricing.List(ctx, subId) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { - return diag.FromErr(err) + pricingList, err := api.client.Pricing.List(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + return diag.FromErr(err) + } } + log.Printf("[DEBUG] Current state after read:\n%s", getResourceStateString(d)) + return diags } @@ -689,6 +707,23 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) + subscription, err := api.client.Subscription.Get(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + + // checks if we are in CMK flow + if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending { + cmk_resources := d.Get("customer_managed_keys").(*schema.Set).List() + if len(cmk_resources) == 0 { + return diag.Errorf("customer_managed_keys must be set when subscription is in encryption key pending state") + } + + ks := subscriptions.UpdateSubscriptionCMKs{} + + err = api.client.Subscription.UpdateCMKs(ctx, subId, ks) + } + if d.HasChange("allowlist") { cidrs := setToStringSlice(d.Get("allowlist.0.cidrs").(*schema.Set)) sgs := setToStringSlice(d.Get("allowlist.0.security_group_ids").(*schema.Set)) @@ -762,9 +797,33 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } +func getResourceStateString(d *schema.ResourceData) string { + var stateStr string + + // Add basic resource information + stateStr += fmt.Sprintf("Resource ID: %s\n", d.Id()) + + // Get all attributes + for k, v := range d.State().Attributes { + stateStr += fmt.Sprintf("%-40s = %v\n", k, v) + } + + // Add any known changes + stateStr += "\nChanges detected:\n" + for _, k := range d.State().Attributes { + if d.HasChange(k) { + old, new := d.GetChange(k) + stateStr += fmt.Sprintf("%-40s: %v -> %v\n", k, old, new) + } + } + + return stateStr +} + func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method api := meta.(*apiClient) + log.Printf("[DEBUG] Current state before deletion:\n%s", getResourceStateString(d)) var diags diag.Diagnostics @@ -776,7 +835,7 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) - if cmkEnabled, ok := d.GetOk("customer_managed_key"); ok && cmkEnabled.(bool) == true { + if cmkEnabled, ok := d.GetOk("customer_managed_key_enabled"); ok && cmkEnabled.(bool) == true { if err := waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api); err != nil { return diag.FromErr(err) } diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 5fe51464..5151d5e9 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -4,6 +4,7 @@ import ( "fmt" "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" "testing" ) @@ -14,103 +15,92 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_subscription.example" //gcpProjectId := os.Getenv("GCP_PROJECT_ID") + //gcpCmkId := os.Getenv("GCP_CMEK_ID") + const gcpCmkResourceName = "projects/macro-outpost-467311-v1/locations/global/keyRings/redis-test/cryptoKeys/redis-test-cmk/cryptoKeyVersions/" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, - CheckDestroy: testAccCheckProSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_create, name), + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledCreate, name), + ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "0"), ), }, - //{ - // Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabled_update, gcpProjectId, name), - // Check: resource.ComposeAggregateTestCheckFunc( - // resource.TestCheckResourceAttr(resourceName, "name", name), - // resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "1"), - // ), - //}, + { + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate, name, gcpCmkResourceName), + + Check: func(s *terraform.State) error { + // Get the resource + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found in state") + } + + // Log all attributes + t.Logf("Resource Attributes:") + for k, v := range rs.Primary.Attributes { + t.Logf("%s = %s", k, v) + } + + return nil + }, + }, }, }) } -const testAccResourceRedisCloudProSubscriptionCmekEnabled_create = ` +const testAccResourceRedisCloudProSubscriptionCmekEnabledCreate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } resource "rediscloud_subscription" "example" { - - name = "%s" - payment_method = "credit-card" - payment_method_id = data.rediscloud_payment_method.card.id - memory_storage = "ram" - customer_managed_key_enabled = true - - allowlist { - cidrs = ["192.168.0.0/16"] - security_group_ids = [] - } - - creation_plan { - dataset_size_in_gb = 1 - quantity = 1 - replication = false - support_oss_cluster_api = false - query_performance_factor = "4x" - - throughput_measurement_by = "operations-per-second" - throughput_measurement_value = 10000 - modules = ["RedisJSON", "RedisBloom", "RediSearch"] - } + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + customer_managed_key_enabled = true cloud_provider { - provider = "GCP" + provider = "GCP" region { region = "europe-west2" networking_deployment_cidr = "10.0.1.0/24" } } + + creation_plan { + dataset_size_in_gb = 1 + quantity = 1 + replication = false + support_oss_cluster_api = false + throughput_measurement_by = "operations-per-second" + throughput_measurement_value = 10000 + } } ` -const testAccResourceRedisCloudProSubscriptionCmekEnabled_update = ` +const testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } resource "rediscloud_subscription" "example" { - - cloud_provider { - provider = "GCP" - cloud_account_id = %s - region { - region = "europe-west2" - networking_deployment_cidr = "10.0.1.0/24" - } - name = "%s" payment_method = "credit-card" payment_method_id = data.rediscloud_payment_method.card.id memory_storage = "ram" customer_managed_key_enabled = true - allowlist { - cidrs = ["192.168.0.0/16"] - security_group_ids = [] - } - cloud_provider { - provider = data.rediscloud_cloud_account.account.provider_type - cloud_account_id = data.rediscloud_cloud_account.account.id + provider = "GCP" region { - region = "eu-west-1" - networking_deployment_cidr = "10.0.0.0/24" - preferred_availability_zones = ["eu-west-1a"] + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" } } @@ -119,15 +109,13 @@ resource "rediscloud_subscription" "example" { quantity = 1 replication = false support_oss_cluster_api = false - query_performance_factor = "4x" - throughput_measurement_by = "operations-per-second" throughput_measurement_value = 10000 - modules = ["RedisJSON", "RedisBloom", "RediSearch"] } - cmek_id = "???" - - + customer_managed_key { + resource_name = "%s" + region = "europe-west2" + } } ` From 3b2235cab5017fe152b2ad028f560f4d3253137f Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 10:58:03 +0100 Subject: [PATCH 06/27] chore: reorganising functions --- .../resource_rediscloud_pro_subscription.go | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 708da097..147c08e3 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -797,29 +797,6 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } -func getResourceStateString(d *schema.ResourceData) string { - var stateStr string - - // Add basic resource information - stateStr += fmt.Sprintf("Resource ID: %s\n", d.Id()) - - // Get all attributes - for k, v := range d.State().Attributes { - stateStr += fmt.Sprintf("%-40s = %v\n", k, v) - } - - // Add any known changes - stateStr += "\nChanges detected:\n" - for _, k := range d.State().Attributes { - if d.HasChange(k) { - old, new := d.GetChange(k) - stateStr += fmt.Sprintf("%-40s: %v -> %v\n", k, old, new) - } - } - - return stateStr -} - func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method api := meta.(*apiClient) @@ -869,6 +846,29 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso return diags } +func getResourceStateString(d *schema.ResourceData) string { + var stateStr string + + // Add basic resource information + stateStr += fmt.Sprintf("Resource ID: %s\n", d.Id()) + + // Get all attributes + for k, v := range d.State().Attributes { + stateStr += fmt.Sprintf("%-40s = %v\n", k, v) + } + + // Add any known changes + stateStr += "\nChanges detected:\n" + for _, k := range d.State().Attributes { + if d.HasChange(k) { + old, new := d.GetChange(k) + stateStr += fmt.Sprintf("%-40s: %v -> %v\n", k, old, new) + } + } + + return stateStr +} + func buildCreateCloudProviders(providers interface{}) ([]*subscriptions.CreateCloudProvider, error) { createCloudProviders := make([]*subscriptions.CreateCloudProvider, 0) From 8adafe4e23c7397cc75d7d64f7a18f5848a07c39 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 11:20:16 +0100 Subject: [PATCH 07/27] chore: temporarily making them not force new on a new cloud provider --- .../resource_rediscloud_pro_subscription.go | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 147c08e3..1859a204 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -155,24 +155,24 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "A cloud provider object", Type: schema.TypeList, Required: true, - ForceNew: true, - MaxItems: 1, - MinItems: 1, + //ForceNew: true, + MaxItems: 1, + MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "provider": { - Description: "The cloud provider to use with the subscription, (either `AWS` or `GCP`)", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Description: "The cloud provider to use with the subscription, (either `AWS` or `GCP`)", + Type: schema.TypeString, + Optional: true, + //ForceNew: true, Default: "AWS", ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(cloud_accounts.ProviderValues(), false)), }, "cloud_account_id": { - Description: "Cloud account identifier. Default: Redis Labs internal cloud account (using Cloud Account Id = 1 implies using Redis Labs internal cloud account). Note that a GCP subscription can be created only with Redis Labs internal cloud account", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Description: "Cloud account identifier. Default: Redis Labs internal cloud account (using Cloud Account Id = 1 implies using Redis Labs internal cloud account). Note that a GCP subscription can be created only with Redis Labs internal cloud account", + Type: schema.TypeString, + Optional: true, + //ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(regexp.MustCompile("^\\d+$"), "must be a number")), Default: "1", }, @@ -180,8 +180,8 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Cloud networking details, per region (single region or multiple regions for Active-Active cluster only)", Type: schema.TypeSet, Required: true, - ForceNew: true, - MinItems: 1, + //ForceNew: true, + MinItems: 1, Set: func(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -199,38 +199,38 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Deployment region as defined by cloud provider", Type: schema.TypeString, Required: true, - ForceNew: true, + //ForceNew: true, }, "multiple_availability_zones": { Description: "Support deployment on multiple availability zones within the selected region", Type: schema.TypeBool, - ForceNew: true, - Optional: true, - Default: false, + //ForceNew: true, + Optional: true, + Default: false, }, "preferred_availability_zones": { Description: "List of availability zones used", Type: schema.TypeList, Optional: true, - ForceNew: true, - Computed: true, + //ForceNew: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, "networking_deployment_cidr": { - Description: "Deployment CIDR mask", - Type: schema.TypeString, - ForceNew: true, + Description: "Deployment CIDR mask", + Type: schema.TypeString, + //ForceNew: true, Required: true, ValidateDiagFunc: validation.ToDiagFunc(validation.IsCIDR), }, "networking_vpc_id": { Description: "Either an existing VPC Id (already exists in the specific region) or create a new VPC (if no VPC is specified)", Type: schema.TypeString, - ForceNew: true, - Optional: true, - Default: "", + //ForceNew: true, + Optional: true, + Default: "", }, "networks": { Description: "List of networks used", From a4e925ad59436a4d4e67ac27d42f7546e47f5556 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 13:15:07 +0100 Subject: [PATCH 08/27] fix: getting pro subs working with cmk update --- .../resource_rediscloud_pro_subscription.go | 68 +++++++++++++++---- ...e_rediscloud_pro_subscription_cmek_test.go | 26 ++----- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 1859a204..b9fd52f5 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -155,7 +155,7 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "A cloud provider object", Type: schema.TypeList, Required: true, - //ForceNew: true, + //ForceNew: true, // TODO: change this back after debugging MaxItems: 1, MinItems: 1, Elem: &schema.Resource{ @@ -199,7 +199,7 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Deployment region as defined by cloud provider", Type: schema.TypeString, Required: true, - //ForceNew: true, + //ForceNew: true, // TODO: change this back after debugging }, "multiple_availability_zones": { Description: "Support deployment on multiple availability zones within the selected region", @@ -484,6 +484,12 @@ func resourceRedisCloudProSubscription() *schema.Resource { Optional: 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, + 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.", Type: schema.TypeList, @@ -496,11 +502,6 @@ func resourceRedisCloudProSubscription() *schema.Resource { Type: schema.TypeString, Required: true, }, - "region": { - Description: "Name of region to for the customer managed key as defined by the cloud provider.", - Type: schema.TypeString, - Optional: true, - }, }, }, }, @@ -712,16 +713,15 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - // checks if we are in CMK flow + // CMK flow if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending { - cmk_resources := d.Get("customer_managed_keys").(*schema.Set).List() - if len(cmk_resources) == 0 { - return diag.Errorf("customer_managed_keys must be set when subscription is in encryption key pending state") - } + diags := resourceRedisCloudProSubscriptionUpdateCmk(ctx, d, api, subId) - ks := subscriptions.UpdateSubscriptionCMKs{} + if diags != nil { + return diags + } - err = api.client.Subscription.UpdateCMKs(ctx, subId, ks) + return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } if d.HasChange("allowlist") { @@ -797,6 +797,46 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } +func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *apiClient, subId int) diag.Diagnostics { + cmkResourcesRaw, ok := d.Get("customer_managed_key").([]interface{}) + if !ok { + return diag.Errorf("invalid type for customer_managed_key") + } + + if len(cmkResourcesRaw) == 0 { + return diag.Errorf("customer_managed_key must be set when subscription is in encryption key pending state") + } + + cmks := buildCMKs(cmkResourcesRaw) + deletionGracePeriod := d.Get("customer_managed_key_deletion_grace_period").(string) + + updateCmkRequest := subscriptions.UpdateSubscriptionCMKs{ + DeletionGracePeriod: redis.String(deletionGracePeriod), + CustomerManagedKeys: &cmks, + } + + if err := api.client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func buildCMKs(cmkResources []interface{}) []subscriptions.CustomerManagedKey { + cmks := make([]subscriptions.CustomerManagedKey, 0, len(cmkResources)) + for _, resource := range cmkResources { + cmkMap := resource.(map[string]interface{}) + + cmk := subscriptions.CustomerManagedKey{ + ResourceName: redis.String(cmkMap["resource_name"].(string)), + } + + cmks = append(cmks, cmk) + } + + return cmks +} + func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method api := meta.(*apiClient) diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 5151d5e9..cda99766 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -4,7 +4,7 @@ import ( "fmt" "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" + "os" "testing" ) @@ -14,9 +14,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_subscription.example" - //gcpProjectId := os.Getenv("GCP_PROJECT_ID") - //gcpCmkId := os.Getenv("GCP_CMEK_ID") - const gcpCmkResourceName = "projects/macro-outpost-467311-v1/locations/global/keyRings/redis-test/cryptoKeys/redis-test-cmk/cryptoKeyVersions/" + gcpCmkResourceName := os.Getenv("GCP_CMK_RESOURCE_NAME") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, @@ -33,21 +31,10 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { { Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate, name, gcpCmkResourceName), - Check: func(s *terraform.State) error { - // Get the resource - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return fmt.Errorf("resource not found in state") - } - - // Log all attributes - t.Logf("Resource Attributes:") - for k, v := range rs.Primary.Attributes { - t.Logf("%s = %s", k, v) - } - - return nil - }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "0"), + ), }, }, }) @@ -115,7 +102,6 @@ resource "rediscloud_subscription" "example" { customer_managed_key { resource_name = "%s" - region = "europe-west2" } } ` From 6e1d65a1dcef69f10355fde5d231b62993fa70b2 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 14:46:20 +0100 Subject: [PATCH 09/27] feat: output subscription principal info and custom force new logic for sub regions --- .../resource_rediscloud_pro_subscription.go | 87 ++++++++++++++----- ...e_rediscloud_pro_subscription_cmek_test.go | 2 +- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index b9fd52f5..c3dc8b9a 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "log" + "reflect" "regexp" "strconv" "time" @@ -68,6 +69,11 @@ func resourceRedisCloudProSubscription() *schema.Resource { } } + err := cloudRegionsForceNewDiff(ctx, diff, meta) + if err != nil { + return err + } + return nil }, @@ -155,24 +161,24 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "A cloud provider object", Type: schema.TypeList, Required: true, - //ForceNew: true, // TODO: change this back after debugging - MaxItems: 1, - MinItems: 1, + ForceNew: true, + MaxItems: 1, + MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "provider": { - Description: "The cloud provider to use with the subscription, (either `AWS` or `GCP`)", - Type: schema.TypeString, - Optional: true, - //ForceNew: true, + Description: "The cloud provider to use with the subscription, (either `AWS` or `GCP`)", + Type: schema.TypeString, + Optional: true, + ForceNew: true, Default: "AWS", ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(cloud_accounts.ProviderValues(), false)), }, "cloud_account_id": { - Description: "Cloud account identifier. Default: Redis Labs internal cloud account (using Cloud Account Id = 1 implies using Redis Labs internal cloud account). Note that a GCP subscription can be created only with Redis Labs internal cloud account", - Type: schema.TypeString, - Optional: true, - //ForceNew: true, + Description: "Cloud account identifier. Default: Redis Labs internal cloud account (using Cloud Account Id = 1 implies using Redis Labs internal cloud account). Note that a GCP subscription can be created only with Redis Labs internal cloud account", + Type: schema.TypeString, + Optional: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(regexp.MustCompile("^\\d+$"), "must be a number")), Default: "1", }, @@ -180,8 +186,8 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Cloud networking details, per region (single region or multiple regions for Active-Active cluster only)", Type: schema.TypeSet, Required: true, - //ForceNew: true, - MinItems: 1, + ForceNew: false, // custom force new logic + MinItems: 1, Set: func(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -199,21 +205,21 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Deployment region as defined by cloud provider", Type: schema.TypeString, Required: true, - //ForceNew: true, // TODO: change this back after debugging + ForceNew: true, }, "multiple_availability_zones": { Description: "Support deployment on multiple availability zones within the selected region", Type: schema.TypeBool, - //ForceNew: true, - Optional: true, - Default: false, + ForceNew: true, + Optional: true, + Default: false, }, "preferred_availability_zones": { Description: "List of availability zones used", Type: schema.TypeList, Optional: true, - //ForceNew: true, - Computed: true, + ForceNew: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -505,10 +511,41 @@ func resourceRedisCloudProSubscription() *schema.Resource { }, }, }, + "customer_managed_key_redis_service_account": { + Description: "The principal of the Redis service account that the subscription is created in. This is used by the user to give access to their customer managed key", + Type: schema.TypeString, + Computed: true, + }, }, } } +// customised force new logic for CMK +// region changes is not available whilst the subscription is in a pending state +func cloudRegionsForceNewDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + // Add custom force new logic for region changes based on CMK + // Get the current subscription status + api := meta.(*apiClient) + subId, err := strconv.Atoi(diff.Id()) + if err != nil { + return err + } + + subscription, err := api.client.Subscription.Get(ctx, subId) + if err != nil { + return err + } + + // Only check for force new if the subscription is not in CMK pending state + if redis.StringValue(subscription.Status) != subscriptions.SubscriptionStatusEncryptionKeyPending { + o, n := diff.GetChange("cloud_provider.0.region") + if o != nil && n != nil && !reflect.DeepEqual(o, n) { + diff.ForceNew("cloud_provider.0.region") + } + } + return nil +} + func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { api := meta.(*apiClient) @@ -692,6 +729,12 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } + if cmkEnabled == true { + if err := d.Set("customer_managed_key_redis_service_account", redis.StringValue(subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount)); err != nil { + return diag.FromErr(err) + } + } + log.Printf("[DEBUG] Current state after read:\n%s", getResourceStateString(d)) return diags @@ -1283,9 +1326,9 @@ func flattenCloudDetails(cloudDetails []*subscriptions.CloudDetail, isResource b } if isResource { - regionMapString["networking_deployment_cidr"] = currentRegion.Networking[0].DeploymentCIDR - - if redis.BoolValue(currentRegion.MultipleAvailabilityZones) { + if len(currentRegion.Networking) > 0 && !redis.BoolValue(currentRegion.MultipleAvailabilityZones) { + regionMapString["networking_deployment_cidr"] = currentRegion.Networking[0].DeploymentCIDR + } else { regionMapString["networking_deployment_cidr"] = "" } } diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index cda99766..83fed784 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -25,7 +25,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), ), }, { From 2c6cbfd25fe90cb39d38cd3ae192e81d98b29534 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 15:10:33 +0100 Subject: [PATCH 10/27] fix: cmk flag not correctly set to bool --- provider/resource_rediscloud_pro_subscription.go | 14 ++++++-------- ...source_rediscloud_pro_subscription_cmek_test.go | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index c3dc8b9a..3111070b 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -69,10 +69,10 @@ func resourceRedisCloudProSubscription() *schema.Resource { } } - err := cloudRegionsForceNewDiff(ctx, diff, meta) - if err != nil { - return err - } + //err := cloudRegionsForceNewDiff(ctx, diff, meta) + //if err != nil { + // return err + //} return nil }, @@ -663,7 +663,7 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - cmkEnabled := d.Get("customer_managed_key_enabled") + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) subscription, err := api.client.Subscription.Get(ctx, subId) if err != nil { @@ -730,13 +730,11 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } if cmkEnabled == true { - if err := d.Set("customer_managed_key_redis_service_account", redis.StringValue(subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount)); err != nil { + if err := d.Set("customer_managed_key_redis_service_account", subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount); err != nil { return diag.FromErr(err) } } - log.Printf("[DEBUG] Current state after read:\n%s", getResourceStateString(d)) - return diags } diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 83fed784..134e98af 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -25,7 +25,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + //resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), ), }, { From 8e51d13dc7276a2393f7dcff931437f6d4270eaf Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 15:44:03 +0100 Subject: [PATCH 11/27] fix: custom logic for regions now does attempt to destroy CMK resources --- .../resource_rediscloud_pro_subscription.go | 74 ++++++++++++------- ...e_rediscloud_pro_subscription_cmek_test.go | 1 - 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 3111070b..93968b43 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -69,10 +69,10 @@ func resourceRedisCloudProSubscription() *schema.Resource { } } - //err := cloudRegionsForceNewDiff(ctx, diff, meta) - //if err != nil { - // return err - //} + err := cloudRegionsForceNewDiff(ctx, diff, meta) + if err != nil { + return err + } return nil }, @@ -186,7 +186,7 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Cloud networking details, per region (single region or multiple regions for Active-Active cluster only)", Type: schema.TypeSet, Required: true, - ForceNew: false, // custom force new logic + ForceNew: false, // custom force new logic enforced elsewhere MinItems: 1, Set: func(v interface{}) int { var buf bytes.Buffer @@ -205,12 +205,10 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "Deployment region as defined by cloud provider", Type: schema.TypeString, Required: true, - ForceNew: true, }, "multiple_availability_zones": { Description: "Support deployment on multiple availability zones within the selected region", Type: schema.TypeBool, - ForceNew: true, Optional: true, Default: false, }, @@ -218,25 +216,22 @@ func resourceRedisCloudProSubscription() *schema.Resource { Description: "List of availability zones used", Type: schema.TypeList, Optional: true, - ForceNew: true, Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, "networking_deployment_cidr": { - Description: "Deployment CIDR mask", - Type: schema.TypeString, - //ForceNew: true, + Description: "Deployment CIDR mask", + Type: schema.TypeString, Required: true, ValidateDiagFunc: validation.ToDiagFunc(validation.IsCIDR), }, "networking_vpc_id": { Description: "Either an existing VPC Id (already exists in the specific region) or create a new VPC (if no VPC is specified)", Type: schema.TypeString, - //ForceNew: true, - Optional: true, - Default: "", + Optional: true, + Default: "", }, "networks": { Description: "List of networks used", @@ -520,32 +515,57 @@ func resourceRedisCloudProSubscription() *schema.Resource { } } -// customised force new logic for CMK -// region changes is not available whilst the subscription is in a pending state +// cloudRegionsForceNewDiff determines if changes to cloud region should force +// creation of a new resource based on whether it's a CMK pending state. func cloudRegionsForceNewDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { - // Add custom force new logic for region changes based on CMK - // Get the current subscription status - api := meta.(*apiClient) - subId, err := strconv.Atoi(diff.Id()) - if err != nil { - return err + if diff.Id() == "" { + return handleNewResourceRegionChange(diff) } + return handleExistingResourceRegionChange(ctx, diff, meta) +} - subscription, err := api.client.Subscription.Get(ctx, subId) +func handleNewResourceRegionChange(diff *schema.ResourceDiff) error { + oldRegion, newRegion := diff.GetChange("cloud_provider.0.region") + if shouldForceNewRegion(oldRegion, newRegion) { + diff.ForceNew("cloud_provider.0.region") + } + return nil +} + +func handleExistingResourceRegionChange(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + subscription, err := getSubscription(ctx, diff, meta) if err != nil { - return err + return fmt.Errorf("failed to get subscription: %w", err) } - // Only check for force new if the subscription is not in CMK pending state + // Only check for force new if not in an encryption key pending state if redis.StringValue(subscription.Status) != subscriptions.SubscriptionStatusEncryptionKeyPending { - o, n := diff.GetChange("cloud_provider.0.region") - if o != nil && n != nil && !reflect.DeepEqual(o, n) { + oldRegion, newRegion := diff.GetChange("cloud_provider.0.region") + oldSet := oldRegion.(*schema.Set) + newSet := newRegion.(*schema.Set) + + // Check if any differences between old and new region sets + if !oldSet.Equal(newSet) { + // Force new for the entire region configuration diff.ForceNew("cloud_provider.0.region") } } return nil } +func shouldForceNewRegion(oldRegion, newRegion interface{}) bool { + return oldRegion != nil && newRegion != nil && !reflect.DeepEqual(oldRegion, newRegion) +} + +func getSubscription(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) (*subscriptions.Subscription, error) { + api := meta.(*apiClient) + subscriptionID, err := strconv.Atoi(diff.Id()) + if err != nil { + return nil, fmt.Errorf("invalid subscription ID: %w", err) + } + return api.client.Subscription.Get(ctx, subscriptionID) +} + func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { api := meta.(*apiClient) diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 134e98af..7d018638 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -33,7 +33,6 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "cloud_provider.0.region.0.preferred_availability_zones.#", "0"), ), }, }, From 9f250664e4ec5faf4ab68727e9e516e2c195c44c Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 16:37:27 +0100 Subject: [PATCH 12/27] fix: adding in extra safeguards against not supplying cmk in terraform --- .../resource_rediscloud_pro_subscription.go | 18 +++++++++--------- ...ce_rediscloud_pro_subscription_cmek_test.go | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 93968b43..4afc6c0b 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -495,7 +495,6 @@ func resourceRedisCloudProSubscription() *schema.Resource { 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.", Type: schema.TypeList, Optional: true, - Default: nil, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "resource_name": { @@ -749,12 +748,11 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } - if cmkEnabled == true { + if subscription.CustomerManagedKeyAccessDetails != nil && subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount != nil { if err := d.Set("customer_managed_key_redis_service_account", subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount); err != nil { return diag.FromErr(err) } } - return diags } @@ -859,16 +857,18 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso } func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *apiClient, subId int) diag.Diagnostics { - cmkResourcesRaw, ok := d.Get("customer_managed_key").([]interface{}) - if !ok { - return diag.Errorf("invalid type for customer_managed_key") - } - if len(cmkResourcesRaw) == 0 { + cmkResourcesRaw, exists := d.GetOk("customer_managed_key") + if !exists { return diag.Errorf("customer_managed_key must be set when subscription is in encryption key pending state") } - cmks := buildCMKs(cmkResourcesRaw) + cmkList := cmkResourcesRaw.([]interface{}) + if len(cmkList) == 0 || cmkList[0] == nil { + return diag.Errorf("customer_managed_key cannot be empty or null") + } + + cmks := buildCMKs(cmkList) deletionGracePeriod := d.Get("customer_managed_key_deletion_grace_period").(string) updateCmkRequest := subscriptions.UpdateSubscriptionCMKs{ diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 7d018638..12ac8f1e 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -11,6 +11,7 @@ import ( func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { testAccRequiresEnvVar(t, "EXECUTE_TESTS") + testAccRequiresEnvVar(t, "GCP_CMK_RESOURCE_NAME") name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_subscription.example" From 799ae16a3db44acdc317b05304a4cf8da12f8af3 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 16:57:20 +0100 Subject: [PATCH 13/27] test: changes expectations at end of test --- provider/resource_rediscloud_pro_subscription_cmek_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 12ac8f1e..1a170b5a 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -30,8 +30,8 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ), }, { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate, name, gcpCmkResourceName), - + Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate, name, gcpCmkResourceName), + ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), ), From 957ca0de13cda6865085d4a2aa5a668bd0aa8b5b Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 18:48:49 +0100 Subject: [PATCH 14/27] fix: output now correctly renders in cmk flow --- provider/resource_rediscloud_pro_subscription.go | 2 +- ...resource_rediscloud_pro_subscription_cmek_test.go | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 4afc6c0b..907e642c 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -631,7 +631,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso if err != nil { return append(diags, diag.FromErr(err)...) } - return diags + return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } // Confirm Subscription Active status diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmek_test.go index 1a170b5a..107f6fbf 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmek_test.go @@ -22,7 +22,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledCreate, name), + Config: fmt.Sprintf(step1Config, name), ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), @@ -30,7 +30,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ), }, { - Config: fmt.Sprintf(testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate, name, gcpCmkResourceName), + Config: fmt.Sprintf(step2Config, name, gcpCmkResourceName), ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), @@ -40,7 +40,7 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { }) } -const testAccResourceRedisCloudProSubscriptionCmekEnabledCreate = ` +const step1Config = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } @@ -69,9 +69,13 @@ resource "rediscloud_subscription" "example" { throughput_measurement_value = 10000 } } + +output "rediscloud_service_account" { + value = rediscloud_subscription.example.customer_managed_key_redis_service_account +} ` -const testAccResourceRedisCloudProSubscriptionCmekEnabledUpdate = ` +const step2Config = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } From 445868cb85b0d51e3b94fdd6d140605417f11074 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 29 Jul 2025 20:30:47 +0100 Subject: [PATCH 15/27] fix: better stability with waiting for subscriptions to be active vs pending --- .../resource_rediscloud_pro_subscription.go | 25 +++++++++--------- ...e_rediscloud_pro_subscription_cmk_test.go} | 26 ++++++++++++++----- 2 files changed, 33 insertions(+), 18 deletions(-) rename provider/{resource_rediscloud_pro_subscription_cmek_test.go => resource_rediscloud_pro_subscription_cmk_test.go} (60%) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 907e642c..b33e560d 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -616,8 +616,6 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) } - log.Printf("createSubscriptionRequest: %v", createSubscriptionRequest) - subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) @@ -625,7 +623,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso d.SetId(strconv.Itoa(subId)) - // If we are in a CMK flow then we need to verify a specific state. + // If in a CMK flow, verify the pending state if isCmk == true { err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) if err != nil { @@ -641,7 +639,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso return append(diags, diag.FromErr(err)...) } - // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed . + // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed. // This additional wait ensures that the databases will be listed before calling api.client.Database.List() time.Sleep(30 * time.Second) //lintignore:R018 if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { @@ -779,8 +777,6 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso if diags != nil { return diags } - - return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } if d.HasChange("allowlist") { @@ -880,6 +876,10 @@ func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.R return diag.FromErr(err) } + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } + return nil } @@ -913,11 +913,12 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) - if cmkEnabled, ok := d.GetOk("customer_managed_key_enabled"); ok && cmkEnabled.(bool) == true { - if err := waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api); err != nil { - return diag.FromErr(err) - } - } else { + subscription, err := api.client.Subscription.Get(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + + if *subscription.Status != subscriptions.SubscriptionStatusEncryptionKeyPending { // Wait for the subscription to be active before deleting it. if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) @@ -1185,7 +1186,7 @@ func waitForSubscriptionToBeActive(ctx context.Context, id int, api *apiClient) func waitForSubscriptionToBeEncryptionKeyPending(ctx context.Context, id int, api *apiClient) error { wait := &retry.StateChangeConf{ Pending: []string{subscriptions.SubscriptionStatusPending}, - Target: []string{subscriptions.SubscriptionStatusEncryptionKeyPending}, + Target: []string{subscriptions.SubscriptionStatusEncryptionKeyPending, subscriptions.SubscriptionStatusActive}, Timeout: safetyTimeout, Delay: 10 * time.Second, PollInterval: 30 * time.Second, diff --git a/provider/resource_rediscloud_pro_subscription_cmek_test.go b/provider/resource_rediscloud_pro_subscription_cmk_test.go similarity index 60% rename from provider/resource_rediscloud_pro_subscription_cmek_test.go rename to provider/resource_rediscloud_pro_subscription_cmk_test.go index 107f6fbf..e40f832d 100644 --- a/provider/resource_rediscloud_pro_subscription_cmek_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmk_test.go @@ -8,7 +8,10 @@ import ( "testing" ) -func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { +// TestAccResourceRedisCloudProSubscription_CMK is a semi-automated test that requires the user to pause midway through +// to give the CMK the necessary permissions. +// TODO: integrate the GCP provider and set up these permissions automatically +func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { testAccRequiresEnvVar(t, "EXECUTE_TESTS") testAccRequiresEnvVar(t, "GCP_CMK_RESOURCE_NAME") @@ -26,7 +29,14 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), - //resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), // number of regions + resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), + resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), ), }, { @@ -34,6 +44,14 @@ func TestAccResourceRedisCloudProSubscription_CMEK(t *testing.T) { ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), + resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), + resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), ), }, }, @@ -69,10 +87,6 @@ resource "rediscloud_subscription" "example" { throughput_measurement_value = 10000 } } - -output "rediscloud_service_account" { - value = rediscloud_subscription.example.customer_managed_key_redis_service_account -} ` const step2Config = ` From 76ec05267aed2df2db7f197489ac7e753d398887 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 30 Jul 2025 17:26:26 +0100 Subject: [PATCH 16/27] feat: active active subscription creation of cmk enabled subscription --- ...e_rediscloud_active_active_subscription.go | 106 +++++++++++---- ...oud_active_active_subscription_cmk_test.go | 123 ++++++++++++++++++ .../resource_rediscloud_pro_subscription.go | 11 +- 3 files changed, 210 insertions(+), 30 deletions(-) create mode 100644 provider/resource_rediscloud_active_active_subscription_cmk_test.go diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 731af1e3..4a1820b6 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -287,6 +287,37 @@ 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. Do not supply a creation plan if this set as true. Defaults to false.", + Type: schema.TypeBool, + Optional: 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, + 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.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_name": { + Description: "Resource name of the customer managed key as defined by the cloud provider.", + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "customer_managed_key_redis_service_account": { + Description: "The principal of the Redis service account that the subscription is created in. This is used by the user to give access to their customer managed key", + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -319,15 +350,13 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc dbs = buildSubscriptionCreatePlanAADatabases(planMap) - createSubscriptionRequest := subscriptions.CreateSubscription{ - DeploymentType: redis.String("active-active"), - Name: redis.String(name), - DryRun: redis.Bool(false), - PaymentMethodID: paymentMethodID, - PaymentMethod: redis.String(paymentMethod), - CloudProviders: providers, - Databases: dbs, - } + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + createSubscriptionRequest := newCreateSubscription(name, + paymentMethodID, + paymentMethod, + providers, + dbs, + cmkEnabled) redisVersion := d.Get("redis_version").(string) if d.Get("redis_version").(string) != "" { @@ -341,13 +370,22 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc d.SetId(strconv.Itoa(subId)) + // If in a CMK flow, verify the pending state + if cmkEnabled { + err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) + if err != nil { + return diag.FromErr(err) + } + return resourceRedisCloudActiveActiveSubscriptionRead(ctx, d, meta) + } + // Confirm Subscription Active status err = waitForSubscriptionToBeActive(ctx, subId, api) if err != nil { return diag.FromErr(err) } - // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed . + // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed. // This additional wait ensures that the databases will be listed before calling api.client.Database.List() time.Sleep(30 * time.Second) //lintignore:R018 if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { @@ -446,22 +484,24 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche return diag.FromErr(err) } - m, err := api.client.Maintenance.Get(ctx, subId) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { - return diag.FromErr(err) - } + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + if !cmkEnabled { + m, err := api.client.Maintenance.Get(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + return diag.FromErr(err) + } - pricingList, err := api.client.Pricing.List(ctx, subId) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { - return diag.FromErr(err) + pricingList, err := api.client.Pricing.List(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + return diag.FromErr(err) + } } - return diags } @@ -577,6 +617,24 @@ func resourceRedisCloudActiveActiveSubscriptionDelete(ctx context.Context, d *sc return diags } +func newCreateSubscription(name string, paymentMethodID *int, paymentMethod string, providers []*subscriptions.CreateCloudProvider, dbs []*subscriptions.CreateDatabase, cmkEnabled bool) subscriptions.CreateSubscription { + req := subscriptions.CreateSubscription{ + DeploymentType: redis.String("active-active"), + Name: redis.String(name), + DryRun: redis.Bool(false), + PaymentMethodID: paymentMethodID, + PaymentMethod: redis.String(paymentMethod), + CloudProviders: providers, + Databases: dbs, + } + + if cmkEnabled { + req.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) + } + + return req +} + func buildCreateActiveActiveCloudProviders(provider string, creationPlan map[string]interface{}) ([]*subscriptions.CreateCloudProvider, error) { createRegions := make([]*subscriptions.CreateRegion, 0) if regions := creationPlan["region"].(*schema.Set).List(); len(regions) != 0 { diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go new file mode 100644 index 00000000..7111a365 --- /dev/null +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -0,0 +1,123 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "testing" +) + +// TestAccResourceRedisCloudActiveActiveSubscription_CMK is a semi-automated test that requires the user to pause midway through +// to give the CMK the necessary permissions. +func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { + + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + testAccRequiresEnvVar(t, "GCP_CMK_RESOURCE_NAME") + + name := acctest.RandomWithPrefix(testResourcePrefix) + const resourceName = "rediscloud_active_active_subscription.example" + //gcpCmkResourceName := os.Getenv("GCP_CMK_RESOURCE_NAME") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(activeActiveCmkStep1Config, name), + ExpectNonEmptyPlan: true, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), // number of regions + resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), + resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), + ), + }, + //{ + // Config: fmt.Sprintf(activeActiveCmkStep2Config, name, gcpCmkResourceName), + // ExpectNonEmptyPlan: true, + // Check: resource.ComposeAggregateTestCheckFunc( + // resource.TestCheckResourceAttr(resourceName, "name", name), + // resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + // resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), + // resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + // resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), + // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), + // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), + // resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), + // resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), + // ), + //}, + }, + }) +} + +const activeActiveCmkStep1Config = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + customer_managed_key_enabled = true + cloud_provider = "GCP" + + creation_plan { + memory_limit_in_gb = 1 + quantity = 1 + region { + region = "europe-west1" + networking_deployment_cidr = "192.168.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + region { + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + } +} +` + +const activeActiveCmkStep2Config = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +resource "rediscloud_active_active_subscription" "example" { + name = "%s" + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id + customer_managed_key_enabled = true + cloud_provider = "GCP" + + creation_plan { + memory_limit_in_gb = 1 + quantity = 1 + region { + region = "europe-west1" + networking_deployment_cidr = "192.168.0.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + region { + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" + write_operations_per_second = 1000 + read_operations_per_second = 1000 + } + + customer_managed_key { + resource_name = "%s" + } + } +} +` diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index b33e560d..303f3dbe 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -610,9 +610,9 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso createSubscriptionRequest.RedisVersion = redis.String(redisVersion) } - isCmk := d.Get("customer_managed_key_enabled").(bool) + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) - if isCmk == true { + if cmkEnabled { createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) } @@ -624,7 +624,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso d.SetId(strconv.Itoa(subId)) // If in a CMK flow, verify the pending state - if isCmk == true { + if cmkEnabled { err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) if err != nil { return append(diags, diag.FromErr(err)...) @@ -680,8 +680,6 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - cmkEnabled := d.Get("customer_managed_key_enabled").(bool) - subscription, err := api.client.Subscription.Get(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { @@ -728,7 +726,8 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } } - if cmkEnabled == false { + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + if !cmkEnabled { m, err := api.client.Maintenance.Get(ctx, subId) if err != nil { return diag.FromErr(err) From df3ea8fdfce4418e7313b4fb0cea1d37a8ee7560 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 30 Jul 2025 17:39:09 +0100 Subject: [PATCH 17/27] feat: updates to the delete and read flow so that CMK is taken into account for AA --- ...e_rediscloud_active_active_subscription.go | 31 ++++++++++++++----- ...oud_active_active_subscription_cmk_test.go | 5 +-- .../resource_rediscloud_pro_subscription.go | 5 ++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 4a1820b6..47e7a0db 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -485,6 +485,7 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche } cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + if !cmkEnabled { m, err := api.client.Maintenance.Get(ctx, subId) if err != nil { @@ -502,6 +503,13 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche return diag.FromErr(err) } } + + if subscription.CustomerManagedKeyAccessDetails != nil && subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount != nil { + if err := d.Set("customer_managed_key_redis_service_account", subscription.CustomerManagedKeyAccessDetails.RedisServiceAccount); err != nil { + return diag.FromErr(err) + } + } + return diags } @@ -590,18 +598,25 @@ func resourceRedisCloudActiveActiveSubscriptionDelete(ctx context.Context, d *sc subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) - // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + subscription, err := api.client.Subscription.Get(ctx, subId) + if err != nil { return diag.FromErr(err) } - // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. - // This additional wait ensures that the databases are deleted before the subscription is deleted. - time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - return diag.FromErr(err) + if *subscription.Status != subscriptions.SubscriptionStatusEncryptionKeyPending { + // Wait for the subscription to be active before deleting it. + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } + + // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. + // This additional wait ensures that the databases are deleted before the subscription is deleted. + time.Sleep(30 * time.Second) //lintignore:R018 + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } + // Delete subscription once all databases are deleted } - // Delete subscription once all databases are deleted err = api.client.Subscription.Delete(ctx, subId) if err != nil { return diag.FromErr(err) diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go index 7111a365..98f97b37 100644 --- a/provider/resource_rediscloud_active_active_subscription_cmk_test.go +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -30,9 +30,7 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), - resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), - resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), - resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), // number of regions + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider"), resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), ), @@ -45,7 +43,6 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { // resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), // resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), // resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), - // resource.TestCheckResourceAttr(resourceName, "memory_storage", "ram"), // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), // resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 303f3dbe..7c006be2 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -514,8 +514,8 @@ func resourceRedisCloudProSubscription() *schema.Resource { } } -// cloudRegionsForceNewDiff determines if changes to cloud region should force -// creation of a new resource based on whether it's a CMK pending state. +// cloudRegionsForceNewDiff determines if changes to a cloud region should force +// creation of a new resource based on whether it is a CMK pending state. func cloudRegionsForceNewDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { if diff.Id() == "" { return handleNewResourceRegionChange(diff) @@ -900,7 +900,6 @@ func buildCMKs(cmkResources []interface{}) []subscriptions.CustomerManagedKey { func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method api := meta.(*apiClient) - log.Printf("[DEBUG] Current state before deletion:\n%s", getResourceStateString(d)) var diags diag.Diagnostics From e9a786111f7d988011958ee78f81967991f48c8d Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 13:26:52 +0100 Subject: [PATCH 18/27] feat: active active subscriptions now updatable with CMKs --- ...e_rediscloud_active_active_subscription.go | 68 ++++++++++++++- ...oud_active_active_subscription_cmk_test.go | 83 ++++++++++++------- .../resource_rediscloud_pro_subscription.go | 6 +- 3 files changed, 121 insertions(+), 36 deletions(-) diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 47e7a0db..23402bfa 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -306,7 +306,12 @@ func resourceRedisCloudActiveActiveSubscription() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "resource_name": { - Description: "Resource name of the customer managed key as defined by the cloud provider.", + Description: "Resource name of the customer managed key as defined by the cloud provider, e.g. projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME", + Type: schema.TypeString, + Required: true, + }, + "region": { + Description: "Name of region for the customer managed key as defined by the cloud provider.", Type: schema.TypeString, Required: true, }, @@ -524,6 +529,20 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc subscriptionMutex.Lock(subId) defer subscriptionMutex.Unlock(subId) + subscription, err := api.client.Subscription.Get(ctx, subId) + if err != nil { + return diag.FromErr(err) + } + + // CMK flow + if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending { + diags := resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx, d, api, subId) + + if diags != nil { + return diags + } + } + if d.HasChanges("name", "payment_method_id") { updateSubscriptionRequest := subscriptions.UpdateSubscription{} @@ -584,6 +603,37 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc return resourceRedisCloudActiveActiveSubscriptionRead(ctx, d, meta) } +func resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *apiClient, subId int) diag.Diagnostics { + + cmkResourcesRaw, exists := d.GetOk("customer_managed_key") + if !exists { + return diag.Errorf("customer_managed_key must be set when subscription is in encryption key pending state") + } + + cmkList := cmkResourcesRaw.([]interface{}) + if len(cmkList) == 0 || cmkList[0] == nil { + return diag.Errorf("customer_managed_key cannot be empty or null") + } + + customerManagedKeys := buildAACmks(cmkList) + deletionGracePeriod := d.Get("customer_managed_key_deletion_grace_period").(string) + + updateCmkRequest := subscriptions.UpdateSubscriptionCMKs{ + DeletionGracePeriod: redis.String(deletionGracePeriod), + CustomerManagedKeys: &customerManagedKeys, + } + + if err := api.client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { + return diag.FromErr(err) + } + + if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + return diag.FromErr(err) + } + + return nil +} + func resourceRedisCloudActiveActiveSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method api := meta.(*apiClient) @@ -748,3 +798,19 @@ func createAADatabase(dbName string, idx *int, localThroughputs []*subscriptions } return dbs } + +func buildAACmks(cmkResources []interface{}) []subscriptions.CustomerManagedKey { + cmks := make([]subscriptions.CustomerManagedKey, 0, len(cmkResources)) + for _, resource := range cmkResources { + cmkMap := resource.(map[string]interface{}) + + cmk := subscriptions.CustomerManagedKey{ + ResourceName: redis.String(cmkMap["resource_name"].(string)), + Region: redis.String(cmkMap["region"].(string)), + } + + cmks = append(cmks, cmk) + } + + return cmks +} diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go index 98f97b37..1dd5ac84 100644 --- a/provider/resource_rediscloud_active_active_subscription_cmk_test.go +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "os" "testing" ) @@ -16,7 +17,7 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_active_active_subscription.example" - //gcpCmkResourceName := os.Getenv("GCP_CMK_RESOURCE_NAME") + gcpCmkResourceName := os.Getenv("GCP_CMK_RESOURCE_NAME") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, @@ -35,31 +36,36 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), ), }, - //{ - // Config: fmt.Sprintf(activeActiveCmkStep2Config, name, gcpCmkResourceName), - // ExpectNonEmptyPlan: true, - // Check: resource.ComposeAggregateTestCheckFunc( - // resource.TestCheckResourceAttr(resourceName, "name", name), - // resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), - // resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), - // resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), - // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.provider"), - // resource.TestCheckResourceAttrSet(resourceName, "cloud_provider.0.region.#"), - // resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), - // resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), - // ), - //}, + { + Config: fmt.Sprintf(activeActiveCmkStep2Config, name, gcpCmkResourceName), + ExpectNonEmptyPlan: true, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttrSet(resourceName, "customer_managed_key_redis_service_account"), + resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_provider"), + resource.TestCheckResourceAttrSet(resourceName, "creation_plan.0.dataset_size_in_gb"), + resource.TestCheckResourceAttr(resourceName, "customer_managed_key_enabled", "true"), + ), + }, }, }) } const activeActiveCmkStep1Config = ` + + +locals { +resource_name = "%s" +} + data "rediscloud_payment_method" "card" { card_type = "Visa" } resource "rediscloud_active_active_subscription" "example" { - name = "%s" + name = local.resource_name payment_method = "credit-card" payment_method_id = data.rediscloud_payment_method.card.id customer_managed_key_enabled = true @@ -85,36 +91,49 @@ resource "rediscloud_active_active_subscription" "example" { ` const activeActiveCmkStep2Config = ` + +locals { +resource_name = "%s" +customer_managed_key_resource_name = "%s" +} + data "rediscloud_payment_method" "card" { card_type = "Visa" } resource "rediscloud_active_active_subscription" "example" { - name = "%s" - payment_method = "credit-card" - payment_method_id = data.rediscloud_payment_method.card.id + name = local.resource_name + payment_method = "credit-card" + payment_method_id = data.rediscloud_payment_method.card.id customer_managed_key_enabled = true - cloud_provider = "GCP" + cloud_provider = "GCP" + + customer_managed_key { + resource_name = local.customer_managed_key_resource_name + region = "europe-west1" + } + + customer_managed_key { + resource_name = local.customer_managed_key_resource_name + region = "europe-west2" + } creation_plan { memory_limit_in_gb = 1 - quantity = 1 + quantity = 1 region { - region = "europe-west1" - networking_deployment_cidr = "192.168.0.0/24" + region = "europe-west1" + networking_deployment_cidr = "192.168.0.0/24" write_operations_per_second = 1000 - read_operations_per_second = 1000 + read_operations_per_second = 1000 } region { - region = "europe-west2" - networking_deployment_cidr = "10.0.1.0/24" + region = "europe-west2" + networking_deployment_cidr = "10.0.1.0/24" write_operations_per_second = 1000 - read_operations_per_second = 1000 + read_operations_per_second = 1000 } - - customer_managed_key { - resource_name = "%s" - } - } + } } + ` diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 7c006be2..36a07e32 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -863,12 +863,12 @@ func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.R return diag.Errorf("customer_managed_key cannot be empty or null") } - cmks := buildCMKs(cmkList) + customerManagedKeys := buildProCmks(cmkList) deletionGracePeriod := d.Get("customer_managed_key_deletion_grace_period").(string) updateCmkRequest := subscriptions.UpdateSubscriptionCMKs{ DeletionGracePeriod: redis.String(deletionGracePeriod), - CustomerManagedKeys: &cmks, + CustomerManagedKeys: &customerManagedKeys, } if err := api.client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { @@ -882,7 +882,7 @@ func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.R return nil } -func buildCMKs(cmkResources []interface{}) []subscriptions.CustomerManagedKey { +func buildProCmks(cmkResources []interface{}) []subscriptions.CustomerManagedKey { cmks := make([]subscriptions.CustomerManagedKey, 0, len(cmkResources)) for _, resource := range cmkResources { cmkMap := resource.(map[string]interface{}) From 6cffcd2431815a24478d8fcc7d0a75ea57d2144e Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 15:02:07 +0100 Subject: [PATCH 19/27] chore: bumping the rediscloud sdk version --- CHANGELOG.md | 13 +++++++++---- go.mod | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9a05e2..05d42b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,23 @@ All notable changes to this project will be documented in this file. See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) +# 2.1.6 (31st July 2025) + +### Added + +- Customer Managed Key support for active-active and pro subscriptions. Only supports redis internal GCP cloud subscriptions. CMKs are externally provided by a customer-supplied GCP account and are managed externally by the user. # 2.1.5 (1st July 2025) ### Added -Feature: Support Marketplace as a payment method for Essentials subscription -Feature: Add TLS certificate to databases’ data sources +- Feature: Support Marketplace as a payment method for Essentials subscription +- Feature: Add TLS certificate to databases’ data sources ### Fixed: -Unexpected state `dynamic-endpoints-creation-pending' -Can not disable default user on essentials db +- Unexpected state `dynamic-endpoints-creation-pending' +- Can not disable default user on essentials db # 2.1.4 (22nd May 2025) diff --git a/go.mod b/go.mod index f990c1d8..dda23d2f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/RedisLabs/terraform-provider-rediscloud go 1.22.4 require ( - github.com/RedisLabs/rediscloud-go-api v0.30.0 + github.com/RedisLabs/rediscloud-go-api v0.31.0 github.com/bflad/tfproviderlint v0.31.0 github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 @@ -68,4 +68,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 From 1bc021f8b33d3f5691c6d056fa170efd192bfa54 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 15:24:19 +0100 Subject: [PATCH 20/27] chore: minor renaming --- provider/resource_rediscloud_pro_subscription_cmk_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription_cmk_test.go b/provider/resource_rediscloud_pro_subscription_cmk_test.go index e40f832d..d6dcb1a9 100644 --- a/provider/resource_rediscloud_pro_subscription_cmk_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmk_test.go @@ -25,7 +25,7 @@ func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(step1Config, name), + Config: fmt.Sprintf(proCmkStep1Config, name), ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), @@ -40,7 +40,7 @@ func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { ), }, { - Config: fmt.Sprintf(step2Config, name, gcpCmkResourceName), + Config: fmt.Sprintf(proCmkStep2Config, name, gcpCmkResourceName), ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", name), @@ -58,7 +58,7 @@ func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { }) } -const step1Config = ` +const proCmkStep1Config = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } @@ -89,7 +89,7 @@ resource "rediscloud_subscription" "example" { } ` -const step2Config = ` +const proCmkStep2Config = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } From f551b50c57149aefd7501cd8d5c0dbdb5c624998 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 15:31:43 +0100 Subject: [PATCH 21/27] fix: updated go.sum --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 961c1a7b..0d5e5a0c 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RedisLabs/rediscloud-go-api v0.30.0 h1:G5d3xaBNa1+nkQY9x7uAk1nV31y09k6qN1gq1ofWXYs= -github.com/RedisLabs/rediscloud-go-api v0.30.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= +github.com/RedisLabs/rediscloud-go-api v0.31.0 h1:hFdR7nrJcCVQN8h3DeXtP0g4zVQP6X5wtS5FoinG8bo= +github.com/RedisLabs/rediscloud-go-api v0.31.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= 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 d3cea0f9a78f5a26ccdf55784d188a3a041a78ef Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 15:43:59 +0100 Subject: [PATCH 22/27] chore: tidying up unused function --- .../resource_rediscloud_pro_subscription.go | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 36a07e32..26f090b0 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -946,29 +946,6 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso return diags } -func getResourceStateString(d *schema.ResourceData) string { - var stateStr string - - // Add basic resource information - stateStr += fmt.Sprintf("Resource ID: %s\n", d.Id()) - - // Get all attributes - for k, v := range d.State().Attributes { - stateStr += fmt.Sprintf("%-40s = %v\n", k, v) - } - - // Add any known changes - stateStr += "\nChanges detected:\n" - for _, k := range d.State().Attributes { - if d.HasChange(k) { - old, new := d.GetChange(k) - stateStr += fmt.Sprintf("%-40s: %v -> %v\n", k, old, new) - } - } - - return stateStr -} - func buildCreateCloudProviders(providers interface{}) ([]*subscriptions.CreateCloudProvider, error) { createCloudProviders := make([]*subscriptions.CreateCloudProvider, 0) From 01ef00c579df3f2fff3aedc903a9aadf9d0192ec Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 16:43:06 +0100 Subject: [PATCH 23/27] docs: updating documentation and schema docs --- .../rediscloud_active_active_subscription.md | 11 +++++++++++ docs/resources/rediscloud_subscription.md | 10 ++++++++++ .../resource_rediscloud_active_active_subscription.go | 4 ++-- provider/resource_rediscloud_pro_subscription.go | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/resources/rediscloud_active_active_subscription.md b/docs/resources/rediscloud_active_active_subscription.md index 0a9f82dd..3e8da02c 100644 --- a/docs/resources/rediscloud_active_active_subscription.md +++ b/docs/resources/rediscloud_active_active_subscription.md @@ -19,6 +19,8 @@ subscription, then the databases defined as separate resources will be attached the subscription. The creation_plan block can ONLY be used for provisioning new subscriptions, the block will be ignored if you make any further changes or try importing the resource (e.g. `terraform import` ...). +~> **Note:** The CMK (customer managed encryption key) fields require a specific flow which involves a multi step apply. Please refer to the relevant documents if using these fields. + ## Example Usage ```hcl @@ -62,6 +64,9 @@ The following arguments are supported: * `redis_version` - (Optional) The Redis version of the databases in the subscription. If omitted, the Redis version will be the default. **Modifying this attribute will force creation of a new resource.** * `creation_plan` - (Required) A creation plan object, documented below. Ignored after creation. * `maintenance_windows` - (Optional) The subscription's maintenance window specification, documented below. +* `customer_managed_key_enabled` - (Optional) Whether to enable the CMK flow. +* `customer_managed_key_deletion_grace_period` - (Optional) The grace period for deleting the subscription. If not set, will default to immediate deletion grace period. +* `customer_managed_key` - (Optional) The customer managed keys (CMK) to use for this subscription. If is active-active subscription, must set a key for each region. The `creation_plan` block supports: @@ -78,6 +83,10 @@ The creation_plan `region` block supports: * `write_operations_per_second` - (Required) Throughput measurement for an active-active subscription * `read_operations_per_second` - (Required) Throughput measurement for an active-active subscription +The `customer_managed_key` block supports: +* `resource_name` - Resource name of the customer managed key as defined by the cloud provider, e.g. projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME +* `region` - Name of the region for the customer managed key as defined by the cloud provider. + The `maintenance_windows` object has these attributes: * `mode` - Either `automatic` (Redis specified) or `manual` (User specified) @@ -93,6 +102,8 @@ The `window` object has these attributes: ## Attribute reference +* `customer_managed_key_redis_service_account` - Outputs the id of the service account associated with the subscription. Useful as part of the CMK flow. + * `pricing` - A list of pricing objects, documented below The `pricing` object has these attributes: diff --git a/docs/resources/rediscloud_subscription.md b/docs/resources/rediscloud_subscription.md index eff8e302..ddf82974 100644 --- a/docs/resources/rediscloud_subscription.md +++ b/docs/resources/rediscloud_subscription.md @@ -20,6 +20,8 @@ subscription, then the databases defined as separate resources will be attached the subscription. The creation_plan block can ONLY be used for provisioning new subscriptions, the block will be ignored if you make any further changes or try importing the resource (e.g. `terraform import` ...). +~> **Note:** The CMK (customer managed encryption key) fields require a specific flow which involves a multi step apply. Please refer to the relevant documents if using these fields. + ## Example Usage ```hcl @@ -80,6 +82,9 @@ The following arguments are supported: * `cloud_provider` - (Required) A cloud provider object, documented below. **Modifying this attribute will force creation of a new resource.** * `creation_plan` - (Required) A creation plan object, documented below. * `maintenance_windows` - (Optional) The subscription's maintenance window specification, documented below. +* `customer_managed_key_enabled` - (Optional) Whether to enable the customer managed encryption key flow. +* `customer_managed_key_deletion_grace_period` - (Optional) The grace period for deleting the subscription. If not set, will default to immediate deletion grace period. +* `customer_managed_key` - (Optional) The customer managed keys (CMK) to use for this subscription. If is active-active subscription, must set a key for each region. The `allowlist` block supports: @@ -128,6 +133,9 @@ The cloud_provider `region` block supports: ~> **Note:** The preferred_availability_zones parameter is required for Terraform, but is optional within the Redis Enterprise Cloud UI. This difference in behaviour is to guarantee that a plan after an apply does not generate differences. In AWS Redis internal cloud account, please set the zone IDs (for example: `["use-az2", "use-az3", "use-az5"]`). +The `customer_managed_key` block supports: +* `resource_name` - The resource name of the customer managed key as defined by the cloud provider, e.g. projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME + The `maintenance_windows` object has these attributes: * `mode` - Either `automatic` (Redis specified) or `manual` (User specified) @@ -149,6 +157,8 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d ## Attribute reference +* `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 `region` block has these attributes: * `networks` - List of generated network configuration diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index 23402bfa..cf5df7c6 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -288,7 +288,7 @@ 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. Do not supply a creation plan if this set as true. 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. Defaults to false.", Type: schema.TypeBool, Optional: true, Default: false, @@ -300,7 +300,7 @@ func resourceRedisCloudActiveActiveSubscription() *schema.Resource { 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.", + 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.", Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 26f090b0..f164c85f 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -480,7 +480,7 @@ 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. Do not supply a creation plan if this set as true. 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. Defaults to false.", Type: schema.TypeBool, Optional: true, Default: false, From d29d92e12cad355fa16ba956723c087d8aa2cf6d Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 16:44:38 +0100 Subject: [PATCH 24/27] fix: adding cmk enabled check to updates on encryption key pending as specified in the schema --- provider/resource_rediscloud_active_active_subscription.go | 4 +++- provider/resource_rediscloud_pro_subscription.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/resource_rediscloud_active_active_subscription.go index cf5df7c6..f8cef29d 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/resource_rediscloud_active_active_subscription.go @@ -534,8 +534,10 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc return diag.FromErr(err) } + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + // CMK flow - if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending { + if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending && cmkEnabled { diags := resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx, d, api, subId) if diags != nil { diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index f164c85f..c528e61d 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -769,8 +769,10 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } + cmkEnabled := d.Get("customer_managed_key_enabled").(bool) + // CMK flow - if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending { + if *subscription.Status == subscriptions.SubscriptionStatusEncryptionKeyPending && cmkEnabled { diags := resourceRedisCloudProSubscriptionUpdateCmk(ctx, d, api, subId) if diags != nil { From d07c893f07de0b59655bf346696593a975a6e4d2 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Thu, 31 Jul 2025 16:48:49 +0100 Subject: [PATCH 25/27] fix: adding checkdestroys as required by tfproviderlint --- .../resource_rediscloud_active_active_subscription_cmk_test.go | 1 + provider/resource_rediscloud_pro_subscription_cmk_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go index 1dd5ac84..d2ae8c9e 100644 --- a/provider/resource_rediscloud_active_active_subscription_cmk_test.go +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -22,6 +22,7 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CMK(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, + CheckDestroy: testAccCheckActiveActiveSubscriptionDestroy, Steps: []resource.TestStep{ { Config: fmt.Sprintf(activeActiveCmkStep1Config, name), diff --git a/provider/resource_rediscloud_pro_subscription_cmk_test.go b/provider/resource_rediscloud_pro_subscription_cmk_test.go index d6dcb1a9..6896efb6 100644 --- a/provider/resource_rediscloud_pro_subscription_cmk_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmk_test.go @@ -23,6 +23,7 @@ func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, ProviderFactories: providerFactories, + CheckDestroy: testAccCheckProSubscriptionDestroy, Steps: []resource.TestStep{ { Config: fmt.Sprintf(proCmkStep1Config, name), From 24515a108f368cb0cb4ca5ac778139fb1733af79 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Fri, 1 Aug 2025 12:16:51 +0100 Subject: [PATCH 26/27] fix: fixes to essentials subscription tests --- ...rediscloud_essentials_subscription_test.go | 7 ++ ...rce_rediscloud_essentials_database_test.go | 91 ++++++++++--------- 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/provider/rediscloud_essentials_subscription_test.go b/provider/rediscloud_essentials_subscription_test.go index 21d8b638..c1132654 100644 --- a/provider/rediscloud_essentials_subscription_test.go +++ b/provider/rediscloud_essentials_subscription_test.go @@ -310,9 +310,16 @@ data "rediscloud_essentials_plan" "example" { region = "us-east-1" } +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" +} + resource "rediscloud_essentials_subscription" "example" { name = "%s" plan_id = data.rediscloud_essentials_plan.example.id + # payment_method = "credit-card" + # payment_method_id = data.rediscloud_payment_method.card.id } data "rediscloud_essentials_subscription" "example" { diff --git a/provider/resource_rediscloud_essentials_database_test.go b/provider/resource_rediscloud_essentials_database_test.go index a2229dcf..7d1aded1 100644 --- a/provider/resource_rediscloud_essentials_database_test.go +++ b/provider/resource_rediscloud_essentials_database_test.go @@ -34,7 +34,7 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", databaseName), resource.TestCheckResourceAttr(resourceName, "protocol", "stack"), resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), - resource.TestCheckResourceAttr(resourceName, "region", "eu-west-1"), + resource.TestCheckResourceAttr(resourceName, "region", "us-east-1"), resource.TestCheckResourceAttrSet(resourceName, "redis_version_compliance"), resource.TestCheckResourceAttr(resourceName, "resp_version", "resp3"), resource.TestCheckResourceAttr(resourceName, "data_persistence", "none"), @@ -43,9 +43,6 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "public_endpoint"), resource.TestCheckResourceAttr(resourceName, "private_endpoint", ""), resource.TestCheckResourceAttr(resourceName, "source_ips.#", "0"), - resource.TestCheckResourceAttr(resourceName, "alert.#", "1"), - resource.TestCheckResourceAttr(resourceName, "alert.0.name", "throughput-higher-than"), - resource.TestCheckResourceAttr(resourceName, "alert.0.value", "80"), resource.TestCheckResourceAttr(resourceName, "enable_default_user", "true"), resource.TestCheckResourceAttr(resourceName, "password", "j43589rhe39f"), @@ -68,7 +65,7 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(datasourceName, "name", databaseName), resource.TestCheckResourceAttr(datasourceName, "protocol", "stack"), resource.TestCheckResourceAttr(datasourceName, "cloud_provider", "AWS"), - resource.TestCheckResourceAttr(datasourceName, "region", "eu-west-1"), + resource.TestCheckResourceAttr(datasourceName, "region", "us-east-1"), resource.TestCheckResourceAttrSet(datasourceName, "redis_version_compliance"), resource.TestCheckResourceAttr(datasourceName, "resp_version", "resp3"), resource.TestCheckResourceAttr(datasourceName, "data_persistence", "none"), @@ -77,9 +74,9 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttrSet(datasourceName, "public_endpoint"), resource.TestCheckResourceAttr(datasourceName, "private_endpoint", ""), resource.TestCheckResourceAttr(datasourceName, "source_ips.#", "0"), - resource.TestCheckResourceAttr(datasourceName, "alert.#", "1"), - resource.TestCheckResourceAttr(datasourceName, "alert.0.name", "throughput-higher-than"), - resource.TestCheckResourceAttr(datasourceName, "alert.0.value", "80"), + //resource.TestCheckResourceAttr(datasourceName, "alert.#", "1"), + //resource.TestCheckResourceAttr(datasourceName, "alert.0.name", "throughput-higher-than"), + //resource.TestCheckResourceAttr(datasourceName, "alert.0.value", "80"), resource.TestCheckResourceAttr(datasourceName, "enable_default_user", "true"), resource.TestCheckResourceAttr(datasourceName, "password", "j43589rhe39f"), @@ -109,7 +106,7 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", databaseNameUpdated), resource.TestCheckResourceAttr(resourceName, "protocol", "stack"), resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), - resource.TestCheckResourceAttr(resourceName, "region", "eu-west-1"), + resource.TestCheckResourceAttr(resourceName, "region", "us-east-1"), resource.TestCheckResourceAttrSet(resourceName, "redis_version_compliance"), resource.TestCheckResourceAttr(resourceName, "resp_version", "resp3"), resource.TestCheckResourceAttr(resourceName, "data_persistence", "none"), @@ -119,9 +116,9 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "public_endpoint"), resource.TestCheckResourceAttr(resourceName, "private_endpoint", ""), resource.TestCheckResourceAttr(resourceName, "source_ips.#", "0"), - resource.TestCheckResourceAttr(resourceName, "alert.#", "1"), - resource.TestCheckResourceAttr(resourceName, "alert.0.name", "throughput-higher-than"), - resource.TestCheckResourceAttr(resourceName, "alert.0.value", "80"), + //resource.TestCheckResourceAttr(resourceName, "alert.#", "1"), + //resource.TestCheckResourceAttr(resourceName, "alert.0.name", "throughput-higher-than"), + //resource.TestCheckResourceAttr(resourceName, "alert.0.value", "80"), resource.TestCheckResourceAttr(resourceName, "enable_default_user", "true"), resource.TestCheckResourceAttr(resourceName, "password", "j43589rhe39f"), @@ -140,7 +137,7 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(datasourceName, "name", databaseNameUpdated), resource.TestCheckResourceAttr(datasourceName, "protocol", "stack"), resource.TestCheckResourceAttr(datasourceName, "cloud_provider", "AWS"), - resource.TestCheckResourceAttr(datasourceName, "region", "eu-west-1"), + resource.TestCheckResourceAttr(datasourceName, "region", "us-east-1"), resource.TestCheckResourceAttrSet(datasourceName, "redis_version_compliance"), resource.TestCheckResourceAttr(datasourceName, "resp_version", "resp3"), resource.TestCheckResourceAttr(datasourceName, "data_persistence", "none"), @@ -149,9 +146,9 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttrSet(datasourceName, "public_endpoint"), resource.TestCheckResourceAttr(datasourceName, "private_endpoint", ""), resource.TestCheckResourceAttr(datasourceName, "source_ips.#", "0"), - resource.TestCheckResourceAttr(datasourceName, "alert.#", "1"), - resource.TestCheckResourceAttr(datasourceName, "alert.0.name", "throughput-higher-than"), - resource.TestCheckResourceAttr(datasourceName, "alert.0.value", "80"), + //resource.TestCheckResourceAttr(datasourceName, "alert.#", "1"), + //resource.TestCheckResourceAttr(datasourceName, "alert.0.name", "throughput-higher-than"), + //resource.TestCheckResourceAttr(datasourceName, "alert.0.value", "80"), resource.TestCheckResourceAttr(datasourceName, "enable_default_user", "true"), resource.TestCheckResourceAttr(datasourceName, "password", "j43589rhe39f"), @@ -175,19 +172,28 @@ func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { } const testAccResourceRedisCloudEssentialsDatabaseBasic = ` -data "rediscloud_payment_method" "card" { - card_type = "Visa" -} data "rediscloud_essentials_plan" "example" { - name = "250MB" + name = "30MB" cloud_provider = "AWS" - region = "eu-west-1" + region = "us-east-1" +} + +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" } + resource "rediscloud_essentials_subscription" "example" { name = "%s" plan_id = data.rediscloud_essentials_plan.example.id - payment_method_id = data.rediscloud_payment_method.card.id + # payment_method = "credit-card" + # payment_method_id = data.rediscloud_payment_method.card.id } + +data "rediscloud_essentials_subscription" "example" { + name = rediscloud_essentials_subscription.example.name +} + resource "rediscloud_essentials_database" "example" { subscription_id = rediscloud_essentials_subscription.example.id name = "%s" @@ -197,10 +203,10 @@ resource "rediscloud_essentials_database" "example" { data_persistence = "none" replication = false - alert { - name = "throughput-higher-than" - value = 80 - } + # alert { + # name = "throughput-higher-than" + # value = 80 + # } tags = { "environment" = "production" @@ -222,7 +228,7 @@ data "rediscloud_payment_method" "card" { data "rediscloud_essentials_plan" "example" { name = "250MB" cloud_provider = "AWS" - region = "eu-west-1" + region = "us-east-1" } resource "rediscloud_essentials_subscription" "example" { name = "%s" @@ -238,10 +244,10 @@ resource "rediscloud_essentials_database" "example" { data_persistence = "none" replication = false - alert { - name = "throughput-higher-than" - value = 80 - } + # alert { + # name = "throughput-higher-than" + # value = 80 + # '} tags = { "UpperCaseKey" = "invalid" @@ -333,7 +339,7 @@ data "rediscloud_payment_method" "card" { data "rediscloud_essentials_plan" "example" { name = "Single-Zone_1GB" cloud_provider = "AWS" - region = "eu-west-1" + region = "us-east-1" } data "rediscloud_essentials_database" "example" { @@ -355,11 +361,11 @@ resource "rediscloud_essentials_database" "example" { data_persistence = "none" replication = false - - alert { - name = "throughput-higher-than" - value = 80 - } + # + # alert { + # name = "throughput-higher-than" + # value = 80 + # } tags = { "envaaaa" = "qaaaa" } @@ -374,7 +380,7 @@ data "rediscloud_payment_method" "card" { data "rediscloud_essentials_plan" "example" { name = "Single-Zone_1GB" cloud_provider = "AWS" - region = "eu-west-1" + region = "us-east-1" } data "rediscloud_essentials_database" "example" { @@ -395,10 +401,11 @@ resource "rediscloud_essentials_database" "example" { data_persistence = "none" replication = false - alert { - name = "throughput-higher-than" - value = 80 - } + # alert { + # name = "throughput-higher-than" + # value = 80 + # } + tags = { "envaaaa" = "qaaaa" } From ecc3da2efa7c0151316b3b214f7c80d50965d225 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Fri, 1 Aug 2025 12:30:54 +0100 Subject: [PATCH 27/27] test: updating all tests to use a specific payment card as there are now multiple within the account --- .../datasource_rediscloud_essentials_plan_test.go | 1 + .../datasource_rediscloud_payment_method_test.go | 5 +++-- .../datasource_rediscloud_pro_database_test.go | 4 +++- ...datasource_rediscloud_pro_subscription_test.go | 6 +++++- ...ource_rediscloud_subscription_peerings_test.go | 3 ++- .../rediscloud_active_active_database_test.go | 2 ++ ...vate_service_connect_endpoint_accepter_test.go | 3 ++- ...ctive_private_service_connect_endpoint_test.go | 4 ++-- ..._active_active_private_service_connect_test.go | 3 ++- .../rediscloud_active_active_subscription_test.go | 3 +++ .../rediscloud_essentials_subscription_test.go | 2 ++ ...vate_service_connect_endpoint_accepter_test.go | 3 ++- ...cloud_private_service_connect_endpoint_test.go | 3 ++- .../rediscloud_private_service_connect_test.go | 3 ++- .../rediscloud_transit_gateway_attachment_test.go | 9 ++++++--- ...iscloud_active_active_subscription_cmk_test.go | 6 ++++-- ...oud_active_active_subscription_peering_test.go | 6 ++++-- ...oud_active_active_subscription_regions_test.go | 1 + ...esource_rediscloud_essentials_database_test.go | 4 ++++ .../resource_rediscloud_pro_database_qpf_test.go | 3 ++- provider/resource_rediscloud_pro_database_test.go | 7 +++++-- ...source_rediscloud_pro_subscription_cmk_test.go | 6 ++++-- ...source_rediscloud_pro_subscription_qpf_test.go | 3 ++- .../resource_rediscloud_pro_subscription_test.go | 15 ++++++++++----- provider/resource_rediscloud_pro_tls_test.go | 3 ++- ...source_rediscloud_subscription_peering_test.go | 6 ++++-- 26 files changed, 81 insertions(+), 33 deletions(-) diff --git a/provider/datasource_rediscloud_essentials_plan_test.go b/provider/datasource_rediscloud_essentials_plan_test.go index 499feba4..ee1dae1a 100644 --- a/provider/datasource_rediscloud_essentials_plan_test.go +++ b/provider/datasource_rediscloud_essentials_plan_test.go @@ -196,6 +196,7 @@ data "rediscloud_essentials_plan" "impossible" { const testAccResourceRedisCloudPaidEssentialsSubscriptionDataSource = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_essentials_plan" "fixed" { diff --git a/provider/datasource_rediscloud_payment_method_test.go b/provider/datasource_rediscloud_payment_method_test.go index b8249b00..bc0f572e 100644 --- a/provider/datasource_rediscloud_payment_method_test.go +++ b/provider/datasource_rediscloud_payment_method_test.go @@ -31,7 +31,8 @@ func TestAccDataSourceRedisCloudPaymentMethod_basic(t *testing.T) { } const testAccDataSourceRedisCloudPaymentMethod = ` -data "rediscloud_payment_method" "foo" { - card_type = "Visa" +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" } ` diff --git a/provider/datasource_rediscloud_pro_database_test.go b/provider/datasource_rediscloud_pro_database_test.go index 9cec9240..23654de7 100644 --- a/provider/datasource_rediscloud_pro_database_test.go +++ b/provider/datasource_rediscloud_pro_database_test.go @@ -69,8 +69,10 @@ func TestAccDataSourceRedisCloudProDatabase_basic(t *testing.T) { const testAccDatasourceRedisCloudProDatabase = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } + data "rediscloud_cloud_account" "account" { exclude_internal_account = true provider_type = "AWS" diff --git a/provider/datasource_rediscloud_pro_subscription_test.go b/provider/datasource_rediscloud_pro_subscription_test.go index 147e6f90..22a83ee1 100644 --- a/provider/datasource_rediscloud_pro_subscription_test.go +++ b/provider/datasource_rediscloud_pro_subscription_test.go @@ -81,8 +81,10 @@ func TestAccDataSourceRedisCloudProSubscription_ignoresAA(t *testing.T) { const testAccDatasourceRedisCloudProSubscription = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } + data "rediscloud_cloud_account" "account" { exclude_internal_account = true provider_type = "AWS" @@ -132,7 +134,9 @@ data "rediscloud_subscription" "example" { const testAccDatasourceRedisCloudAADatabaseWithProDataSource = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } + resource "rediscloud_active_active_subscription" "example" { name = "%s" payment_method_id = data.rediscloud_payment_method.card.id diff --git a/provider/datasource_rediscloud_subscription_peerings_test.go b/provider/datasource_rediscloud_subscription_peerings_test.go index 99786533..30ef98d2 100644 --- a/provider/datasource_rediscloud_subscription_peerings_test.go +++ b/provider/datasource_rediscloud_subscription_peerings_test.go @@ -60,7 +60,8 @@ func TestAccDataSourceRedisCloudSubscriptionPeerings_basic(t *testing.T) { const testAccDatasourceRedisCloudSubscriptionPeeringsDataSource = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/rediscloud_active_active_database_test.go b/provider/rediscloud_active_active_database_test.go index 40d0f1ec..bc0cc996 100644 --- a/provider/rediscloud_active_active_database_test.go +++ b/provider/rediscloud_active_active_database_test.go @@ -262,6 +262,7 @@ func TestAccResourceRedisCloudActiveActiveDatabase_timeUtcRequiresValidInterval( const activeActiveSubscriptionBoilerplate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { @@ -407,6 +408,7 @@ resource "rediscloud_active_active_subscription_database" "example" { const testAccResourceRedisCloudActiveActiveDatabaseOptionalAttributes = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go b/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go index c055c7fa..e9cb0145 100644 --- a/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go +++ b/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go @@ -71,7 +71,8 @@ func TestAccResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter_ const testAccResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterPro = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "subscription" { diff --git a/provider/rediscloud_active_active_private_service_connect_endpoint_test.go b/provider/rediscloud_active_active_private_service_connect_endpoint_test.go index 8145095d..4938de57 100644 --- a/provider/rediscloud_active_active_private_service_connect_endpoint_test.go +++ b/provider/rediscloud_active_active_private_service_connect_endpoint_test.go @@ -62,9 +62,9 @@ func TestAccResourceRedisCloudActiveActivePrivateServiceConnectEndpoint_CRUDI(t const testAccResourceRedisCloudActiveActivePrivateServiceConnectEndpointProStep1 = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } - resource "rediscloud_active_active_subscription" "subscription_resource" { name = "%s" payment_method_id = data.rediscloud_payment_method.card.id diff --git a/provider/rediscloud_active_active_private_service_connect_test.go b/provider/rediscloud_active_active_private_service_connect_test.go index b19a0994..cdfaa98c 100644 --- a/provider/rediscloud_active_active_private_service_connect_test.go +++ b/provider/rediscloud_active_active_private_service_connect_test.go @@ -54,7 +54,8 @@ func TestAccResourceRedisCloudActiveActivePrivateServiceConnect_CRUDI(t *testing const testAccResourceRedisCloudActiveActivePrivateServiceConnectProStep1 = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "subscription_resource" { diff --git a/provider/rediscloud_active_active_subscription_test.go b/provider/rediscloud_active_active_subscription_test.go index 6c89f4b4..fe825f68 100644 --- a/provider/rediscloud_active_active_subscription_test.go +++ b/provider/rediscloud_active_active_subscription_test.go @@ -346,6 +346,7 @@ func testAccCheckActiveActiveSubscriptionDestroy(s *terraform.State) error { const testAccResourceRedisCloudActiveActiveSubscription = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { @@ -435,6 +436,7 @@ const testAccResourceRedisCloudActiveActiveSubscriptionNoCreationPlan = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { @@ -455,6 +457,7 @@ data "rediscloud_active_active_subscription" "example" { const testAccResourceRedisCloudActiveActiveSubscriptionChangedPaymentMethod = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/rediscloud_essentials_subscription_test.go b/provider/rediscloud_essentials_subscription_test.go index c1132654..4b1a3aea 100644 --- a/provider/rediscloud_essentials_subscription_test.go +++ b/provider/rediscloud_essentials_subscription_test.go @@ -348,6 +348,7 @@ data "rediscloud_essentials_subscription" "example" { const testAccResourceRedisCloudPaidCreditCardEssentialsSubscription = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_essentials_plan" "example" { @@ -372,6 +373,7 @@ data "rediscloud_essentials_subscription" "example" { const testAccResourceRedisCloudPaidNoPaymentTypeEssentialsSubscription = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_essentials_plan" "example" { diff --git a/provider/rediscloud_private_service_connect_endpoint_accepter_test.go b/provider/rediscloud_private_service_connect_endpoint_accepter_test.go index a0a1ec86..dfddf854 100644 --- a/provider/rediscloud_private_service_connect_endpoint_accepter_test.go +++ b/provider/rediscloud_private_service_connect_endpoint_accepter_test.go @@ -70,7 +70,8 @@ func TestAccResourceRedisCloudPrivateServiceConnectEndpointAccepter_Create(t *te const testAccResourceRedisCloudPrivateServiceConnectEndpointAccepterPro = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "subscription" { diff --git a/provider/rediscloud_private_service_connect_endpoint_test.go b/provider/rediscloud_private_service_connect_endpoint_test.go index 848b3db1..4b67f061 100644 --- a/provider/rediscloud_private_service_connect_endpoint_test.go +++ b/provider/rediscloud_private_service_connect_endpoint_test.go @@ -62,7 +62,8 @@ func TestAccResourceRedisCloudPrivateServiceConnectEndpoint_CRUDI(t *testing.T) const testAccResourceRedisCloudPrivateServiceConnectEndpointProStep1 = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "subscription_resource" { diff --git a/provider/rediscloud_private_service_connect_test.go b/provider/rediscloud_private_service_connect_test.go index 1b8ab305..e7b32cee 100644 --- a/provider/rediscloud_private_service_connect_test.go +++ b/provider/rediscloud_private_service_connect_test.go @@ -52,7 +52,8 @@ func TestAccResourceRedisCloudPrivateServiceConnect_CRUDI(t *testing.T) { const testAccResourceRedisCloudPrivateServiceConnectProStep1 = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "subscription_resource" { diff --git a/provider/rediscloud_transit_gateway_attachment_test.go b/provider/rediscloud_transit_gateway_attachment_test.go index 777860ff..f882cab1 100644 --- a/provider/rediscloud_transit_gateway_attachment_test.go +++ b/provider/rediscloud_transit_gateway_attachment_test.go @@ -66,7 +66,8 @@ func TestAccResourceRedisCloudTransitGatewayAttachment_Pro(t *testing.T) { const testAccResourceRedisCloudTransitGatewayPro = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -116,7 +117,8 @@ data "rediscloud_transit_gateway" "test" { const testAccResourceRedisCloudTransitGatewayAttachmentPro = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -171,7 +173,8 @@ resource "rediscloud_transit_gateway_attachment" "test" { const testAccResourceRedisCloudTransitGatewayAttachmentProWithCidrs = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go index d2ae8c9e..5f2e5984 100644 --- a/provider/resource_rediscloud_active_active_subscription_cmk_test.go +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -62,7 +62,8 @@ resource_name = "%s" } data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { @@ -99,7 +100,8 @@ customer_managed_key_resource_name = "%s" } 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/resource_rediscloud_active_active_subscription_peering_test.go b/provider/resource_rediscloud_active_active_subscription_peering_test.go index 0928d75b..8afda00a 100644 --- a/provider/resource_rediscloud_active_active_subscription_peering_test.go +++ b/provider/resource_rediscloud_active_active_subscription_peering_test.go @@ -112,7 +112,8 @@ func TestAccResourceRedisCloudActiveActiveSubscriptionPeering_gcp(t *testing.T) const testAccResourceRedisCloudActiveActiveSubscriptionPeeringAWS = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { @@ -151,7 +152,8 @@ resource "rediscloud_active_active_subscription_peering" "test" { const testAccResourceRedisCloudActiveActiveSubscriptionPeeringGCP = ` 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/resource_rediscloud_active_active_subscription_regions_test.go b/provider/resource_rediscloud_active_active_subscription_regions_test.go index fb0138eb..10958efa 100644 --- a/provider/resource_rediscloud_active_active_subscription_regions_test.go +++ b/provider/resource_rediscloud_active_active_subscription_regions_test.go @@ -122,6 +122,7 @@ func TestAccResourceRedisCloudActiveActiveSubscriptionRegions_CRUDI(t *testing.T const testAARegionsBoilerplate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_active_active_subscription" "example" { diff --git a/provider/resource_rediscloud_essentials_database_test.go b/provider/resource_rediscloud_essentials_database_test.go index 7d1aded1..27ea93b1 100644 --- a/provider/resource_rediscloud_essentials_database_test.go +++ b/provider/resource_rediscloud_essentials_database_test.go @@ -224,7 +224,9 @@ data "rediscloud_essentials_database" "example" { const testAccResourceRedisCloudEssentialsDatabaseBasicWithUpperCaseTagKey = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } + data "rediscloud_essentials_plan" "example" { name = "250MB" cloud_provider = "AWS" @@ -334,6 +336,7 @@ const testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserCreate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_essentials_plan" "example" { @@ -375,6 +378,7 @@ resource "rediscloud_essentials_database" "example" { const testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserUpdate = ` data "rediscloud_payment_method" "card" { card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_essentials_plan" "example" { diff --git a/provider/resource_rediscloud_pro_database_qpf_test.go b/provider/resource_rediscloud_pro_database_qpf_test.go index 24cb2496..4fab7c9e 100644 --- a/provider/resource_rediscloud_pro_database_qpf_test.go +++ b/provider/resource_rediscloud_pro_database_qpf_test.go @@ -14,7 +14,8 @@ import ( func proSubscriptionQPFBoilerplate(name, cloudAccountName, qpf string) string { return fmt.Sprintf(` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/resource_rediscloud_pro_database_test.go b/provider/resource_rediscloud_pro_database_test.go index c284efa4..b7250b2d 100644 --- a/provider/resource_rediscloud_pro_database_test.go +++ b/provider/resource_rediscloud_pro_database_test.go @@ -273,7 +273,8 @@ func TestAccResourceRedisCloudProDatabase_respversion(t *testing.T) { const proSubscriptionBoilerplate = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -317,8 +318,10 @@ resource "rediscloud_subscription" "example" { const multiModulesProSubscriptionBoilerplate = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } + data "rediscloud_cloud_account" "account" { exclude_internal_account = true provider_type = "AWS" diff --git a/provider/resource_rediscloud_pro_subscription_cmk_test.go b/provider/resource_rediscloud_pro_subscription_cmk_test.go index 6896efb6..d20395a8 100644 --- a/provider/resource_rediscloud_pro_subscription_cmk_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmk_test.go @@ -61,7 +61,8 @@ func TestAccResourceRedisCloudProSubscription_CMK(t *testing.T) { const proCmkStep1Config = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "example" { @@ -92,7 +93,8 @@ resource "rediscloud_subscription" "example" { const proCmkStep2Config = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "example" { diff --git a/provider/resource_rediscloud_pro_subscription_qpf_test.go b/provider/resource_rediscloud_pro_subscription_qpf_test.go index 9c97b8bb..c20319d1 100644 --- a/provider/resource_rediscloud_pro_subscription_qpf_test.go +++ b/provider/resource_rediscloud_pro_subscription_qpf_test.go @@ -14,7 +14,8 @@ import ( func formatSubscriptionConfig(name, cloudAccountName, qpf, extraConfig string) string { return fmt.Sprintf(` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/resource_rediscloud_pro_subscription_test.go b/provider/resource_rediscloud_pro_subscription_test.go index d16e0311..0ea71a11 100644 --- a/provider/resource_rediscloud_pro_subscription_test.go +++ b/provider/resource_rediscloud_pro_subscription_test.go @@ -725,7 +725,8 @@ func testAccCheckProSubscriptionDestroy(s *terraform.State) error { // TF config for provisioning a new subscription. const testAccResourceRedisCloudProSubscription = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -772,7 +773,8 @@ resource "rediscloud_subscription" "example" { const testAccResourceRedisCloudProSubscriptionWithRedisVersion = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -818,7 +820,8 @@ resource "rediscloud_subscription" "test" { const testAccResourceRedisCloudProSubscriptionPreferredAZsModulesOptional = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -861,7 +864,8 @@ resource "rediscloud_subscription" "example" { // TF config for provisioning a subscription without the creation_plan block. const testAccResourceRedisCloudProSubscriptionNoCreationPlan = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -1016,7 +1020,8 @@ resource "rediscloud_subscription" "example" { const testAccResourceRedisCloudProSubscriptionMaintenanceWindows = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/resource_rediscloud_pro_tls_test.go b/provider/resource_rediscloud_pro_tls_test.go index 2c0b86ac..b68033b4 100644 --- a/provider/resource_rediscloud_pro_tls_test.go +++ b/provider/resource_rediscloud_pro_tls_test.go @@ -383,7 +383,8 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn const subscriptionBoilerplate = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { diff --git a/provider/resource_rediscloud_subscription_peering_test.go b/provider/resource_rediscloud_subscription_peering_test.go index 28c68db0..4ec71d8c 100644 --- a/provider/resource_rediscloud_subscription_peering_test.go +++ b/provider/resource_rediscloud_subscription_peering_test.go @@ -138,7 +138,8 @@ func cidrRangesOverlap(cidr1 string, cidr2 string) (bool, error) { const testAccResourceRedisCloudSubscriptionPeeringAWS = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } data "rediscloud_cloud_account" "account" { @@ -184,7 +185,8 @@ resource "rediscloud_subscription_peering" "test" { const testAccResourceRedisCloudSubscriptionPeeringGCP = ` data "rediscloud_payment_method" "card" { - card_type = "Visa" + card_type = "Visa" + last_four_numbers = "5556" } resource "rediscloud_subscription" "example" {