diff --git a/.changelog/10367.txt b/.changelog/10367.txt new file mode 100644 index 0000000000..dda2bce537 --- /dev/null +++ b/.changelog/10367.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +redis: added a `deletion_protection_enabled` field with a default value of `true` to the `google_redis_cluster` resource +``` \ No newline at end of file diff --git a/google-beta/services/redis/resource_redis_cluster.go b/google-beta/services/redis/resource_redis_cluster.go index 4d2a4d4ebc..13d6eb1290 100644 --- a/google-beta/services/redis/resource_redis_cluster.go +++ b/google-beta/services/redis/resource_redis_cluster.go @@ -95,6 +95,14 @@ projects/{network_project_id_or_number}/global/networks/{network_id}.`, Description: `Optional. The authorization mode of the Redis cluster. If not provided, auth feature is disabled for the cluster. Default value: "AUTH_MODE_DISABLED" Possible values: ["AUTH_MODE_UNSPECIFIED", "AUTH_MODE_IAM_AUTH", "AUTH_MODE_DISABLED"]`, Default: "AUTH_MODE_DISABLED", }, + "deletion_protection_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: `Optional. Indicates if the cluster is deletion protected or not. +If the value if set to true, any delete cluster operation will fail. +Default value is true.`, + Default: true, + }, "node_type": { Type: schema.TypeString, Computed: true, @@ -347,6 +355,12 @@ func resourceRedisClusterCreate(d *schema.ResourceData, meta interface{}) error } else if v, ok := d.GetOkExists("shard_count"); !tpgresource.IsEmptyValue(reflect.ValueOf(shardCountProp)) && (ok || !reflect.DeepEqual(v, shardCountProp)) { obj["shardCount"] = shardCountProp } + deletionProtectionEnabledProp, err := expandRedisClusterDeletionProtectionEnabled(d.Get("deletion_protection_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("deletion_protection_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(deletionProtectionEnabledProp)) && (ok || !reflect.DeepEqual(v, deletionProtectionEnabledProp)) { + obj["deletionProtectionEnabled"] = deletionProtectionEnabledProp + } redisConfigsProp, err := expandRedisClusterRedisConfigs(d.Get("redis_configs"), d, config) if err != nil { return err @@ -502,6 +516,9 @@ func resourceRedisClusterRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("shard_count", flattenRedisClusterShardCount(res["shardCount"], d, config)); err != nil { return fmt.Errorf("Error reading Cluster: %s", err) } + if err := d.Set("deletion_protection_enabled", flattenRedisClusterDeletionProtectionEnabled(res["deletionProtectionEnabled"], d, config)); err != nil { + return fmt.Errorf("Error reading Cluster: %s", err) + } if err := d.Set("redis_configs", flattenRedisClusterRedisConfigs(res["redisConfigs"], d, config)); err != nil { return fmt.Errorf("Error reading Cluster: %s", err) } @@ -543,6 +560,12 @@ func resourceRedisClusterUpdate(d *schema.ResourceData, meta interface{}) error } else if v, ok := d.GetOkExists("shard_count"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, shardCountProp)) { obj["shardCount"] = shardCountProp } + deletionProtectionEnabledProp, err := expandRedisClusterDeletionProtectionEnabled(d.Get("deletion_protection_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("deletion_protection_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, deletionProtectionEnabledProp)) { + obj["deletionProtectionEnabled"] = deletionProtectionEnabledProp + } redisConfigsProp, err := expandRedisClusterRedisConfigs(d.Get("redis_configs"), d, config) if err != nil { return err @@ -571,6 +594,10 @@ func resourceRedisClusterUpdate(d *schema.ResourceData, meta interface{}) error updateMask = append(updateMask, "shardCount") } + if d.HasChange("deletion_protection_enabled") { + updateMask = append(updateMask, "deletionProtectionEnabled") + } + if d.HasChange("redis_configs") { updateMask = append(updateMask, "redisConfigs") } @@ -958,6 +985,10 @@ func flattenRedisClusterShardCount(v interface{}, d *schema.ResourceData, config return v // let terraform core handle it otherwise } +func flattenRedisClusterDeletionProtectionEnabled(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenRedisClusterRedisConfigs(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { return v } @@ -1042,6 +1073,10 @@ func expandRedisClusterShardCount(v interface{}, d tpgresource.TerraformResource return v, nil } +func expandRedisClusterDeletionProtectionEnabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandRedisClusterRedisConfigs(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil diff --git a/google-beta/services/redis/resource_redis_cluster_generated_test.go b/google-beta/services/redis/resource_redis_cluster_generated_test.go index 6fe407fa32..30e8d9bcd7 100644 --- a/google-beta/services/redis/resource_redis_cluster_generated_test.go +++ b/google-beta/services/redis/resource_redis_cluster_generated_test.go @@ -34,8 +34,8 @@ func TestAccRedisCluster_redisClusterHaExample(t *testing.T) { t.Parallel() context := map[string]interface{}{ - "prevent_destroy": false, - "random_suffix": acctest.RandString(t, 10), + "deletion_protection_enabled": false, + "random_suffix": acctest.RandString(t, 10), } acctest.VcrTest(t, resource.TestCase{ @@ -72,16 +72,14 @@ resource "google_redis_cluster" "cluster-ha" { redis_configs = { maxmemory-policy = "volatile-ttl" } + deletion_protection_enabled = false + zone_distribution_config { mode = "MULTI_ZONE" } depends_on = [ google_network_connectivity_service_connection_policy.default ] - - lifecycle { - prevent_destroy = %{prevent_destroy} - } } resource "google_network_connectivity_service_connection_policy" "default" { @@ -113,8 +111,8 @@ func TestAccRedisCluster_redisClusterHaSingleZoneExample(t *testing.T) { t.Parallel() context := map[string]interface{}{ - "prevent_destroy": false, - "random_suffix": acctest.RandString(t, 10), + "deletion_protection_enabled": false, + "random_suffix": acctest.RandString(t, 10), } acctest.VcrTest(t, resource.TestCase{ @@ -148,13 +146,11 @@ resource "google_redis_cluster" "cluster-ha-single-zone" { mode = "SINGLE_ZONE" zone = "us-central1-f" } + deletion_protection_enabled = false depends_on = [ google_network_connectivity_service_connection_policy.default ] - lifecycle { - prevent_destroy = %{prevent_destroy} - } } resource "google_network_connectivity_service_connection_policy" "default" { diff --git a/google-beta/services/redis/resource_redis_cluster_test.go b/google-beta/services/redis/resource_redis_cluster_test.go index f63f080dcf..0dd39943c6 100644 --- a/google-beta/services/redis/resource_redis_cluster_test.go +++ b/google-beta/services/redis/resource_redis_cluster_test.go @@ -12,6 +12,7 @@ import ( ) func TestAccRedisCluster_createClusterWithNodeType(t *testing.T) { + t.Parallel() name := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) @@ -23,7 +24,7 @@ func TestAccRedisCluster_createClusterWithNodeType(t *testing.T) { Steps: []resource.TestStep{ { // create cluster with replica count 1 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, preventDestroy: true, nodeType: "REDIS_STANDARD_SMALL", zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, deletionProtectionEnabled: true, nodeType: "REDIS_STANDARD_SMALL", zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -33,7 +34,7 @@ func TestAccRedisCluster_createClusterWithNodeType(t *testing.T) { }, { // clean up the resource - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, preventDestroy: false, nodeType: "REDIS_STANDARD_SMALL", zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, deletionProtectionEnabled: false, nodeType: "REDIS_STANDARD_SMALL", zoneDistributionMode: "MULTI_ZONE"}), }, }, }) @@ -52,7 +53,7 @@ func TestAccRedisCluster_createClusterWithZoneDistribution(t *testing.T) { Steps: []resource.TestStep{ { // create cluster with replica count 1 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, preventDestroy: false, zoneDistributionMode: "SINGLE_ZONE", zone: "us-central1-b"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: false, zoneDistributionMode: "SINGLE_ZONE", zone: "us-central1-b"}), }, { ResourceName: "google_redis_cluster.test", @@ -62,7 +63,7 @@ func TestAccRedisCluster_createClusterWithZoneDistribution(t *testing.T) { }, { // clean up the resource - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, preventDestroy: false, zoneDistributionMode: "SINGLE_ZONE", zone: "us-central1-b"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: false, zoneDistributionMode: "SINGLE_ZONE", zone: "us-central1-b"}), }, }, }) @@ -81,7 +82,7 @@ func TestAccRedisCluster_updateReplicaCount(t *testing.T) { Steps: []resource.TestStep{ { // create cluster with replica count 1 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, preventDestroy: true, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -91,7 +92,7 @@ func TestAccRedisCluster_updateReplicaCount(t *testing.T) { }, { // update replica count to 2 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 2, shardCount: 3, preventDestroy: true, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 2, shardCount: 3, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -99,13 +100,9 @@ func TestAccRedisCluster_updateReplicaCount(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"psc_configs"}, }, - { - // clean up the resource - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, preventDestroy: false, zoneDistributionMode: "MULTI_ZONE"}), - }, { // update replica count to 0 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, preventDestroy: true, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -115,7 +112,7 @@ func TestAccRedisCluster_updateReplicaCount(t *testing.T) { }, { // clean up the resource - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, preventDestroy: false, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: false, zoneDistributionMode: "MULTI_ZONE"}), }, }, }) @@ -134,7 +131,7 @@ func TestAccRedisCluster_updateShardCount(t *testing.T) { Steps: []resource.TestStep{ { // create cluster with shard count 3 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, preventDestroy: true, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 3, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -144,7 +141,7 @@ func TestAccRedisCluster_updateShardCount(t *testing.T) { }, { // update shard count to 5 - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 5, preventDestroy: true, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 5, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), }, { ResourceName: "google_redis_cluster.test", @@ -154,7 +151,7 @@ func TestAccRedisCluster_updateShardCount(t *testing.T) { }, { // clean up the resource - Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 5, preventDestroy: false, zoneDistributionMode: "MULTI_ZONE"}), + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 1, shardCount: 5, deletionProtectionEnabled: false, zoneDistributionMode: "MULTI_ZONE"}), }, }, }) @@ -212,25 +209,57 @@ func TestAccRedisCluster_updateRedisConfigs(t *testing.T) { }) } +// Validate that deletion protection enabled/disabled cluster is created updated +func TestAccRedisCluster_createUpdateDeletionProtection(t *testing.T) { + t.Parallel() + + name := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckRedisClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + // create cluster with deletion protection set to false + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: false, zoneDistributionMode: "MULTI_ZONE"}), + }, + { + ResourceName: "google_redis_cluster.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"psc_configs"}, + }, + { + // update deletion protection to true + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: true, zoneDistributionMode: "MULTI_ZONE"}), + }, + { + ResourceName: "google_redis_cluster.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"psc_configs"}, + }, + { + // update deletion protection to false and delete the cluster + Config: createOrUpdateRedisCluster(&ClusterParams{name: name, replicaCount: 0, shardCount: 3, deletionProtectionEnabled: false, zoneDistributionMode: "MULTI_ZONE"}), + }, + }, + }) +} + type ClusterParams struct { - name string - replicaCount int - shardCount int - preventDestroy bool - nodeType string - redisConfigs map[string]string - zoneDistributionMode string - zone string + name string + replicaCount int + shardCount int + deletionProtectionEnabled bool + nodeType string + redisConfigs map[string]string + zoneDistributionMode string + zone string } func createOrUpdateRedisCluster(params *ClusterParams) string { - lifecycleBlock := "" - if params.preventDestroy { - lifecycleBlock = ` - lifecycle { - prevent_destroy = true - }` - } var strBuilder strings.Builder for key, value := range params.redisConfigs { strBuilder.WriteString(fmt.Sprintf("%s = \"%s\"\n", key, value)) @@ -253,6 +282,7 @@ resource "google_redis_cluster" "test" { replica_count = %d shard_count = %d node_type = "%s" + deletion_protection_enabled = %v region = "us-central1" psc_configs { network = google_compute_network.producer_net.id @@ -262,9 +292,8 @@ resource "google_redis_cluster" "test" { } %s depends_on = [ - google_network_connectivity_service_connection_policy.default - ] - %s + google_network_connectivity_service_connection_policy.default + ] } resource "google_network_connectivity_service_connection_policy" "default" { @@ -292,5 +321,5 @@ resource "google_compute_network" "producer_net" { name = "%s" auto_create_subnetworks = false } -`, params.name, params.replicaCount, params.shardCount, params.nodeType, strBuilder.String(), zoneDistributionConfigBlock, lifecycleBlock, params.name, params.name, params.name) +`, params.name, params.replicaCount, params.shardCount, params.nodeType, params.deletionProtectionEnabled, strBuilder.String(), zoneDistributionConfigBlock, params.name, params.name, params.name) } diff --git a/website/docs/r/redis_cluster.html.markdown b/website/docs/r/redis_cluster.html.markdown index 16659ff534..34567a89b6 100644 --- a/website/docs/r/redis_cluster.html.markdown +++ b/website/docs/r/redis_cluster.html.markdown @@ -51,16 +51,14 @@ resource "google_redis_cluster" "cluster-ha" { redis_configs = { maxmemory-policy = "volatile-ttl" } + deletion_protection_enabled = true + zone_distribution_config { mode = "MULTI_ZONE" } depends_on = [ google_network_connectivity_service_connection_policy.default ] - - lifecycle { - prevent_destroy = true - } } resource "google_network_connectivity_service_connection_policy" "default" { @@ -106,13 +104,11 @@ resource "google_redis_cluster" "cluster-ha-single-zone" { mode = "SINGLE_ZONE" zone = "us-central1-f" } + deletion_protection_enabled = true depends_on = [ google_network_connectivity_service_connection_policy.default ] - lifecycle { - prevent_destroy = true - } } resource "google_network_connectivity_service_connection_policy" "default" { @@ -200,6 +196,12 @@ The following arguments are supported: (Optional) Optional. The number of replica nodes per shard. +* `deletion_protection_enabled` - + (Optional) + Optional. Indicates if the cluster is deletion protected or not. + If the value if set to true, any delete cluster operation will fail. + Default value is true. + * `redis_configs` - (Optional) Configure Redis Cluster behavior using a subset of native Redis configuration parameters.