From af1fdb93805e33db8b70f645138bf8437212ab22 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Mon, 6 Sep 2021 18:32:25 -0600 Subject: [PATCH 01/29] Changelog --- .changelog/19098.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/19098.txt diff --git a/.changelog/19098.txt b/.changelog/19098.txt new file mode 100644 index 00000000000..e729b1a0ca7 --- /dev/null +++ b/.changelog/19098.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +``` \ No newline at end of file From 94fea9d178eaffd0ed4640d62e2753508dcde8a0 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Mon, 6 Sep 2021 18:32:56 -0600 Subject: [PATCH 02/29] Add AZ Relocation --- aws/resource_aws_redshift_cluster.go | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/aws/resource_aws_redshift_cluster.go b/aws/resource_aws_redshift_cluster.go index c219ee4c7fa..2fea2782fac 100644 --- a/aws/resource_aws_redshift_cluster.go +++ b/aws/resource_aws_redshift_cluster.go @@ -59,6 +59,15 @@ func resourceAwsRedshiftCluster() *schema.Resource { ForceNew: true, Computed: true, }, + "availability_zone_relocation": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "availability_zone_relocation_status": { + Type: schema.TypeString, + Computed: true, + }, "cluster_identifier": { Type: schema.TypeString, Required: true, @@ -354,6 +363,10 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) restoreOpts.AvailabilityZone = aws.String(v.(string)) } + if v, ok := d.GetOk("availability_zone_relocation"); ok { + restoreOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("cluster_subnet_group_name"); ok { restoreOpts.ClusterSubnetGroupName = aws.String(v.(string)) } @@ -446,6 +459,10 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) createOpts.AvailabilityZone = aws.String(v.(string)) } + if v, ok := d.GetOk("availability_zone_relocation"); ok { + createOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("preferred_maintenance_window"); ok { createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) } @@ -550,6 +567,8 @@ func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) er d.Set("arn", arn) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) + d.Set("availability_zone_relocation", getAvailabilityZoneRelocationValue(rsc, d)) + d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) d.Set("cluster_identifier", rsc.ClusterIdentifier) if err := d.Set("cluster_nodes", flattenRedshiftClusterNodes(rsc.ClusterNodes)); err != nil { return fmt.Errorf("error setting cluster_nodes: %w", err) @@ -661,6 +680,11 @@ func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) requestUpdate = true } + if d.HasChange("availability_zone_relocation") { + req.AvailabilityZoneRelocation = aws.Bool(d.Get("availability_zone_relocation").(bool)) + requestUpdate = true + } + if d.HasChange("cluster_security_groups") { req.ClusterSecurityGroups = expandStringSet(d.Get("cluster_security_groups").(*schema.Set)) requestUpdate = true @@ -974,3 +998,27 @@ func flattenRedshiftClusterNodes(apiObjects []*redshift.ClusterNode) []interface return tfList } + +func getAvailabilityZoneRelocationValue(rsc *redshift.Cluster, d *schema.ResourceData) bool { + // AvailabilityZoneRelocationStatus is an official API, but not documented. + // based on interactions with the API, these are the possible values that can be returned + // we infer the AvailabilityZoneRelocation from here + azr := *rsc.AvailabilityZoneRelocationStatus + statuses := map[string]bool{ + "enabled": true, + "pending_enabling": true, + "disabled": false, + "pending_disabled": false, + } + if v, ok := statuses[azr]; ok { + return v + } + // if the API returns an unknown value, we try to use the state if available to avoid a possible dud plan + // otherwise, we default to false + if v, ok := d.GetOkExists("availability_zone_relocation"); ok { + log.Printf("[WARN] Redshift Cluster (%s) Unexpected AvailabilityZoneRelocationStatus from API: %s, returning value from state: %t.", d.Id(), azr, v) + return v.(bool) + } + log.Printf("[WARN] Redshift Cluster (%s) Unexpected AvailabilityZoneRelocationStatus from API: %s, returning default value: false.", d.Id(), azr) + return false +} From 9aa59c2a864902326e09d7f54c18846205553796 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Mon, 6 Sep 2021 18:33:03 -0600 Subject: [PATCH 03/29] Add AZ Relocation Tests --- aws/resource_aws_redshift_cluster_test.go | 80 +++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/aws/resource_aws_redshift_cluster_test.go b/aws/resource_aws_redshift_cluster_test.go index 658d151ca25..6f5fba63b5b 100644 --- a/aws/resource_aws_redshift_cluster_test.go +++ b/aws/resource_aws_redshift_cluster_test.go @@ -605,6 +605,46 @@ func TestAccAWSRedshiftCluster_changeEncryption2(t *testing.T) { }) } +func TestAccAWSRedshiftCluster_availabilityZoneRelocation(t *testing.T) { + var v redshift.Cluster + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftClusterConfig_availabilityZoneRelocationEnabled(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_redshift_cluster.default", "availability_zone_relocation", "true"), + ), + }, + { + ResourceName: "aws_redshift_cluster.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "final_snapshot_identifier", + "master_password", + "skip_final_snapshot", + }, + }, + { + Config: testAccAWSRedshiftClusterConfig_availabilityZoneRelocationDisabled(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_redshift_cluster.default", "availability_zone_relocation", "false"), + ), + }, + }, + }) +} + func testAccCheckAWSRedshiftClusterDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).redshiftconn @@ -1413,3 +1453,43 @@ resource "aws_redshift_cluster" "default" { } `, rInt)) } + +func testAccAWSRedshiftClusterConfig_availabilityZoneRelocationDisabled(rInt int) string { + return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` +resource "aws_redshift_cluster" "default" { + cluster_identifier = "tf-redshift-cluster-%[1]d" + availability_zone = data.aws_availability_zones.available.names[0] + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "ra3.xlplus" + number_of_nodes = 2 + cluster_type = "multi-node" + availability_zone_relocation = false + publicly_accessible = false + automated_snapshot_retention_period = 1 + allow_version_upgrade = false + skip_final_snapshot = true +} +`, rInt)) +} + +func testAccAWSRedshiftClusterConfig_availabilityZoneRelocationEnabled(rInt int) string { + return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` +resource "aws_redshift_cluster" "default" { + cluster_identifier = "tf-redshift-cluster-%[1]d" + availability_zone = data.aws_availability_zones.available.names[0] + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "ra3.xlplus" + number_of_nodes = 2 + cluster_type = "multi-node" + availability_zone_relocation = true + publicly_accessible = false + automated_snapshot_retention_period = 1 + allow_version_upgrade = false + skip_final_snapshot = true +} +`, rInt)) +} From 0cce54ff4a07cac0c677ed2f5d9f2e83e22fe21f Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Mon, 6 Sep 2021 18:33:14 -0600 Subject: [PATCH 04/29] Add AZ Relocation Docs --- website/docs/r/redshift_cluster.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/r/redshift_cluster.html.markdown b/website/docs/r/redshift_cluster.html.markdown index 40c5ea7ab5a..893503f3309 100644 --- a/website/docs/r/redshift_cluster.html.markdown +++ b/website/docs/r/redshift_cluster.html.markdown @@ -48,6 +48,7 @@ string. * `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster. * `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC). * `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency. +* `availability_zone_relocation` - (Optional) Enable relocation for an Amazon Redshift cluster between Availability Zones. Default is false. Available for use on clusters from the RA3 instance family. * `preferred_maintenance_window` - (Optional) The weekly time range (in UTC) during which automated cluster maintenance can occur. Format: ddd:hh24:mi-ddd:hh24:mi * `cluster_parameter_group_name` - (Optional) The name of the parameter group to be associated with this cluster. @@ -108,6 +109,7 @@ In addition to all arguments above, the following attributes are exported: * `node_type` - The type of nodes in the cluster * `database_name` - The name of the default database in the Cluster * `availability_zone` - The availability zone of the Cluster +* `availability_zone_relocation_status` - The status of the Availability Zone relocation operation (enabled, disabled, pending_enabling, pending_disabling) * `automated_snapshot_retention_period` - The backup retention period * `preferred_maintenance_window` - The backup window * `endpoint` - The connection endpoint From 3bdb1bda680fc180c14c925fd9f61cf511af6707 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sat, 18 Sep 2021 19:28:55 -0600 Subject: [PATCH 05/29] reformat TF test --- aws/resource_aws_redshift_cluster_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_redshift_cluster_test.go b/aws/resource_aws_redshift_cluster_test.go index 6f5fba63b5b..cc3b481c27a 100644 --- a/aws/resource_aws_redshift_cluster_test.go +++ b/aws/resource_aws_redshift_cluster_test.go @@ -1469,7 +1469,7 @@ resource "aws_redshift_cluster" "default" { publicly_accessible = false automated_snapshot_retention_period = 1 allow_version_upgrade = false - skip_final_snapshot = true + skip_final_snapshot = true } `, rInt)) } @@ -1489,7 +1489,7 @@ resource "aws_redshift_cluster" "default" { publicly_accessible = false automated_snapshot_retention_period = 1 allow_version_upgrade = false - skip_final_snapshot = true + skip_final_snapshot = true } `, rInt)) } From bcb7a8095171690135c90cf4ebe897fd68d2a056 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sat, 18 Sep 2021 19:46:43 -0600 Subject: [PATCH 06/29] Updating implementation --- aws/resource_aws_redshift_cluster.go | 34 ++++++++-------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/aws/resource_aws_redshift_cluster.go b/aws/resource_aws_redshift_cluster.go index 2fea2782fac..a5512375ff3 100644 --- a/aws/resource_aws_redshift_cluster.go +++ b/aws/resource_aws_redshift_cluster.go @@ -567,8 +567,16 @@ func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) er d.Set("arn", arn) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) - d.Set("availability_zone_relocation", getAvailabilityZoneRelocationValue(rsc, d)) d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) + // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. + switch availabilityZoneRelocationStatus := *rsc.AvailabilityZoneRelocationStatus; availabilityZoneRelocationStatus { + case "enabled", "pending_enabling": + d.Set("availability_zone_relocation", true) + case "disabled", "pending_disabling": + d.Set("availability_zone_relocation", false) + default: + return fmt.Errorf("error reading cluster %s, unexpected AvailabilityZoneRelocationStatus attribute: %s", d.Id(), availabilityZoneRelocationStatus) + } d.Set("cluster_identifier", rsc.ClusterIdentifier) if err := d.Set("cluster_nodes", flattenRedshiftClusterNodes(rsc.ClusterNodes)); err != nil { return fmt.Errorf("error setting cluster_nodes: %w", err) @@ -998,27 +1006,3 @@ func flattenRedshiftClusterNodes(apiObjects []*redshift.ClusterNode) []interface return tfList } - -func getAvailabilityZoneRelocationValue(rsc *redshift.Cluster, d *schema.ResourceData) bool { - // AvailabilityZoneRelocationStatus is an official API, but not documented. - // based on interactions with the API, these are the possible values that can be returned - // we infer the AvailabilityZoneRelocation from here - azr := *rsc.AvailabilityZoneRelocationStatus - statuses := map[string]bool{ - "enabled": true, - "pending_enabling": true, - "disabled": false, - "pending_disabled": false, - } - if v, ok := statuses[azr]; ok { - return v - } - // if the API returns an unknown value, we try to use the state if available to avoid a possible dud plan - // otherwise, we default to false - if v, ok := d.GetOkExists("availability_zone_relocation"); ok { - log.Printf("[WARN] Redshift Cluster (%s) Unexpected AvailabilityZoneRelocationStatus from API: %s, returning value from state: %t.", d.Id(), azr, v) - return v.(bool) - } - log.Printf("[WARN] Redshift Cluster (%s) Unexpected AvailabilityZoneRelocationStatus from API: %s, returning default value: false.", d.Id(), azr) - return false -} From ee2fd75a2433a76cd32826bec6c69f064be81bcf Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:50:56 -0700 Subject: [PATCH 07/29] #19098 cluster: add schema --- internal/service/redshift/cluster.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index ad02ff73914..92f87bbdec1 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -59,6 +59,15 @@ func ResourceCluster() *schema.Resource { ForceNew: true, Computed: true, }, + "availability_zone_relocation": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "availability_zone_relocation_status": { + Type: schema.TypeString, + Computed: true, + }, "cluster_identifier": { Type: schema.TypeString, Required: true, From 005ae12f26b99199e9ddac5cec78ae6498ef28cd Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:51:16 -0700 Subject: [PATCH 08/29] #19098 cluster_data_source: add computed schema --- internal/service/redshift/cluster_data_source.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/service/redshift/cluster_data_source.go b/internal/service/redshift/cluster_data_source.go index d61589d277e..61063145405 100644 --- a/internal/service/redshift/cluster_data_source.go +++ b/internal/service/redshift/cluster_data_source.go @@ -37,6 +37,16 @@ func DataSourceCluster() *schema.Resource { Computed: true, }, + "availability_zone_relocation": { + Type: schema.TypeBool, + Computed: true, + }, + + "availability_zone_relocation_status": { + Type: schema.TypeString, + Computed: true, + }, + "bucket_name": { Type: schema.TypeString, Computed: true, From 22e5662a77eefdd24c25d79501dbf31e695d4d31 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:51:40 -0700 Subject: [PATCH 09/29] #19098 cluster: add zone relocation status calculator --- internal/service/redshift/status.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/service/redshift/status.go b/internal/service/redshift/status.go index 9709bad6982..39ac52f0aae 100644 --- a/internal/service/redshift/status.go +++ b/internal/service/redshift/status.go @@ -1,6 +1,8 @@ package redshift import ( + "errors" + "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/redshift" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -22,3 +24,15 @@ func statusCluster(conn *redshift.Redshift, id string) resource.StateRefreshFunc return output, aws.StringValue(output.ClusterStatus), nil } } + +func availabilityZoneRelocationStatus(cluster *redshift.Cluster) (bool, error) { + // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. + switch availabilityZoneRelocationStatus := *cluster.AvailabilityZoneRelocationStatus; availabilityZoneRelocationStatus { + case "enabled", "pending_enabling": + return true, nil + case "disabled", "pending_disabling": + return false, nil + default: + return false, errors.New(fmt.Sprintf("unexpected AvailabilityZoneRelocationStatus attribute value: %s", availabilityZoneRelocationStatus)) + } +} From 2b9abd425b9528745b63b858a52944e75bb674df Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:51:58 -0700 Subject: [PATCH 10/29] #19098 cluster: add restore option --- internal/service/redshift/cluster.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 92f87bbdec1..4cb4ed9b0fb 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -363,6 +363,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { restoreOpts.AvailabilityZone = aws.String(v.(string)) } + if v, ok := d.GetOk("availability_zone_relocation"); ok { + restoreOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("cluster_subnet_group_name"); ok { restoreOpts.ClusterSubnetGroupName = aws.String(v.(string)) } From 55f8512880968848a607039fb3640178b8654adf Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:52:06 -0700 Subject: [PATCH 11/29] #19098 cluster: add create option --- internal/service/redshift/cluster.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 4cb4ed9b0fb..435cb2a5ce6 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -459,6 +459,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { createOpts.AvailabilityZone = aws.String(v.(string)) } + if v, ok := d.GetOk("availability_zone_relocation"); ok { + createOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("preferred_maintenance_window"); ok { createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) } From bfcb64222e70c57d424a941d50de34be21ec1b96 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:52:15 -0700 Subject: [PATCH 12/29] #19098 cluster: add read option --- internal/service/redshift/cluster.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 435cb2a5ce6..c50e61d9a58 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -567,6 +567,12 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", arn) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) + d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) + azr, err := availabilityZoneRelocationStatus(rsc) + if err != nil { + return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) + } + d.Set("availability_zone_relocation", azr) d.Set("cluster_identifier", rsc.ClusterIdentifier) if err := d.Set("cluster_nodes", flattenRedshiftClusterNodes(rsc.ClusterNodes)); err != nil { return fmt.Errorf("error setting cluster_nodes: %w", err) From ead80447e102a53500b4f74a4ea4f4054ee01c28 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:52:23 -0700 Subject: [PATCH 13/29] #19098 cluster: add update option --- internal/service/redshift/cluster.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index c50e61d9a58..b50293ce111 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -684,6 +684,11 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { requestUpdate = true } + if d.HasChange("availability_zone_relocation") { + req.AvailabilityZoneRelocation = aws.Bool(d.Get("availability_zone_relocation").(bool)) + requestUpdate = true + } + if d.HasChange("cluster_security_groups") { req.ClusterSecurityGroups = flex.ExpandStringSet(d.Get("cluster_security_groups").(*schema.Set)) requestUpdate = true From b808677f4bcfdb73b4623d272f00c7816a328f2d Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:52:37 -0700 Subject: [PATCH 14/29] #19098 cluster_data_source: add read option --- internal/service/redshift/cluster_data_source.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/service/redshift/cluster_data_source.go b/internal/service/redshift/cluster_data_source.go index 61063145405..6ad252fb357 100644 --- a/internal/service/redshift/cluster_data_source.go +++ b/internal/service/redshift/cluster_data_source.go @@ -206,6 +206,12 @@ func dataSourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) + d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) + azr, err := availabilityZoneRelocationStatus(rsc) + if err != nil { + return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) + } + d.Set("availability_zone_relocation", azr) d.Set("cluster_identifier", rsc.ClusterIdentifier) if len(rsc.ClusterParameterGroups) > 0 { From 91a6f0149854848ab6c843d8ac93a986f2b585e6 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:53:11 -0700 Subject: [PATCH 15/29] #19098 cluster_data_source_test: check for availability_zone_relocation attribute --- internal/service/redshift/cluster_data_source_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/redshift/cluster_data_source_test.go b/internal/service/redshift/cluster_data_source_test.go index abcf1174b02..67bc305d0fb 100644 --- a/internal/service/redshift/cluster_data_source_test.go +++ b/internal/service/redshift/cluster_data_source_test.go @@ -40,6 +40,7 @@ func TestAccRedshiftClusterDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrSet(dataSourceName, "port"), resource.TestCheckResourceAttrSet(dataSourceName, "preferred_maintenance_window"), resource.TestCheckResourceAttrSet(dataSourceName, "publicly_accessible"), + resource.TestCheckResourceAttrSet(dataSourceName, "availability_zone_relocation"), ), }, }, From ca69df9787eb512c3587f7a6dfed3ae4c12ad97b Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:53:24 -0700 Subject: [PATCH 16/29] #19098 cluster_data_source_test: website docs --- website/docs/d/redshift_cluster.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/d/redshift_cluster.html.markdown b/website/docs/d/redshift_cluster.html.markdown index f4be77cf712..b1544735c6c 100644 --- a/website/docs/d/redshift_cluster.html.markdown +++ b/website/docs/d/redshift_cluster.html.markdown @@ -54,6 +54,8 @@ In addition to all arguments above, the following attributes are exported: * `allow_version_upgrade` - Whether major version upgrades can be applied during maintenance period * `automated_snapshot_retention_period` - The backup retention period * `availability_zone` - The availability zone of the cluster +* `availability_zone_relocation` - Relocation for an Amazon Redshift cluster between Availability Zones. +* `availability_zone_relocation_status` - The status of the Availability Zone relocation operation (enabled, disabled, pending_enabling, pending_disabling) * `bucket_name` - The name of the S3 bucket where the log files are to be stored * `cluster_identifier` - The cluster identifier * `cluster_parameter_group_name` - The name of the parameter group to be associated with this cluster From ddef77088cad151cb46a30699cd38a76d6870f1c Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:53:42 -0700 Subject: [PATCH 17/29] #19098 cluster: test: enable/disable model --- internal/service/redshift/cluster_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index bac9463a141..80ad8d54e2a 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -1278,3 +1278,23 @@ resource "aws_redshift_cluster" "test" { } `, rName)) } + +func testAccClusterConfig_availabilityZoneRelocation(rInt int, enabled bool) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(` +resource "aws_redshift_cluster" "default" { + cluster_identifier = "tf-redshift-cluster-%[1]d" + availability_zone = data.aws_availability_zones.available.names[0] + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "ra3.xlplus" + number_of_nodes = 2 + cluster_type = "multi-node" + availability_zone_relocation = tobool("%[2]t") + publicly_accessible = false + automated_snapshot_retention_period = 1 + allow_version_upgrade = false + skip_final_snapshot = true +} +`, rInt, enabled)) +} From 0b7d66f6e5d15e626d4814b5eb36d297b2b035b5 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:53:50 -0700 Subject: [PATCH 18/29] #19098 cluster: test: enable/disable test --- internal/service/redshift/cluster_test.go | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index 80ad8d54e2a..1cfbf36dd9e 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -546,6 +546,46 @@ func TestAccRedshiftCluster_changeEncryption2(t *testing.T) { }) } +func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { + var v redshift.Cluster + rInt := sdkacctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_availabilityZoneRelocation(rInt, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists("aws_redshift_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_redshift_cluster.default", "availability_zone_relocation", "true"), + ), + }, + { + ResourceName: "aws_redshift_cluster.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "final_snapshot_identifier", + "master_password", + "skip_final_snapshot", + }, + }, + { + Config: testAccClusterConfig_availabilityZoneRelocation(rInt, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists("aws_redshift_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_redshift_cluster.default", "availability_zone_relocation", "false"), + ), + }, + }, + }) +} + func testAccCheckClusterDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn From e39835f8732a8a2d23a735733a209547b90c3741 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 17:54:00 -0700 Subject: [PATCH 19/29] #19098 cluster: changelog --- .changelog/19098.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changelog/19098.txt b/.changelog/19098.txt index e729b1a0ca7..478770c4240 100644 --- a/.changelog/19098.txt +++ b/.changelog/19098.txt @@ -1,3 +1,4 @@ ```release-note:enhancement -resource/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +internal/service/redshift/cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +internal/service/redshift/cluster_data_source: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. ``` \ No newline at end of file From 1783e743f219dc380f0be3157bad218f945199f5 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 18:01:12 -0700 Subject: [PATCH 20/29] #19098 cluster: remove broken merge files --- aws/resource_aws_redshift_cluster.go | 1008 -------------- aws/resource_aws_redshift_cluster_test.go | 1495 --------------------- 2 files changed, 2503 deletions(-) delete mode 100644 aws/resource_aws_redshift_cluster.go delete mode 100644 aws/resource_aws_redshift_cluster_test.go diff --git a/aws/resource_aws_redshift_cluster.go b/aws/resource_aws_redshift_cluster.go deleted file mode 100644 index a5512375ff3..00000000000 --- a/aws/resource_aws_redshift_cluster.go +++ /dev/null @@ -1,1008 +0,0 @@ -package aws - -import ( - "fmt" - "log" - "regexp" - "strings" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/redshift" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - tfredshift "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift/finder" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift/waiter" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" -) - -func resourceAwsRedshiftCluster() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsRedshiftClusterCreate, - Read: resourceAwsRedshiftClusterRead, - Update: resourceAwsRedshiftClusterUpdate, - Delete: resourceAwsRedshiftClusterDelete, - Importer: &schema.ResourceImporter{ - State: resourceAwsRedshiftClusterImport, - }, - - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(75 * time.Minute), - Update: schema.DefaultTimeout(75 * time.Minute), - Delete: schema.DefaultTimeout(40 * time.Minute), - }, - - Schema: map[string]*schema.Schema{ - "allow_version_upgrade": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "automated_snapshot_retention_period": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - ValidateFunc: validation.IntAtMost(35), - }, - "availability_zone": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - }, - "availability_zone_relocation": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "availability_zone_relocation_status": { - Type: schema.TypeString, - Computed: true, - }, - "cluster_identifier": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringMatch(regexp.MustCompile(`^[0-9a-z-]+$`), "must contain only lowercase alphanumeric characters and hyphens"), - validation.StringMatch(regexp.MustCompile(`(?i)^[a-z]`), "first character must be a letter"), - validation.StringDoesNotMatch(regexp.MustCompile(`--`), "cannot contain two consecutive hyphens"), - validation.StringDoesNotMatch(regexp.MustCompile(`-$`), "cannot end with a hyphen"), - ), - }, - "cluster_nodes": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "node_role": { - Type: schema.TypeString, - Computed: true, - }, - "private_ip_address": { - Type: schema.TypeString, - Computed: true, - }, - "public_ip_address": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "cluster_parameter_group_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cluster_public_key": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cluster_revision_number": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cluster_security_groups": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - "cluster_subnet_group_name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - }, - "cluster_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cluster_version": { - Type: schema.TypeString, - Optional: true, - Default: "1.0", - }, - "database_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 64), - validation.StringMatch(regexp.MustCompile(`^[0-9a-z_$]+$`), "must contain only lowercase alphanumeric characters, underscores, and dollar signs"), - validation.StringMatch(regexp.MustCompile(`(?i)^[a-z_]`), "first character must be a letter or underscore"), - ), - }, - "dns_name": { - Type: schema.TypeString, - Computed: true, - }, - "elastic_ip": { - Type: schema.TypeString, - Optional: true, - }, - "encrypted": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "endpoint": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "enhanced_vpc_routing": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "final_snapshot_identifier": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 255), - validation.StringMatch(regexp.MustCompile(`^[0-9A-Za-z-]+$`), "must only contain alphanumeric characters and hyphens"), - validation.StringDoesNotMatch(regexp.MustCompile(`--`), "cannot contain two consecutive hyphens"), - validation.StringDoesNotMatch(regexp.MustCompile(`-$`), "cannot end in a hyphen"), - ), - }, - "iam_roles": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - "kms_key_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validateArn, - }, - "logging": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - DiffSuppressFunc: suppressMissingOptionalConfigurationBlock, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bucket_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "enable": { - Type: schema.TypeBool, - Required: true, - }, - "s3_key_prefix": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - "master_password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - ValidateFunc: validation.All( - validation.StringLenBetween(8, 64), - validation.StringMatch(regexp.MustCompile(`^.*[a-z].*`), "must contain at least one lowercase letter"), - validation.StringMatch(regexp.MustCompile(`^.*[A-Z].*`), "must contain at least one uppercase letter"), - validation.StringMatch(regexp.MustCompile(`^.*[0-9].*`), "must contain at least one number"), - validation.StringMatch(regexp.MustCompile(`^[^\@\/'" ]*$`), "cannot contain [/@\"' ]"), - ), - }, - "master_username": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 128), - validation.StringMatch(regexp.MustCompile(`^\w+$`), "must contain only alphanumeric characters"), - validation.StringMatch(regexp.MustCompile(`(?i)^[a-z_]`), "first character must be a letter"), - ), - }, - "node_type": { - Type: schema.TypeString, - Required: true, - }, - "number_of_nodes": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - }, - "owner_account": { - Type: schema.TypeString, - Optional: true, - }, - "port": { - Type: schema.TypeInt, - Optional: true, - Default: 5439, - }, - "preferred_maintenance_window": { - Type: schema.TypeString, - Optional: true, - Computed: true, - StateFunc: func(val interface{}) string { - if val == nil { - return "" - } - return strings.ToLower(val.(string)) - }, - ValidateFunc: validateOnceAWeekWindowFormat, - }, - "publicly_accessible": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "skip_final_snapshot": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "snapshot_cluster_identifier": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "snapshot_copy": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "destination_region": { - Type: schema.TypeString, - Required: true, - }, - "grant_name": { - Type: schema.TypeString, - Optional: true, - }, - "retention_period": { - Type: schema.TypeInt, - Optional: true, - Default: 7, - }, - }, - }, - }, - "snapshot_identifier": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "vpc_security_group_ids": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - "tags": tagsSchema(), - "tags_all": tagsSchemaComputed(), - }, - - CustomizeDiff: SetTagsDiff, - } -} - -func resourceAwsRedshiftClusterImport( - d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - // Neither skip_final_snapshot nor final_snapshot_identifier can be fetched - // from any API call, so we need to default skip_final_snapshot to true so - // that final_snapshot_identifier is not required - d.Set("skip_final_snapshot", true) - return []*schema.ResourceData{d}, nil -} - -func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).redshiftconn - defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) - - if v, ok := d.GetOk("snapshot_identifier"); ok { - restoreOpts := &redshift.RestoreFromClusterSnapshotInput{ - ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), - SnapshotIdentifier: aws.String(v.(string)), - Port: aws.Int64(int64(d.Get("port").(int))), - AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), - NodeType: aws.String(d.Get("node_type").(string)), - PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), - AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), - } - - if v, ok := d.GetOk("owner_account"); ok { - restoreOpts.OwnerAccount = aws.String(v.(string)) - } - - if v, ok := d.GetOk("snapshot_cluster_identifier"); ok { - restoreOpts.SnapshotClusterIdentifier = aws.String(v.(string)) - } - - if v, ok := d.GetOk("availability_zone"); ok { - restoreOpts.AvailabilityZone = aws.String(v.(string)) - } - - if v, ok := d.GetOk("availability_zone_relocation"); ok { - restoreOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("cluster_subnet_group_name"); ok { - restoreOpts.ClusterSubnetGroupName = aws.String(v.(string)) - } - - if v, ok := d.GetOk("cluster_parameter_group_name"); ok { - restoreOpts.ClusterParameterGroupName = aws.String(v.(string)) - } - - if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { - restoreOpts.ClusterSecurityGroups = expandStringSet(v) - } - - if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { - restoreOpts.VpcSecurityGroupIds = expandStringSet(v) - } - - if v, ok := d.GetOk("preferred_maintenance_window"); ok { - restoreOpts.PreferredMaintenanceWindow = aws.String(v.(string)) - } - - if v, ok := d.GetOk("kms_key_id"); ok { - restoreOpts.KmsKeyId = aws.String(v.(string)) - } - - if v, ok := d.GetOk("elastic_ip"); ok { - restoreOpts.ElasticIp = aws.String(v.(string)) - } - - if v, ok := d.GetOk("enhanced_vpc_routing"); ok { - restoreOpts.EnhancedVpcRouting = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("iam_roles"); ok { - restoreOpts.IamRoles = expandStringSet(v.(*schema.Set)) - } - - log.Printf("[DEBUG] Redshift Cluster restore cluster options: %s", restoreOpts) - - resp, err := conn.RestoreFromClusterSnapshot(restoreOpts) - if err != nil { - log.Printf("[ERROR] Error Restoring Redshift Cluster from Snapshot: %s", err) - return err - } - - d.SetId(aws.StringValue(resp.Cluster.ClusterIdentifier)) - - } else { - if _, ok := d.GetOk("master_password"); !ok { - return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_password": required field is not set`, d.Get("cluster_identifier").(string)) - } - - if _, ok := d.GetOk("master_username"); !ok { - return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_username": required field is not set`, d.Get("cluster_identifier").(string)) - } - - createOpts := &redshift.CreateClusterInput{ - ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), - Port: aws.Int64(int64(d.Get("port").(int))), - MasterUserPassword: aws.String(d.Get("master_password").(string)), - MasterUsername: aws.String(d.Get("master_username").(string)), - ClusterVersion: aws.String(d.Get("cluster_version").(string)), - NodeType: aws.String(d.Get("node_type").(string)), - DBName: aws.String(d.Get("database_name").(string)), - AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), - PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), - AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), - Tags: tags.IgnoreAws().RedshiftTags(), - } - - if v := d.Get("number_of_nodes").(int); v > 1 { - createOpts.ClusterType = aws.String("multi-node") - createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) - } else { - createOpts.ClusterType = aws.String("single-node") - } - - if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { - createOpts.ClusterSecurityGroups = expandStringSet(v) - } - - if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { - createOpts.VpcSecurityGroupIds = expandStringSet(v) - } - - if v, ok := d.GetOk("cluster_subnet_group_name"); ok { - createOpts.ClusterSubnetGroupName = aws.String(v.(string)) - } - - if v, ok := d.GetOk("availability_zone"); ok { - createOpts.AvailabilityZone = aws.String(v.(string)) - } - - if v, ok := d.GetOk("availability_zone_relocation"); ok { - createOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("preferred_maintenance_window"); ok { - createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) - } - - if v, ok := d.GetOk("cluster_parameter_group_name"); ok { - createOpts.ClusterParameterGroupName = aws.String(v.(string)) - } - - if v, ok := d.GetOk("encrypted"); ok { - createOpts.Encrypted = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("enhanced_vpc_routing"); ok { - createOpts.EnhancedVpcRouting = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("kms_key_id"); ok { - createOpts.KmsKeyId = aws.String(v.(string)) - } - - if v, ok := d.GetOk("elastic_ip"); ok { - createOpts.ElasticIp = aws.String(v.(string)) - } - - if v, ok := d.GetOk("iam_roles"); ok { - createOpts.IamRoles = expandStringSet(v.(*schema.Set)) - } - - log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) - resp, err := conn.CreateCluster(createOpts) - if err != nil { - log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) - return err - } - - log.Printf("[DEBUG]: Cluster create response: %s", resp) - d.SetId(aws.StringValue(resp.Cluster.ClusterIdentifier)) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{"creating", "backing-up", "modifying", "restoring", "available, prep-for-resize"}, - Target: []string{"available"}, - Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d.Id(), conn), - Timeout: d.Timeout(schema.TimeoutCreate), - MinTimeout: 10 * time.Second, - } - - _, err := stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for Redshift Cluster state to be \"available\": %s", err) - } - - if v, ok := d.GetOk("snapshot_copy"); ok { - err := enableRedshiftSnapshotCopy(d.Id(), v.([]interface{}), conn) - if err != nil { - return err - } - } - - if _, ok := d.GetOk("logging.0.enable"); ok { - if err := enableRedshiftClusterLogging(d, conn); err != nil { - return fmt.Errorf("error enabling Redshift Cluster (%s) logging: %s", d.Id(), err) - } - } - - return resourceAwsRedshiftClusterRead(d, meta) -} - -func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).redshiftconn - defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - - rsc, err := finder.ClusterByID(conn, d.Id()) - - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Redshift Cluster (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) - } - - loggingStatus, err := conn.DescribeLoggingStatus(&redshift.DescribeLoggingStatusInput{ - ClusterIdentifier: aws.String(d.Id()), - }) - - if err != nil { - return fmt.Errorf("error reading Redshift Cluster (%s) logging status: %w", d.Id(), err) - } - - d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) - arn := arn.ARN{ - Partition: meta.(*AWSClient).partition, - Service: "redshift", - Region: meta.(*AWSClient).region, - AccountID: meta.(*AWSClient).accountid, - Resource: fmt.Sprintf("cluster:%s", d.Id()), - }.String() - d.Set("arn", arn) - d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) - d.Set("availability_zone", rsc.AvailabilityZone) - d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) - // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. - switch availabilityZoneRelocationStatus := *rsc.AvailabilityZoneRelocationStatus; availabilityZoneRelocationStatus { - case "enabled", "pending_enabling": - d.Set("availability_zone_relocation", true) - case "disabled", "pending_disabling": - d.Set("availability_zone_relocation", false) - default: - return fmt.Errorf("error reading cluster %s, unexpected AvailabilityZoneRelocationStatus attribute: %s", d.Id(), availabilityZoneRelocationStatus) - } - d.Set("cluster_identifier", rsc.ClusterIdentifier) - if err := d.Set("cluster_nodes", flattenRedshiftClusterNodes(rsc.ClusterNodes)); err != nil { - return fmt.Errorf("error setting cluster_nodes: %w", err) - } - d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName) - d.Set("cluster_public_key", rsc.ClusterPublicKey) - d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) - d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName) - if len(rsc.ClusterNodes) > 1 { - d.Set("cluster_type", tfredshift.ClusterTypeMultiNode) - } else { - d.Set("cluster_type", tfredshift.ClusterTypeSingleNode) - } - d.Set("cluster_version", rsc.ClusterVersion) - d.Set("database_name", rsc.DBName) - d.Set("encrypted", rsc.Encrypted) - d.Set("enhanced_vpc_routing", rsc.EnhancedVpcRouting) - d.Set("kms_key_id", rsc.KmsKeyId) - if err := d.Set("logging", flattenRedshiftLogging(loggingStatus)); err != nil { - return fmt.Errorf("error setting logging: %w", err) - } - d.Set("master_username", rsc.MasterUsername) - d.Set("node_type", rsc.NodeType) - d.Set("number_of_nodes", rsc.NumberOfNodes) - d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) - d.Set("publicly_accessible", rsc.PubliclyAccessible) - if err := d.Set("snapshot_copy", flattenRedshiftSnapshotCopy(rsc.ClusterSnapshotCopyStatus)); err != nil { - return fmt.Errorf("error setting snapshot_copy: %w", err) - } - - d.Set("dns_name", nil) - d.Set("endpoint", nil) - d.Set("port", nil) - if endpoint := rsc.Endpoint; endpoint != nil { - if address := aws.StringValue(endpoint.Address); address != "" { - d.Set("dns_name", address) - if port := aws.Int64Value(endpoint.Port); port != 0 { - d.Set("endpoint", fmt.Sprintf("%s:%d", address, port)) - d.Set("port", port) - } else { - d.Set("endpoint", address) - } - } - } - - var apiList []*string - - for _, clusterSecurityGroup := range rsc.ClusterSecurityGroups { - apiList = append(apiList, clusterSecurityGroup.ClusterSecurityGroupName) - } - d.Set("cluster_security_groups", aws.StringValueSlice(apiList)) - - apiList = nil - - for _, iamRole := range rsc.IamRoles { - apiList = append(apiList, iamRole.IamRoleArn) - } - d.Set("iam_roles", aws.StringValueSlice(apiList)) - - apiList = nil - - for _, vpcSecurityGroup := range rsc.VpcSecurityGroups { - apiList = append(apiList, vpcSecurityGroup.VpcSecurityGroupId) - } - d.Set("vpc_security_group_ids", aws.StringValueSlice(apiList)) - - tags := keyvaluetags.RedshiftKeyValueTags(rsc.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) - - //lintignore:AWSR002 - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } - - if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) - } - - return nil -} - -func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).redshiftconn - - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - - if err := keyvaluetags.RedshiftUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Redshift Cluster (%s) tags: %s", d.Get("arn").(string), err) - } - } - - requestUpdate := false - log.Printf("[INFO] Building Redshift Modify Cluster Options") - req := &redshift.ModifyClusterInput{ - ClusterIdentifier: aws.String(d.Id()), - } - - // If the cluster type, node type, or number of nodes changed, then the AWS API expects all three - // items to be sent over - if d.HasChanges("cluster_type", "node_type", "number_of_nodes") { - req.ClusterType = aws.String(d.Get("cluster_type").(string)) - req.NodeType = aws.String(d.Get("node_type").(string)) - if v := d.Get("number_of_nodes").(int); v > 1 { - req.ClusterType = aws.String("multi-node") - req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) - } else { - req.ClusterType = aws.String("single-node") - } - requestUpdate = true - } - - if d.HasChange("availability_zone_relocation") { - req.AvailabilityZoneRelocation = aws.Bool(d.Get("availability_zone_relocation").(bool)) - requestUpdate = true - } - - if d.HasChange("cluster_security_groups") { - req.ClusterSecurityGroups = expandStringSet(d.Get("cluster_security_groups").(*schema.Set)) - requestUpdate = true - } - - if d.HasChange("vpc_security_group_ids") { - req.VpcSecurityGroupIds = expandStringSet(d.Get("vpc_security_group_ids").(*schema.Set)) - requestUpdate = true - } - - if d.HasChange("master_password") { - req.MasterUserPassword = aws.String(d.Get("master_password").(string)) - requestUpdate = true - } - - if d.HasChange("cluster_parameter_group_name") { - req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string)) - requestUpdate = true - } - - if d.HasChange("automated_snapshot_retention_period") { - req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))) - requestUpdate = true - } - - if d.HasChange("preferred_maintenance_window") { - req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) - requestUpdate = true - } - - if d.HasChange("cluster_version") { - req.ClusterVersion = aws.String(d.Get("cluster_version").(string)) - requestUpdate = true - } - - if d.HasChange("allow_version_upgrade") { - req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool)) - requestUpdate = true - } - - if d.HasChange("publicly_accessible") { - req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool)) - requestUpdate = true - } - - if d.HasChange("enhanced_vpc_routing") { - req.EnhancedVpcRouting = aws.Bool(d.Get("enhanced_vpc_routing").(bool)) - requestUpdate = true - } - - if d.HasChange("encrypted") { - req.Encrypted = aws.Bool(d.Get("encrypted").(bool)) - requestUpdate = true - } - - if d.Get("encrypted").(bool) && d.HasChange("kms_key_id") { - req.KmsKeyId = aws.String(d.Get("kms_key_id").(string)) - requestUpdate = true - } - - if requestUpdate { - log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) - log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) - _, err := conn.ModifyCluster(req) - if err != nil { - return fmt.Errorf("Error modifying Redshift Cluster (%s): %s", d.Id(), err) - } - } - - if d.HasChange("iam_roles") { - o, n := d.GetChange("iam_roles") - if o == nil { - o = new(schema.Set) - } - if n == nil { - n = new(schema.Set) - } - - os := o.(*schema.Set) - ns := n.(*schema.Set) - - removeIams := os.Difference(ns) - addIams := ns.Difference(os) - - log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") - req := &redshift.ModifyClusterIamRolesInput{ - ClusterIdentifier: aws.String(d.Id()), - AddIamRoles: expandStringSet(addIams), - RemoveIamRoles: expandStringSet(removeIams), - } - - log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) - log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) - _, err := conn.ModifyClusterIamRoles(req) - if err != nil { - return fmt.Errorf("Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) - } - } - - if requestUpdate || d.HasChange("iam_roles") { - - stateConf := &resource.StateChangeConf{ - Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying", "available, prep-for-resize"}, - Target: []string{"available"}, - Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d.Id(), conn), - Timeout: d.Timeout(schema.TimeoutUpdate), - MinTimeout: 10 * time.Second, - } - - // Wait, catching any errors - _, err := stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error Modifying Redshift Cluster (%s): %s", d.Id(), err) - } - } - - if d.HasChange("snapshot_copy") { - if v, ok := d.GetOk("snapshot_copy"); ok { - err := enableRedshiftSnapshotCopy(d.Id(), v.([]interface{}), conn) - if err != nil { - return err - } - } else { - _, err := conn.DisableSnapshotCopy(&redshift.DisableSnapshotCopyInput{ - ClusterIdentifier: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("Failed to disable snapshot copy: %s", err) - } - } - } - - if d.HasChange("logging") { - if loggingEnabled, ok := d.GetOk("logging.0.enable"); ok && loggingEnabled.(bool) { - log.Printf("[INFO] Enabling Logging for Redshift Cluster %q", d.Id()) - err := enableRedshiftClusterLogging(d, conn) - if err != nil { - return err - } - } else { - log.Printf("[INFO] Disabling Logging for Redshift Cluster %q", d.Id()) - _, err := conn.DisableLogging(&redshift.DisableLoggingInput{ - ClusterIdentifier: aws.String(d.Id()), - }) - if err != nil { - return err - } - } - } - - return resourceAwsRedshiftClusterRead(d, meta) -} - -func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshift) error { - bucketNameRaw, ok := d.GetOk("logging.0.bucket_name") - - if !ok { - return fmt.Errorf("bucket_name must be set when enabling logging for Redshift Clusters") - } - - params := &redshift.EnableLoggingInput{ - ClusterIdentifier: aws.String(d.Id()), - BucketName: aws.String(bucketNameRaw.(string)), - } - - if v, ok := d.GetOk("logging.0.s3_key_prefix"); ok { - params.S3KeyPrefix = aws.String(v.(string)) - } - - if _, err := conn.EnableLogging(params); err != nil { - return fmt.Errorf("error enabling Redshift Cluster (%s) logging: %s", d.Id(), err) - } - return nil -} - -func enableRedshiftSnapshotCopy(id string, scList []interface{}, conn *redshift.Redshift) error { - sc := scList[0].(map[string]interface{}) - - input := redshift.EnableSnapshotCopyInput{ - ClusterIdentifier: aws.String(id), - DestinationRegion: aws.String(sc["destination_region"].(string)), - } - if rp, ok := sc["retention_period"]; ok { - input.RetentionPeriod = aws.Int64(int64(rp.(int))) - } - if gn, ok := sc["grant_name"]; ok { - input.SnapshotCopyGrantName = aws.String(gn.(string)) - } - - _, err := conn.EnableSnapshotCopy(&input) - if err != nil { - return fmt.Errorf("Failed to enable snapshot copy: %s", err) - } - return nil -} - -func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).redshiftconn - - skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) - input := &redshift.DeleteClusterInput{ - ClusterIdentifier: aws.String(d.Id()), - SkipFinalClusterSnapshot: aws.Bool(skipFinalSnapshot), - } - - if !skipFinalSnapshot { - if v, ok := d.GetOk("final_snapshot_identifier"); ok { - input.FinalClusterSnapshotIdentifier = aws.String(v.(string)) - } else { - return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required") - } - } - - log.Printf("[DEBUG] Deleting Redshift Cluster: %s", d.Id()) - _, err := tfresource.RetryWhenAwsErrCodeEquals( - waiter.ClusterInvalidClusterStateFaultTimeout, - func() (interface{}, error) { - return conn.DeleteCluster(input) - }, - redshift.ErrCodeInvalidClusterStateFault, - ) - - if tfawserr.ErrCodeEquals(err, redshift.ErrCodeClusterNotFoundFault) { - return nil - } - - if err != nil { - return fmt.Errorf("error deleting Redshift Cluster (%s): %w", d.Id(), err) - } - - _, err = waiter.ClusterDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) - - if err != nil { - return fmt.Errorf("error waiting for Redshift Cluster (%s) delete: %w", d.Id(), err) - } - - return nil -} - -func resourceAwsRedshiftClusterStateRefreshFunc(id string, conn *redshift.Redshift) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[INFO] Reading Redshift Cluster Information: %s", id) - resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ - ClusterIdentifier: aws.String(id), - }) - - if err != nil { - if isAWSErr(err, redshift.ErrCodeClusterNotFoundFault, "") { - return 42, "destroyed", nil - } - log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", id, err) - return nil, "", err - } - - var rsc *redshift.Cluster - - for _, c := range resp.Clusters { - if *c.ClusterIdentifier == id { - rsc = c - } - } - - if rsc == nil { - return 42, "destroyed", nil - } - - if rsc.ClusterStatus != nil { - log.Printf("[DEBUG] Redshift Cluster status (%s): %s", id, *rsc.ClusterStatus) - } - - return rsc, *rsc.ClusterStatus, nil - } -} - -func flattenRedshiftClusterNode(apiObject *redshift.ClusterNode) map[string]interface{} { - if apiObject == nil { - return nil - } - - tfMap := map[string]interface{}{} - - if v := apiObject.NodeRole; v != nil { - tfMap["node_role"] = aws.StringValue(v) - } - - if v := apiObject.PrivateIPAddress; v != nil { - tfMap["private_ip_address"] = aws.StringValue(v) - } - - if v := apiObject.PublicIPAddress; v != nil { - tfMap["public_ip_address"] = aws.StringValue(v) - } - - return tfMap -} - -func flattenRedshiftClusterNodes(apiObjects []*redshift.ClusterNode) []interface{} { - if len(apiObjects) == 0 { - return nil - } - - var tfList []interface{} - - for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - - tfList = append(tfList, flattenRedshiftClusterNode(apiObject)) - } - - return tfList -} diff --git a/aws/resource_aws_redshift_cluster_test.go b/aws/resource_aws_redshift_cluster_test.go deleted file mode 100644 index cc3b481c27a..00000000000 --- a/aws/resource_aws_redshift_cluster_test.go +++ /dev/null @@ -1,1495 +0,0 @@ -package aws - -import ( - "errors" - "fmt" - "log" - "regexp" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/redshift" - multierror "github.com/hashicorp/go-multierror" - "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/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/redshift/finder" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" -) - -func init() { - resource.AddTestSweepers("aws_redshift_cluster", &resource.Sweeper{ - Name: "aws_redshift_cluster", - F: testSweepRedshiftClusters, - }) -} - -func testSweepRedshiftClusters(region string) error { - client, err := sharedClientForRegion(region) - - if err != nil { - return fmt.Errorf("error getting client: %s", err) - } - - conn := client.(*AWSClient).redshiftconn - sweepResources := make([]*testSweepResource, 0) - var errs *multierror.Error - - err = conn.DescribeClustersPages(&redshift.DescribeClustersInput{}, func(resp *redshift.DescribeClustersOutput, lastPage bool) bool { - if len(resp.Clusters) == 0 { - log.Print("[DEBUG] No Redshift clusters to sweep") - return !lastPage - } - - for _, c := range resp.Clusters { - r := resourceAwsRedshiftCluster() - d := r.Data(nil) - d.Set("skip_final_snapshot", true) - d.SetId(aws.StringValue(c.ClusterIdentifier)) - - sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) - } - - return !lastPage - }) - - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error describing Redshift Clusters: %w", err)) - // in case work can be done, don't jump out yet - } - - if err = testSweepResourceOrchestrator(sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping Redshift Clusters for %s: %w", region, err)) - } - - if testSweepSkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping Redshift Cluster sweep for %s: %s", region, err) - return nil - } - - return errs.ErrorOrNil() -} - -func TestAccAWSRedshiftCluster_basic(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - config := testAccAWSRedshiftClusterConfig_basic(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "cluster_nodes.#", "1"), - resource.TestCheckResourceAttrSet("aws_redshift_cluster.default", "cluster_nodes.0.public_ip_address"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "cluster_type", "single-node"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "publicly_accessible", "true"), - resource.TestMatchResourceAttr("aws_redshift_cluster.default", "dns_name", regexp.MustCompile(fmt.Sprintf("^tf-redshift-cluster-%d.*\\.redshift\\..*", ri))), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_withFinalSnapshot(t *testing.T) { - var v redshift.Cluster - - rInt := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterSnapshot(rInt), - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfigWithFinalSnapshot(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_kmsKey(t *testing.T) { - var v redshift.Cluster - - resourceName := "aws_redshift_cluster.default" - kmsResourceName := "aws_kms_key.foo" - - ri := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfig_kmsKey(ri), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "cluster_type", "single-node"), - resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "true"), - resource.TestCheckResourceAttrPair(resourceName, "kms_key_id", kmsResourceName, "arn"), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_enhancedVpcRoutingEnabled(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_enhancedVpcRoutingEnabled(ri) - postConfig := testAccAWSRedshiftClusterConfig_enhancedVpcRoutingDisabled(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "enhanced_vpc_routing", "true"), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "enhanced_vpc_routing", "false"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_loggingEnabled(t *testing.T) { - var v redshift.Cluster - rInt := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfig_loggingEnabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "logging.0.enable", "true"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "logging.0.bucket_name", fmt.Sprintf("tf-test-redshift-logging-%d", rInt)), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - { - Config: testAccAWSRedshiftClusterConfig_loggingDisabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "logging.0.enable", "false"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_snapshotCopy(t *testing.T) { - var providers []*schema.Provider - var v redshift.Cluster - rInt := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccMultipleRegionPreCheck(t, 2) - }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - ProviderFactories: testAccProviderFactoriesAlternate(&providers), - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfig_snapshotCopyEnabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttrPair("aws_redshift_cluster.default", - "snapshot_copy.0.destination_region", "data.aws_region.alternate", "name"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "snapshot_copy.0.retention_period", "1"), - ), - }, - - { - Config: testAccAWSRedshiftClusterConfig_snapshotCopyDisabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "snapshot_copy.#", "0"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_iamRoles(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_iamRoles(ri) - postConfig := testAccAWSRedshiftClusterConfig_updateIamRoles(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "iam_roles.#", "2"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "iam_roles.#", "1"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_publiclyAccessible(t *testing.T) { - var v redshift.Cluster - rInt := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfig_notPubliclyAccessible(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "publicly_accessible", "false"), - ), - }, - - { - Config: testAccAWSRedshiftClusterConfig_updatePubliclyAccessible(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "publicly_accessible", "true"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_updateNodeCount(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_basic(ri) - postConfig := testAccAWSRedshiftClusterConfig_updateNodeCount(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "number_of_nodes", "1"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "number_of_nodes", "2"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "cluster_type", "multi-node"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "node_type", "dc1.large"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_updateNodeType(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_basic(ri) - postConfig := testAccAWSRedshiftClusterConfig_updateNodeType(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "node_type", "dc1.large"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "number_of_nodes", "1"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "cluster_type", "single-node"), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "node_type", "dc2.large"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_tags(t *testing.T) { - var v redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_tags(ri) - postConfig := testAccAWSRedshiftClusterConfig_updatedTags(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "tags.%", "3"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "tags.environment", "Production"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "tags.%", "1"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "tags.environment", "Production"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_forceNewUsername(t *testing.T) { - var first, second redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_basic(ri) - postConfig := testAccAWSRedshiftClusterConfig_updatedUsername(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &first), - testAccCheckAWSRedshiftClusterMasterUsername(&first, "foo_test"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "master_username", "foo_test"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &second), - testAccCheckAWSRedshiftClusterMasterUsername(&second, "new_username"), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "master_username", "new_username"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_changeAvailabilityZone(t *testing.T) { - var first, second redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_basic(ri) - postConfig := testAccAWSRedshiftClusterConfig_updatedAvailabilityZone(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &first), - resource.TestCheckResourceAttrPair("aws_redshift_cluster.default", "availability_zone", "data.aws_availability_zones.available", "names.0"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &second), - resource.TestCheckResourceAttrPair("aws_redshift_cluster.default", "availability_zone", "data.aws_availability_zones.available", "names.1"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_changeEncryption1(t *testing.T) { - var cluster1, cluster2 redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_basic(ri) - postConfig := testAccAWSRedshiftClusterConfig_encrypted(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &cluster1), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "encrypted", "false"), - ), - }, - - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &cluster2), - testAccCheckAWSRedshiftClusterNotRecreated(&cluster1, &cluster2), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "encrypted", "true"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_changeEncryption2(t *testing.T) { - var cluster1, cluster2 redshift.Cluster - - ri := acctest.RandInt() - preConfig := testAccAWSRedshiftClusterConfig_encrypted(ri) - postConfig := testAccAWSRedshiftClusterConfig_unencrypted(ri) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: preConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &cluster1), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "encrypted", "true"), - ), - }, - { - Config: postConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &cluster2), - testAccCheckAWSRedshiftClusterNotRecreated(&cluster1, &cluster2), - resource.TestCheckResourceAttr("aws_redshift_cluster.default", "encrypted", "false"), - ), - }, - }, - }) -} - -func TestAccAWSRedshiftCluster_availabilityZoneRelocation(t *testing.T) { - var v redshift.Cluster - rInt := acctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, redshift.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSRedshiftClusterConfig_availabilityZoneRelocationEnabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "availability_zone_relocation", "true"), - ), - }, - { - ResourceName: "aws_redshift_cluster.default", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "final_snapshot_identifier", - "master_password", - "skip_final_snapshot", - }, - }, - { - Config: testAccAWSRedshiftClusterConfig_availabilityZoneRelocationDisabled(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "availability_zone_relocation", "false"), - ), - }, - }, - }) -} - -func testAccCheckAWSRedshiftClusterDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).redshiftconn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_redshift_cluster" { - continue - } - - _, err := finder.ClusterByID(conn, rs.Primary.ID) - - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return err - } - - return fmt.Errorf("Redshift Cluster %s still exists", rs.Primary.ID) - } - - return nil -} - -func testAccCheckAWSRedshiftClusterSnapshot(rInt int) resource.TestCheckFunc { - return func(s *terraform.State) error { - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_redshift_cluster" { - continue - } - - // Try and delete the snapshot before we check for the cluster not found - conn := testAccProvider.Meta().(*AWSClient).redshiftconn - - snapshot_identifier := fmt.Sprintf("tf-acctest-snapshot-%d", rInt) - - log.Printf("[INFO] Deleting the Snapshot %s", snapshot_identifier) - _, err := conn.DeleteClusterSnapshot( - &redshift.DeleteClusterSnapshotInput{ - SnapshotIdentifier: aws.String(snapshot_identifier), - }) - if err != nil { - return fmt.Errorf("error deleting Redshift Cluster Snapshot (%s): %w", snapshot_identifier, err) - } - - _, err = finder.ClusterByID(conn, rs.Primary.ID) - - if tfresource.NotFound(err) { - return nil - } - - if err != nil { - return err - } - - return fmt.Errorf("Redshift Cluster %s still exists", rs.Primary.ID) - } - - return nil - } -} - -func testAccCheckAWSRedshiftClusterExists(n string, v *redshift.Cluster) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No Redshift Cluster ID is set") - } - - conn := testAccProvider.Meta().(*AWSClient).redshiftconn - - output, err := finder.ClusterByID(conn, rs.Primary.ID) - - if err != nil { - return err - } - - *v = *output - - return nil - } -} - -func testAccCheckAWSRedshiftClusterMasterUsername(c *redshift.Cluster, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *c.MasterUsername != value { - return fmt.Errorf("Expected cluster's MasterUsername: %q, given: %q", value, *c.MasterUsername) - } - return nil - } -} - -func testAccCheckAWSRedshiftClusterNotRecreated(i, j *redshift.Cluster) resource.TestCheckFunc { - return func(s *terraform.State) error { - // In lieu of some other uniquely identifying attribute from the API that always changes - // when a cluster is destroyed and recreated with the same identifier, we use the SSH key - // as it will get regenerated when a cluster is destroyed. - // Certain update operations (e.g KMS encrypting a cluster) will change ClusterCreateTime. - // Clusters with the same identifier can/will have an overlapping Endpoint.Address. - if aws.StringValue(i.ClusterPublicKey) != aws.StringValue(j.ClusterPublicKey) { - return errors.New("Redshift Cluster was recreated") - } - - return nil - } -} - -func testAccAWSRedshiftClusterConfig_updateNodeCount(rInt int) string { - return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` -resource "aws_redshift_cluster" "default" { - cluster_identifier = "tf-redshift-cluster-%[1]d" - availability_zone = data.aws_availability_zones.available.names[0] - database_name = "mydb" - master_username = "foo_test" - master_password = "Mustbe8characters" - node_type = "dc1.large" - automated_snapshot_retention_period = 0 - allow_version_upgrade = false - number_of_nodes = 2 - skip_final_snapshot = true -} -`, rInt)) -} - -func testAccAWSRedshiftClusterConfig_updateNodeType(rInt int) string { - return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` -resource "aws_redshift_cluster" "default" { - cluster_identifier = "tf-redshift-cluster-%[1]d" - availability_zone = data.aws_availability_zones.available.names[0] - database_name = "mydb" - master_username = "foo_test" - master_password = "Mustbe8characters" - node_type = "dc2.large" - automated_snapshot_retention_period = 0 - allow_version_upgrade = false - number_of_nodes = 1 - skip_final_snapshot = true -} -`, rInt)) -} - -func testAccAWSRedshiftClusterConfig_basic(rInt int) string { - // "InvalidVPCNetworkStateFault: The requested AZ us-west-2a is not a valid AZ." - return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` -resource "aws_redshift_cluster" "default" { - cluster_identifier = "tf-redshift-cluster-%[1]d" - availability_zone = data.aws_availability_zones.available.names[0] - database_name = "mydb" - master_username = "foo_test" - master_password = "Mustbe8characters" - node_type = "dc1.large" - automated_snapshot_retention_period = 0 - allow_version_upgrade = false - skip_final_snapshot = true -} -`, rInt)) -} - -func testAccAWSRedshiftClusterConfig_encrypted(rInt int) string { - return composeConfig(testAccAvailableAZsNoOptInExcludeConfig("usw2-az2"), fmt.Sprintf(` -resource "aws_kms_key" "foo" { - description = "Terraform acc test %[1]d" - - policy = < Date: Sun, 12 Dec 2021 18:14:18 -0700 Subject: [PATCH 21/29] #19098: Lint: Imports of different types are not allowed in the same group --- internal/service/redshift/status.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/redshift/status.go b/internal/service/redshift/status.go index 39ac52f0aae..f3801c3eada 100644 --- a/internal/service/redshift/status.go +++ b/internal/service/redshift/status.go @@ -3,6 +3,7 @@ package redshift import ( "errors" "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/redshift" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" From 4fc4489845ed305915c0948d7c42f5a619ea08a7 Mon Sep 17 00:00:00 2001 From: nickyamanaka Date: Sun, 12 Dec 2021 18:58:32 -0700 Subject: [PATCH 22/29] #19098: Lint: should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...)) --- internal/service/redshift/status.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/service/redshift/status.go b/internal/service/redshift/status.go index f3801c3eada..b6f7be92084 100644 --- a/internal/service/redshift/status.go +++ b/internal/service/redshift/status.go @@ -1,7 +1,6 @@ package redshift import ( - "errors" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -34,6 +33,6 @@ func availabilityZoneRelocationStatus(cluster *redshift.Cluster) (bool, error) { case "disabled", "pending_disabling": return false, nil default: - return false, errors.New(fmt.Sprintf("unexpected AvailabilityZoneRelocationStatus attribute value: %s", availabilityZoneRelocationStatus)) + return false, fmt.Errorf("unexpected AvailabilityZoneRelocationStatus attribute value: %s", availabilityZoneRelocationStatus) } } From d31e80d0510d99714a4c175c8225bf133e80bea9 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 9 Mar 2022 16:16:13 -0800 Subject: [PATCH 23/29] Cleanup --- internal/service/redshift/cluster.go | 2 +- website/docs/d/redshift_cluster.html.markdown | 18 +++++++-------- website/docs/r/redshift_cluster.html.markdown | 22 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index b50293ce111..0c2c17f7a8d 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -952,7 +952,7 @@ func resourceClusterStateRefreshFunc(id string, conn *redshift.Redshift) resourc }) if err != nil { - if tfawserr.ErrMessageContains(err, redshift.ErrCodeClusterNotFoundFault, "") { + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeClusterNotFoundFault) { return 42, "destroyed", nil } log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", id, err) diff --git a/website/docs/d/redshift_cluster.html.markdown b/website/docs/d/redshift_cluster.html.markdown index b1544735c6c..51b5c57d339 100644 --- a/website/docs/d/redshift_cluster.html.markdown +++ b/website/docs/d/redshift_cluster.html.markdown @@ -13,12 +13,12 @@ Provides details about a specific redshift cluster. ## Example Usage ```terraform -data "aws_redshift_cluster" "test_cluster" { - cluster_identifier = "test-cluster" +data "aws_redshift_cluster" "example" { + cluster_identifier = "example-cluster" } -resource "aws_kinesis_firehose_delivery_stream" "test_stream" { - name = "terraform-kinesis-firehose-test-stream" +resource "aws_kinesis_firehose_delivery_stream" "example_stream" { + name = "terraform-kinesis-firehose-example-stream" destination = "redshift" s3_configuration { @@ -31,12 +31,12 @@ resource "aws_kinesis_firehose_delivery_stream" "test_stream" { redshift_configuration { role_arn = aws_iam_role.firehose_role.arn - cluster_jdbcurl = "jdbc:redshift://${data.aws_redshift_cluster.test_cluster.endpoint}/${data.aws_redshift_cluster.test_cluster.database_name}" - username = "testuser" - password = "T3stPass" - data_table_name = "test-table" + cluster_jdbcurl = "jdbc:redshift://${data.aws_redshift_cluster.example.endpoint}/${data.aws_redshift_cluster.example.database_name}" + username = "exampleuser" + password = "Exampl3Pass" + data_table_name = "example-table" copy_options = "delimiter '|'" # the default delimiter - data_table_columns = "test-col" + data_table_columns = "example-col" } } ``` diff --git a/website/docs/r/redshift_cluster.html.markdown b/website/docs/r/redshift_cluster.html.markdown index d3e22d44cfe..2d4478b994c 100644 --- a/website/docs/r/redshift_cluster.html.markdown +++ b/website/docs/r/redshift_cluster.html.markdown @@ -16,10 +16,10 @@ Provides a Redshift Cluster Resource. ## Example Usage ```terraform -resource "aws_redshift_cluster" "default" { +resource "aws_redshift_cluster" "example" { cluster_identifier = "tf-redshift-cluster" database_name = "mydb" - master_username = "foo" + master_username = "exampleuser" master_password = "Mustbe8characters" node_type = "dc1.large" cluster_type = "single-node" @@ -33,30 +33,30 @@ the [AWS official documentation](http://docs.aws.amazon.com/cli/latest/reference The following arguments are supported: -* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case -string. +* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case string. * `database_name` - (Optional) The name of the first database to be created when the cluster is created. - If you do not provide a name, Amazon Redshift will create a default database called `dev`. + If you do not provide a name, Amazon Redshift will create a default database called `dev`. * `node_type` - (Required) The node type to be provisioned for the cluster. * `cluster_type` - (Optional) The cluster type to use. Either `single-node` or `multi-node`. * `master_password` - (Required unless a `snapshot_identifier` is provided) Password for the master DB user. - Note that this may show up in logs, and it will be stored in the state file. Password must contain at least 8 chars and - contain at least one uppercase letter, one lowercase letter, and one number. + Note that this may show up in logs, and it will be stored in the state file. Password must contain at least 8 chars and + contain at least one uppercase letter, one lowercase letter, and one number. * `master_username` - (Required unless a `snapshot_identifier` is provided) Username for the master DB user. - * `cluster_security_groups` - (Optional) A list of security groups to be associated with this cluster. * `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster. * `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC). * `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency. * `availability_zone_relocation` - (Optional) Enable relocation for an Amazon Redshift cluster between Availability Zones. Default is false. Available for use on clusters from the RA3 instance family. * `preferred_maintenance_window` - (Optional) The weekly time range (in UTC) during which automated cluster maintenance can occur. - Format: ddd:hh24:mi-ddd:hh24:mi + Format: ddd:hh24:mi-ddd:hh24:mi * `cluster_parameter_group_name` - (Optional) The name of the parameter group to be associated with this cluster. * `automated_snapshot_retention_period` - (Optional) The number of days that automated snapshots are retained. If the value is 0, automated snapshots are disabled. Even if automated snapshots are disabled, you can still create manual snapshots when you want with create-cluster-snapshot. Default is 1. * `port` - (Optional) The port number on which the cluster accepts incoming connections. - The cluster is accessible only via the JDBC and ODBC connection strings. Part of the connection string requires the port on which the cluster will listen for incoming connections. Default port is 5439. + The cluster is accessible only via the JDBC and ODBC connection strings. + Part of the connection string requires the port on which the cluster will listen for incoming connections. + Default port is 5439. * `cluster_version` - (Optional) The version of the Amazon Redshift engine software that you want to deploy on the cluster. - The version selected runs on all the nodes in the cluster. + The version selected runs on all the nodes in the cluster. * `allow_version_upgrade` - (Optional) If true , major version upgrades can be applied during the maintenance window to the Amazon Redshift engine that is running on the cluster. Default is true * `number_of_nodes` - (Optional) The number of compute nodes in the cluster. This parameter is required when the ClusterType parameter is specified as multi-node. Default is 1. * `publicly_accessible` - (Optional) If true, the cluster can be accessed from a public network. Default is `true`. From df44ef146d203b26682837001ecd9532aa1ad6ac Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 9 Mar 2022 17:05:55 -0800 Subject: [PATCH 24/29] Tweaks to acceptance tests for `availability_zone_relocation` --- .../redshift/cluster_data_source_test.go | 45 +++++++++++++++- internal/service/redshift/cluster_test.go | 51 ++++++++++++------- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/internal/service/redshift/cluster_data_source_test.go b/internal/service/redshift/cluster_data_source_test.go index 67bc305d0fb..9cd473057cb 100644 --- a/internal/service/redshift/cluster_data_source_test.go +++ b/internal/service/redshift/cluster_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccRedshiftClusterDataSource_basic(t *testing.T) { dataSourceName := "data.aws_redshift_cluster.test" + resourceName := "aws_redshift_cluster.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -40,7 +41,7 @@ func TestAccRedshiftClusterDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrSet(dataSourceName, "port"), resource.TestCheckResourceAttrSet(dataSourceName, "preferred_maintenance_window"), resource.TestCheckResourceAttrSet(dataSourceName, "publicly_accessible"), - resource.TestCheckResourceAttrSet(dataSourceName, "availability_zone_relocation"), + resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation", resourceName, "availability_zone_relocation"), ), }, }, @@ -92,6 +93,26 @@ func TestAccRedshiftClusterDataSource_logging(t *testing.T) { }) } +func TestAccRedshiftClusterDataSource_availabilityZoneRelocationEnabled(t *testing.T) { + dataSourceName := "data.aws_redshift_cluster.test" + resourceName := "aws_redshift_cluster.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccClusterDataSourceConfig_availabilityZoneRelocationEnabled(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation", resourceName, "availability_zone_relocation"), + ), + }, + }, + }) +} + func testAccClusterDataSourceConfig(rName string) string { return fmt.Sprintf(` resource "aws_redshift_cluster" "test" { @@ -235,3 +256,25 @@ data "aws_redshift_cluster" "test" { } `, rName) } + +func testAccClusterDataSourceConfig_availabilityZoneRelocationEnabled(rName string) string { + return fmt.Sprintf(` +resource "aws_redshift_cluster" "test" { + cluster_identifier = %[1]q + + database_name = "testdb" + master_username = "foo" + master_password = "Password1" + node_type = "ra3.xlplus" + cluster_type = "single-node" + skip_final_snapshot = true + publicly_accessible = false + + availability_zone_relocation = true +} + +data "aws_redshift_cluster" "test" { + cluster_identifier = aws_redshift_cluster.test.cluster_identifier +} +`, rName) +} diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index 1cfbf36dd9e..f5d88091f10 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -33,11 +33,13 @@ func TestAccRedshiftCluster_basic(t *testing.T) { Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), resource.TestCheckResourceAttr(resourceName, "cluster_nodes.#", "1"), resource.TestCheckResourceAttrSet(resourceName, "cluster_nodes.0.public_ip_address"), resource.TestCheckResourceAttr(resourceName, "cluster_type", "single-node"), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "true"), resource.TestMatchResourceAttr(resourceName, "dns_name", regexp.MustCompile(fmt.Sprintf("^%s.*\\.redshift\\..*", rName))), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), ), }, { @@ -458,7 +460,7 @@ func TestAccRedshiftCluster_forceNewUsername(t *testing.T) { } func TestAccRedshiftCluster_changeAvailabilityZone(t *testing.T) { - var v redshift.Cluster + var v1, v2 redshift.Cluster resourceName := "aws_redshift_cluster.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -471,14 +473,15 @@ func TestAccRedshiftCluster_changeAvailabilityZone(t *testing.T) { { Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists(resourceName, &v), + testAccCheckClusterExists(resourceName, &v1), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), ), }, { Config: testAccClusterConfig_updatedAvailabilityZone(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists(resourceName, &v), + testAccCheckClusterExists(resourceName, &v2), + testAccCheckClusterRecreated(&v1, &v2), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.1"), ), }, @@ -548,7 +551,8 @@ func TestAccRedshiftCluster_changeEncryption2(t *testing.T) { func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { var v redshift.Cluster - rInt := sdkacctest.RandInt() + resourceName := "aws_redshift_cluster.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -557,15 +561,14 @@ func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccClusterConfig_availabilityZoneRelocation(rInt, true), + Config: testAccClusterConfig_availabilityZoneRelocation(rName, true), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "availability_zone_relocation", "true"), + testAccCheckClusterExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), ), }, { - ResourceName: "aws_redshift_cluster.default", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{ @@ -575,11 +578,10 @@ func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { }, }, { - Config: testAccClusterConfig_availabilityZoneRelocation(rInt, false), + Config: testAccClusterConfig_availabilityZoneRelocation(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists("aws_redshift_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_redshift_cluster.default", "availability_zone_relocation", "false"), + testAccCheckClusterExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), ), }, }, @@ -695,6 +697,21 @@ func testAccCheckClusterNotRecreated(i, j *redshift.Cluster) resource.TestCheckF } } +func testAccCheckClusterRecreated(i, j *redshift.Cluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + // In lieu of some other uniquely identifying attribute from the API that always changes + // when a cluster is destroyed and recreated with the same identifier, we use the SSH key + // as it will get regenerated when a cluster is destroyed. + // Certain update operations (e.g KMS encrypting a cluster) will change ClusterCreateTime. + // Clusters with the same identifier can/will have an overlapping Endpoint.Address. + if aws.StringValue(i.ClusterPublicKey) == aws.StringValue(j.ClusterPublicKey) { + return errors.New("Redshift Cluster was not recreated") + } + + return nil + } +} + func testAccClusterConfig_updateNodeCount(rName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(` resource "aws_redshift_cluster" "test" { @@ -1319,10 +1336,10 @@ resource "aws_redshift_cluster" "test" { `, rName)) } -func testAccClusterConfig_availabilityZoneRelocation(rInt int, enabled bool) string { +func testAccClusterConfig_availabilityZoneRelocation(rName string, enabled bool) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(` -resource "aws_redshift_cluster" "default" { - cluster_identifier = "tf-redshift-cluster-%[1]d" +resource "aws_redshift_cluster" "test" { + cluster_identifier = %[1]q availability_zone = data.aws_availability_zones.available.names[0] database_name = "mydb" master_username = "foo_test" @@ -1336,5 +1353,5 @@ resource "aws_redshift_cluster" "default" { allow_version_upgrade = false skip_final_snapshot = true } -`, rInt, enabled)) +`, rName, enabled)) } From 95ae8661d11fe8c12ecd62a86a9124039427d922 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 11 Mar 2022 16:01:54 -0800 Subject: [PATCH 25/29] Adds plan-time checks for incompatible settings and adds test for relocating cluster --- internal/service/redshift/cluster.go | 86 +++++++-- .../service/redshift/cluster_data_source.go | 2 +- internal/service/redshift/cluster_test.go | 167 +++++++++++++++--- internal/service/redshift/status.go | 14 -- 4 files changed, 219 insertions(+), 50 deletions(-) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 0c2c17f7a8d..577b79c3baa 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -1,6 +1,8 @@ package redshift import ( + "context" + "errors" "fmt" "log" "regexp" @@ -11,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/redshift" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -56,7 +59,6 @@ func ResourceCluster() *schema.Resource { "availability_zone": { Type: schema.TypeString, Optional: true, - ForceNew: true, Computed: true, }, "availability_zone_relocation": { @@ -322,7 +324,28 @@ func ResourceCluster() *schema.Resource { "tags_all": tftags.TagsSchemaComputed(), }, - CustomizeDiff: verify.SetTagsDiff, + CustomizeDiff: customdiff.All( + verify.SetTagsDiff, + func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + if diff.Get("availability_zone_relocation").(bool) && diff.Get("publicly_accessible").(bool) { + return errors.New("availability_zone_relocation can not be true when publicly_accessible is true") + } + return nil + }, + func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + if diff.Id() == "" { + return nil + } + if diff.Get("availability_zone_relocation").(bool) { + return nil + } + o, n := diff.GetChange("availability_zone") + if o.(string) != n.(string) { + return fmt.Errorf("cannot change availability_zone if availability_zone_relocation is not true") + } + return nil + }, + ), } } @@ -568,7 +591,7 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) - azr, err := availabilityZoneRelocationStatus(rsc) + azr, err := clusterAvailabilityZoneRelocationStatus(rsc) if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) } @@ -750,11 +773,10 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { } if requestUpdate { - log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) - log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) + log.Printf("[DEBUG] Modifying Redshift Cluster: %s", d.Id()) _, err := conn.ModifyCluster(req) if err != nil { - return fmt.Errorf("Error modifying Redshift Cluster (%s): %s", d.Id(), err) + return fmt.Errorf("Error modifying Redshift Cluster (%s): %w", d.Id(), err) } } @@ -773,23 +795,20 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { removeIams := os.Difference(ns) addIams := ns.Difference(os) - log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") req := &redshift.ModifyClusterIamRolesInput{ ClusterIdentifier: aws.String(d.Id()), AddIamRoles: flex.ExpandStringSet(addIams), RemoveIamRoles: flex.ExpandStringSet(removeIams), } - log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) - log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) + log.Printf("[DEBUG] Modifying Redshift Cluster IAM Roles: %s", d.Id()) _, err := conn.ModifyClusterIamRoles(req) if err != nil { - return fmt.Errorf("Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) + return fmt.Errorf("Error modifying Redshift Cluster IAM Roles (%s): %w", d.Id(), err) } } if requestUpdate || d.HasChange("iam_roles") { - stateConf := &resource.StateChangeConf{ Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying", "available, prep-for-resize"}, Target: []string{"available"}, @@ -798,10 +817,35 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { MinTimeout: 10 * time.Second, } - // Wait, catching any errors _, err := stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error Modifying Redshift Cluster (%s): %s", d.Id(), err) + return fmt.Errorf("Error waiting for Redshift Cluster modification (%s): %w", d.Id(), err) + } + } + + // Availability Zone cannot be changed at the same time as other settings + if d.HasChange("availability_zone") { + req := &redshift.ModifyClusterInput{ + ClusterIdentifier: aws.String(d.Id()), + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + } + log.Printf("[DEBUG] Relocating Redshift Cluster: %s", d.Id()) + _, err := conn.ModifyCluster(req) + if err != nil { + return fmt.Errorf("Error relocating Redshift Cluster (%s): %w", d.Id(), err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying", "available, prep-for-resize", "recovering"}, + Target: []string{"available"}, + Refresh: resourceClusterStateRefreshFunc(d.Id(), conn), + Timeout: d.Timeout(schema.TimeoutUpdate), + MinTimeout: 10 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Redshift Cluster relocation (%s): %w", d.Id(), err) } } @@ -816,7 +860,7 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { ClusterIdentifier: aws.String(d.Id()), }) if err != nil { - return fmt.Errorf("Failed to disable snapshot copy: %s", err) + return fmt.Errorf("Failed to disable snapshot copy: %w", err) } } } @@ -896,7 +940,7 @@ func enableRedshiftSnapshotCopy(id string, scList []interface{}, conn *redshift. _, err := conn.EnableSnapshotCopy(&input) if err != nil { - return fmt.Errorf("Failed to enable snapshot copy: %s", err) + return fmt.Errorf("Failed to enable snapshot copy: %w", err) } return nil } @@ -1018,3 +1062,15 @@ func flattenRedshiftClusterNodes(apiObjects []*redshift.ClusterNode) []interface return tfList } + +func clusterAvailabilityZoneRelocationStatus(cluster *redshift.Cluster) (bool, error) { + // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. + switch availabilityZoneRelocationStatus := aws.StringValue(cluster.AvailabilityZoneRelocationStatus); availabilityZoneRelocationStatus { + case "enabled", "pending_enabling": + return true, nil + case "disabled", "pending_disabling": + return false, nil + default: + return false, fmt.Errorf("unexpected AvailabilityZoneRelocationStatus value %q returned by API", availabilityZoneRelocationStatus) + } +} diff --git a/internal/service/redshift/cluster_data_source.go b/internal/service/redshift/cluster_data_source.go index 6ad252fb357..3a5cd9a6c46 100644 --- a/internal/service/redshift/cluster_data_source.go +++ b/internal/service/redshift/cluster_data_source.go @@ -207,7 +207,7 @@ func dataSourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) - azr, err := availabilityZoneRelocationStatus(rsc) + azr, err := clusterAvailabilityZoneRelocationStatus(rsc) if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) } diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index f5d88091f10..82d139e9456 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -429,7 +429,7 @@ func TestAccRedshiftCluster_tags(t *testing.T) { } func TestAccRedshiftCluster_forceNewUsername(t *testing.T) { - var v redshift.Cluster + var v1, v2 redshift.Cluster resourceName := "aws_redshift_cluster.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -442,16 +442,17 @@ func TestAccRedshiftCluster_forceNewUsername(t *testing.T) { { Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists(resourceName, &v), - testAccCheckClusterMasterUsername(&v, "foo_test"), + testAccCheckClusterExists(resourceName, &v1), + testAccCheckClusterMasterUsername(&v1, "foo_test"), resource.TestCheckResourceAttr(resourceName, "master_username", "foo_test"), ), }, { Config: testAccClusterConfig_updatedUsername(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExists(resourceName, &v), - testAccCheckClusterMasterUsername(&v, "new_username"), + testAccCheckClusterExists(resourceName, &v2), + testAccCheckClusterRecreated(&v1, &v2), + testAccCheckClusterMasterUsername(&v2, "new_username"), resource.TestCheckResourceAttr(resourceName, "master_username", "new_username"), ), }, @@ -471,17 +472,52 @@ func TestAccRedshiftCluster_changeAvailabilityZone(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccClusterConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccClusterConfig_updateAvailabilityZone(rName, 0), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), ), }, { - Config: testAccClusterConfig_updatedAvailabilityZone(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccClusterConfig_updateAvailabilityZone(rName, 1), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v2), - testAccCheckClusterRecreated(&v1, &v2), + testAccCheckClusterNotRecreated(&v1, &v2), + resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.1"), + ), + }, + }, + }) +} + +func TestAccRedshiftCluster_changeAvailabilityZoneAndSetAvailabilityZoneRelocation(t *testing.T) { + var v1, v2 redshift.Cluster + resourceName := "aws_redshift_cluster.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_updateAvailabilityZone_availabilityZoneRelocationNotSet(rName, 0), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckClusterExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), + ), + }, + { + Config: testAccClusterConfig_updateAvailabilityZone(rName, 1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckClusterExists(resourceName, &v2), + testAccCheckClusterNotRecreated(&v1, &v2), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.1"), ), }, @@ -489,6 +525,34 @@ func TestAccRedshiftCluster_changeAvailabilityZone(t *testing.T) { }) } +func TestAccRedshiftCluster_changeAvailabilityZone_availabilityZoneRelocationNotSet(t *testing.T) { + var v redshift.Cluster + resourceName := "aws_redshift_cluster.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_updateAvailabilityZone_availabilityZoneRelocationNotSet(rName, 0), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckClusterExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), + ), + }, + { + Config: testAccClusterConfig_updateAvailabilityZone_availabilityZoneRelocationNotSet(rName, 1), + ExpectError: regexp.MustCompile(`cannot change availability_zone if availability_zone_relocation is not true`), + }, + }, + }) +} + func TestAccRedshiftCluster_changeEncryption1(t *testing.T) { var cluster1, cluster2 redshift.Cluster resourceName := "aws_redshift_cluster.test" @@ -588,6 +652,23 @@ func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { }) } +func TestAccRedshiftCluster_availabilityZoneRelocation_publiclyAccessible(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_availabilityZoneRelocation_publiclyAccessible(rName), + ExpectError: regexp.MustCompile(`availability_zone_relocation can not be true when publicly_accessible is true`), + }, + }, + }) +} + func testAccCheckClusterDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn @@ -1320,38 +1401,84 @@ resource "aws_redshift_cluster" "test" { `, rName)) } -func testAccClusterConfig_updatedAvailabilityZone(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(` +func testAccClusterConfig_updateAvailabilityZone(rName string, regionIndex int) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), + fmt.Sprintf(` resource "aws_redshift_cluster" "test" { cluster_identifier = %[1]q - availability_zone = data.aws_availability_zones.available.names[1] database_name = "mydb" master_username = "foo_test" master_password = "Mustbe8characters" - node_type = "dc2.large" - automated_snapshot_retention_period = 0 + node_type = "ra3.xlplus" + automated_snapshot_retention_period = 1 allow_version_upgrade = false skip_final_snapshot = true + + publicly_accessible = false + availability_zone_relocation = true + availability_zone = data.aws_availability_zones.available.names[%[2]d] } -`, rName)) +`, rName, regionIndex)) +} + +func testAccClusterConfig_updateAvailabilityZone_availabilityZoneRelocationNotSet(rName string, regionIndex int) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), + fmt.Sprintf(` +resource "aws_redshift_cluster" "test" { + cluster_identifier = %[1]q + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "ra3.xlplus" + automated_snapshot_retention_period = 1 + allow_version_upgrade = false + skip_final_snapshot = true + + publicly_accessible = false + availability_zone_relocation = false + availability_zone = data.aws_availability_zones.available.names[%[2]d] +} +`, rName, regionIndex)) } func testAccClusterConfig_availabilityZoneRelocation(rName string, enabled bool) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(` + return acctest.ConfigCompose( + fmt.Sprintf(` resource "aws_redshift_cluster" "test" { cluster_identifier = %[1]q - availability_zone = data.aws_availability_zones.available.names[0] database_name = "mydb" master_username = "foo_test" master_password = "Mustbe8characters" node_type = "ra3.xlplus" number_of_nodes = 2 cluster_type = "multi-node" - availability_zone_relocation = tobool("%[2]t") - publicly_accessible = false automated_snapshot_retention_period = 1 allow_version_upgrade = false skip_final_snapshot = true + + publicly_accessible = false + availability_zone_relocation = %[2]t } `, rName, enabled)) } + +func testAccClusterConfig_availabilityZoneRelocation_publiclyAccessible(rName string) string { + return acctest.ConfigCompose( + fmt.Sprintf(` +resource "aws_redshift_cluster" "test" { + cluster_identifier = %[1]q + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "ra3.xlplus" + automated_snapshot_retention_period = 1 + allow_version_upgrade = false + skip_final_snapshot = true + + publicly_accessible = true + availability_zone_relocation = true +} +`, rName)) +} diff --git a/internal/service/redshift/status.go b/internal/service/redshift/status.go index b6f7be92084..9709bad6982 100644 --- a/internal/service/redshift/status.go +++ b/internal/service/redshift/status.go @@ -1,8 +1,6 @@ package redshift import ( - "fmt" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/redshift" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -24,15 +22,3 @@ func statusCluster(conn *redshift.Redshift, id string) resource.StateRefreshFunc return output, aws.StringValue(output.ClusterStatus), nil } } - -func availabilityZoneRelocationStatus(cluster *redshift.Cluster) (bool, error) { - // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. - switch availabilityZoneRelocationStatus := *cluster.AvailabilityZoneRelocationStatus; availabilityZoneRelocationStatus { - case "enabled", "pending_enabling": - return true, nil - case "disabled", "pending_disabling": - return false, nil - default: - return false, fmt.Errorf("unexpected AvailabilityZoneRelocationStatus attribute value: %s", availabilityZoneRelocationStatus) - } -} From 49e054f0b4d4f45d97b0e6041ab530bfe2758531 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 11 Mar 2022 17:02:41 -0800 Subject: [PATCH 26/29] Now waits for AvailabilityZoneRelocationStatus to be either `enabled` or `disabled` --- internal/service/redshift/cluster.go | 27 ++++++++++++++++----------- internal/service/redshift/enum.go | 21 +++++++++++++++++++++ internal/service/redshift/status.go | 16 ++++++++++++++++ internal/service/redshift/wait.go | 19 +++++++++++++++++++ 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 577b79c3baa..1a6784616f5 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -430,8 +430,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { resp, err := conn.RestoreFromClusterSnapshot(restoreOpts) if err != nil { - log.Printf("[ERROR] Error Restoring Redshift Cluster from Snapshot: %s", err) - return err + return fmt.Errorf("error restoring Redshift Cluster from snapshot: %w", err) } d.SetId(aws.StringValue(resp.Cluster.ClusterIdentifier)) @@ -517,8 +516,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) resp, err := conn.CreateCluster(createOpts) if err != nil { - log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) - return err + return fmt.Errorf("error creating Redshift Cluster: %w", err) } log.Printf("[DEBUG]: Cluster create response: %s", resp) @@ -532,10 +530,14 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { Timeout: d.Timeout(schema.TimeoutCreate), MinTimeout: 10 * time.Second, } - _, err := stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error waiting for Redshift Cluster state to be \"available\": %s", err) + return fmt.Errorf("Error waiting for Redshift Cluster state to be \"available\": %w", err) + } + + _, err = waitClusterRelocationStatusResolved(conn, d.Id()) + if err != nil { + return fmt.Errorf("error waiting for Redshift Cluster Availability Zone Relocation Status to resolve: %w", err) } if v, ok := d.GetOk("snapshot_copy"); ok { @@ -547,7 +549,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { if _, ok := d.GetOk("logging.0.enable"); ok { if err := enableRedshiftClusterLogging(d, conn); err != nil { - return fmt.Errorf("error enabling Redshift Cluster (%s) logging: %s", d.Id(), err) + return fmt.Errorf("error enabling Redshift Cluster (%s) logging: %w", d.Id(), err) } } @@ -816,11 +818,15 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { Timeout: d.Timeout(schema.TimeoutUpdate), MinTimeout: 10 * time.Second, } - _, err := stateConf.WaitForState() if err != nil { return fmt.Errorf("Error waiting for Redshift Cluster modification (%s): %w", d.Id(), err) } + + _, err = waitClusterRelocationStatusResolved(conn, d.Id()) + if err != nil { + return fmt.Errorf("error waiting for Redshift Cluster Availability Zone Relocation Status to resolve: %w", err) + } } // Availability Zone cannot be changed at the same time as other settings @@ -842,7 +848,6 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { Timeout: d.Timeout(schema.TimeoutUpdate), MinTimeout: 10 * time.Second, } - _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf("Error waiting for Redshift Cluster relocation (%s): %w", d.Id(), err) @@ -1066,9 +1071,9 @@ func flattenRedshiftClusterNodes(apiObjects []*redshift.ClusterNode) []interface func clusterAvailabilityZoneRelocationStatus(cluster *redshift.Cluster) (bool, error) { // AvailabilityZoneRelocation is not returned by the API, and AvailabilityZoneRelocationStatus is not implemented as Const at this time. switch availabilityZoneRelocationStatus := aws.StringValue(cluster.AvailabilityZoneRelocationStatus); availabilityZoneRelocationStatus { - case "enabled", "pending_enabling": + case "enabled": return true, nil - case "disabled", "pending_disabling": + case "disabled": return false, nil default: return false, fmt.Errorf("unexpected AvailabilityZoneRelocationStatus value %q returned by API", availabilityZoneRelocationStatus) diff --git a/internal/service/redshift/enum.go b/internal/service/redshift/enum.go index 9d2a5e24007..4b49fa8c3d1 100644 --- a/internal/service/redshift/enum.go +++ b/internal/service/redshift/enum.go @@ -37,3 +37,24 @@ func clusterType_Values() []string { clusterTypeSingleNode, } } + +const ( + clusterAvailabilityZoneRelocationStatusEnabled = "enabled" + clusterAvailabilityZoneRelocationStatusDisabled = "disabled" + clusterAvailabilityZoneRelocationStatusPendingEnabling = "pending_enabling" + clusterAvailabilityZoneRelocationStatusPendingDisabling = "pending_disabling" +) + +func clusterAvailabilityZoneRelocationStatus_TerminalValues() []string { + return []string{ + clusterAvailabilityZoneRelocationStatusEnabled, + clusterAvailabilityZoneRelocationStatusDisabled, + } +} +func clusterAvailabilityZoneRelocationStatus_PendingValues() []string { + return []string{ + clusterAvailabilityZoneRelocationStatusPendingEnabling, + clusterAvailabilityZoneRelocationStatusPendingDisabling, + } + +} diff --git a/internal/service/redshift/status.go b/internal/service/redshift/status.go index 9709bad6982..63898f2afa6 100644 --- a/internal/service/redshift/status.go +++ b/internal/service/redshift/status.go @@ -22,3 +22,19 @@ func statusCluster(conn *redshift.Redshift, id string) resource.StateRefreshFunc return output, aws.StringValue(output.ClusterStatus), nil } } + +func statusClusterAvailabilityZoneRelocation(conn *redshift.Redshift, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindClusterByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.AvailabilityZoneRelocationStatus), nil + } +} diff --git a/internal/service/redshift/wait.go b/internal/service/redshift/wait.go index 032fd14965a..4ad501f94e2 100644 --- a/internal/service/redshift/wait.go +++ b/internal/service/redshift/wait.go @@ -9,6 +9,8 @@ import ( const ( clusterInvalidClusterStateFaultTimeout = 15 * time.Minute + + clusterRelocationStatusResolvedTimeout = 1 * time.Minute ) func waitClusterDeleted(conn *redshift.Redshift, id string, timeout time.Duration) (*redshift.Cluster, error) { @@ -35,3 +37,20 @@ func waitClusterDeleted(conn *redshift.Redshift, id string, timeout time.Duratio return nil, err } + +func waitClusterRelocationStatusResolved(conn *redshift.Redshift, id string) (*redshift.Cluster, error) { + stateConf := &resource.StateChangeConf{ + Pending: clusterAvailabilityZoneRelocationStatus_PendingValues(), + Target: clusterAvailabilityZoneRelocationStatus_TerminalValues(), + Refresh: statusClusterAvailabilityZoneRelocation(conn, id), + Timeout: clusterRelocationStatusResolvedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*redshift.Cluster); ok { + return output, err + } + + return nil, err +} From 09900dfc18a10bc0eb7ddb6a226c6e26326d49b9 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 11 Mar 2022 17:06:43 -0800 Subject: [PATCH 27/29] Corrections to CHANGELOG --- .changelog/19098.txt | 4 ---- .changelog/20812.txt | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 .changelog/19098.txt create mode 100644 .changelog/20812.txt diff --git a/.changelog/19098.txt b/.changelog/19098.txt deleted file mode 100644 index 478770c4240..00000000000 --- a/.changelog/19098.txt +++ /dev/null @@ -1,4 +0,0 @@ -```release-note:enhancement -internal/service/redshift/cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. -internal/service/redshift/cluster_data_source: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. -``` \ No newline at end of file diff --git a/.changelog/20812.txt b/.changelog/20812.txt new file mode 100644 index 00000000000..dd14775cc9b --- /dev/null +++ b/.changelog/20812.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +``` + +```release-note:enhancement +data_source/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +``` From 37f96597e21aa81db5f80677510ec1144d46ce63 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 11 Mar 2022 17:53:10 -0800 Subject: [PATCH 28/29] Renames `availability_zone_relocation` to `availability_zone_relocation_enabled` for clarity --- .changelog/20812.txt | 4 +-- internal/service/redshift/cluster.go | 21 +++++++-------- .../service/redshift/cluster_data_source.go | 4 +-- .../redshift/cluster_data_source_test.go | 6 ++--- internal/service/redshift/cluster_test.go | 26 +++++++++---------- website/docs/d/redshift_cluster.html.markdown | 2 +- website/docs/r/redshift_cluster.html.markdown | 2 +- 7 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.changelog/20812.txt b/.changelog/20812.txt index dd14775cc9b..b5b09d2a95b 100644 --- a/.changelog/20812.txt +++ b/.changelog/20812.txt @@ -1,7 +1,7 @@ ```release-note:enhancement -resource/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +resource/aws_redshift_cluster: Add `availability_zone_relocation_enabled` and `availability_zone_relocation_status` attributes. ``` ```release-note:enhancement -data_source/aws_redshift_cluster: Add `availability_zone_relocation` and `availability_zone_relocation_status` attributes. +data_source/aws_redshift_cluster: Add `availability_zone_relocation_enabled` and `availability_zone_relocation_status` attributes. ``` diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 1a6784616f5..752d51ec15b 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -61,10 +61,9 @@ func ResourceCluster() *schema.Resource { Optional: true, Computed: true, }, - "availability_zone_relocation": { + "availability_zone_relocation_enabled": { Type: schema.TypeBool, Optional: true, - Default: false, }, "availability_zone_relocation_status": { Type: schema.TypeString, @@ -327,8 +326,8 @@ func ResourceCluster() *schema.Resource { CustomizeDiff: customdiff.All( verify.SetTagsDiff, func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { - if diff.Get("availability_zone_relocation").(bool) && diff.Get("publicly_accessible").(bool) { - return errors.New("availability_zone_relocation can not be true when publicly_accessible is true") + if diff.Get("availability_zone_relocation_enabled").(bool) && diff.Get("publicly_accessible").(bool) { + return errors.New("availability_zone_relocation_enabled can not be true when publicly_accessible is true") } return nil }, @@ -336,12 +335,12 @@ func ResourceCluster() *schema.Resource { if diff.Id() == "" { return nil } - if diff.Get("availability_zone_relocation").(bool) { + if diff.Get("availability_zone_relocation_enabled").(bool) { return nil } o, n := diff.GetChange("availability_zone") if o.(string) != n.(string) { - return fmt.Errorf("cannot change availability_zone if availability_zone_relocation is not true") + return fmt.Errorf("cannot change availability_zone if availability_zone_relocation_enabled is not true") } return nil }, @@ -386,7 +385,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { restoreOpts.AvailabilityZone = aws.String(v.(string)) } - if v, ok := d.GetOk("availability_zone_relocation"); ok { + if v, ok := d.GetOk("availability_zone_relocation_enabled"); ok { restoreOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) } @@ -481,7 +480,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { createOpts.AvailabilityZone = aws.String(v.(string)) } - if v, ok := d.GetOk("availability_zone_relocation"); ok { + if v, ok := d.GetOk("availability_zone_relocation_enabled"); ok { createOpts.AvailabilityZoneRelocation = aws.Bool(v.(bool)) } @@ -597,7 +596,7 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) } - d.Set("availability_zone_relocation", azr) + d.Set("availability_zone_relocation_enabled", azr) d.Set("cluster_identifier", rsc.ClusterIdentifier) if err := d.Set("cluster_nodes", flattenRedshiftClusterNodes(rsc.ClusterNodes)); err != nil { return fmt.Errorf("error setting cluster_nodes: %w", err) @@ -709,8 +708,8 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { requestUpdate = true } - if d.HasChange("availability_zone_relocation") { - req.AvailabilityZoneRelocation = aws.Bool(d.Get("availability_zone_relocation").(bool)) + if d.HasChange("availability_zone_relocation_enabled") { + req.AvailabilityZoneRelocation = aws.Bool(d.Get("availability_zone_relocation_enabled").(bool)) requestUpdate = true } diff --git a/internal/service/redshift/cluster_data_source.go b/internal/service/redshift/cluster_data_source.go index 3a5cd9a6c46..f79727205e7 100644 --- a/internal/service/redshift/cluster_data_source.go +++ b/internal/service/redshift/cluster_data_source.go @@ -37,7 +37,7 @@ func DataSourceCluster() *schema.Resource { Computed: true, }, - "availability_zone_relocation": { + "availability_zone_relocation_enabled": { Type: schema.TypeBool, Computed: true, }, @@ -211,7 +211,7 @@ func dataSourceClusterRead(d *schema.ResourceData, meta interface{}) error { if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) } - d.Set("availability_zone_relocation", azr) + d.Set("availability_zone_relocation_enabled", azr) d.Set("cluster_identifier", rsc.ClusterIdentifier) if len(rsc.ClusterParameterGroups) > 0 { diff --git a/internal/service/redshift/cluster_data_source_test.go b/internal/service/redshift/cluster_data_source_test.go index 9cd473057cb..ae16d732ac6 100644 --- a/internal/service/redshift/cluster_data_source_test.go +++ b/internal/service/redshift/cluster_data_source_test.go @@ -41,7 +41,7 @@ func TestAccRedshiftClusterDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrSet(dataSourceName, "port"), resource.TestCheckResourceAttrSet(dataSourceName, "preferred_maintenance_window"), resource.TestCheckResourceAttrSet(dataSourceName, "publicly_accessible"), - resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation", resourceName, "availability_zone_relocation"), + resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation_enabled", resourceName, "availability_zone_relocation_enabled"), ), }, }, @@ -106,7 +106,7 @@ func TestAccRedshiftClusterDataSource_availabilityZoneRelocationEnabled(t *testi { Config: testAccClusterDataSourceConfig_availabilityZoneRelocationEnabled(rName), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation", resourceName, "availability_zone_relocation"), + resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone_relocation_enabled", resourceName, "availability_zone_relocation_enabled"), ), }, }, @@ -270,7 +270,7 @@ resource "aws_redshift_cluster" "test" { skip_final_snapshot = true publicly_accessible = false - availability_zone_relocation = true + availability_zone_relocation_enabled = true } data "aws_redshift_cluster" "test" { diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index 82d139e9456..86ab5cd9ddf 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -39,7 +39,7 @@ func TestAccRedshiftCluster_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "cluster_type", "single-node"), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "true"), resource.TestMatchResourceAttr(resourceName, "dns_name", regexp.MustCompile(fmt.Sprintf("^%s.*\\.redshift\\..*", rName))), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "false"), ), }, { @@ -476,7 +476,7 @@ func TestAccRedshiftCluster_changeAvailabilityZone(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "true"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), ), }, @@ -508,7 +508,7 @@ func TestAccRedshiftCluster_changeAvailabilityZoneAndSetAvailabilityZoneRelocati Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "false"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), ), }, @@ -517,7 +517,7 @@ func TestAccRedshiftCluster_changeAvailabilityZoneAndSetAvailabilityZoneRelocati Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v2), testAccCheckClusterNotRecreated(&v1, &v2), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "true"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.1"), ), }, @@ -541,13 +541,13 @@ func TestAccRedshiftCluster_changeAvailabilityZone_availabilityZoneRelocationNot Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "false"), resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"), ), }, { Config: testAccClusterConfig_updateAvailabilityZone_availabilityZoneRelocationNotSet(rName, 1), - ExpectError: regexp.MustCompile(`cannot change availability_zone if availability_zone_relocation is not true`), + ExpectError: regexp.MustCompile(`cannot change availability_zone if availability_zone_relocation_enabled is not true`), }, }, }) @@ -628,7 +628,7 @@ func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { Config: testAccClusterConfig_availabilityZoneRelocation(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "true"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "true"), ), }, { @@ -645,7 +645,7 @@ func TestAccRedshiftCluster_availabilityZoneRelocation(t *testing.T) { Config: testAccClusterConfig_availabilityZoneRelocation(rName, false), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation", "false"), + resource.TestCheckResourceAttr(resourceName, "availability_zone_relocation_enabled", "false"), ), }, }, @@ -663,7 +663,7 @@ func TestAccRedshiftCluster_availabilityZoneRelocation_publiclyAccessible(t *tes Steps: []resource.TestStep{ { Config: testAccClusterConfig_availabilityZoneRelocation_publiclyAccessible(rName), - ExpectError: regexp.MustCompile(`availability_zone_relocation can not be true when publicly_accessible is true`), + ExpectError: regexp.MustCompile(`availability_zone_relocation_enabled can not be true when publicly_accessible is true`), }, }, }) @@ -1416,7 +1416,7 @@ resource "aws_redshift_cluster" "test" { skip_final_snapshot = true publicly_accessible = false - availability_zone_relocation = true + availability_zone_relocation_enabled = true availability_zone = data.aws_availability_zones.available.names[%[2]d] } `, rName, regionIndex)) @@ -1437,7 +1437,7 @@ resource "aws_redshift_cluster" "test" { skip_final_snapshot = true publicly_accessible = false - availability_zone_relocation = false + availability_zone_relocation_enabled = false availability_zone = data.aws_availability_zones.available.names[%[2]d] } `, rName, regionIndex)) @@ -1459,7 +1459,7 @@ resource "aws_redshift_cluster" "test" { skip_final_snapshot = true publicly_accessible = false - availability_zone_relocation = %[2]t + availability_zone_relocation_enabled = %[2]t } `, rName, enabled)) } @@ -1478,7 +1478,7 @@ resource "aws_redshift_cluster" "test" { skip_final_snapshot = true publicly_accessible = true - availability_zone_relocation = true + availability_zone_relocation_enabled = true } `, rName)) } diff --git a/website/docs/d/redshift_cluster.html.markdown b/website/docs/d/redshift_cluster.html.markdown index 51b5c57d339..3bcad79afde 100644 --- a/website/docs/d/redshift_cluster.html.markdown +++ b/website/docs/d/redshift_cluster.html.markdown @@ -54,7 +54,7 @@ In addition to all arguments above, the following attributes are exported: * `allow_version_upgrade` - Whether major version upgrades can be applied during maintenance period * `automated_snapshot_retention_period` - The backup retention period * `availability_zone` - The availability zone of the cluster -* `availability_zone_relocation` - Relocation for an Amazon Redshift cluster between Availability Zones. +* `availability_zone_relocation_enabled` - Indicates whether the cluster is able to be relocated to another availability zone. * `availability_zone_relocation_status` - The status of the Availability Zone relocation operation (enabled, disabled, pending_enabling, pending_disabling) * `bucket_name` - The name of the S3 bucket where the log files are to be stored * `cluster_identifier` - The cluster identifier diff --git a/website/docs/r/redshift_cluster.html.markdown b/website/docs/r/redshift_cluster.html.markdown index 2d4478b994c..31fc089ab79 100644 --- a/website/docs/r/redshift_cluster.html.markdown +++ b/website/docs/r/redshift_cluster.html.markdown @@ -46,7 +46,7 @@ The following arguments are supported: * `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster. * `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC). * `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency. -* `availability_zone_relocation` - (Optional) Enable relocation for an Amazon Redshift cluster between Availability Zones. Default is false. Available for use on clusters from the RA3 instance family. +* `availability_zone_relocation_enabled` - (Optional) If true, the cluster can be relocated to another availabity zone, either automatically by AWS or when requested. Default is `false`. Available for use on clusters from the RA3 instance family. * `preferred_maintenance_window` - (Optional) The weekly time range (in UTC) during which automated cluster maintenance can occur. Format: ddd:hh24:mi-ddd:hh24:mi * `cluster_parameter_group_name` - (Optional) The name of the parameter group to be associated with this cluster. From 510056c9b386f70f0d8afd9ba952ce6a53ee5470 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 11 Mar 2022 18:16:14 -0800 Subject: [PATCH 29/29] Removes `availability_zone_relocation_status` since it is redundant with `availability_zone_relocation_enabled` --- .changelog/20812.txt | 4 ++-- internal/service/redshift/cluster.go | 5 ----- internal/service/redshift/cluster_data_source.go | 6 ------ internal/service/redshift/cluster_test.go | 12 ++++++------ website/docs/d/redshift_cluster.html.markdown | 1 - website/docs/r/redshift_cluster.html.markdown | 3 +-- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/.changelog/20812.txt b/.changelog/20812.txt index b5b09d2a95b..f5aefb10a9e 100644 --- a/.changelog/20812.txt +++ b/.changelog/20812.txt @@ -1,7 +1,7 @@ ```release-note:enhancement -resource/aws_redshift_cluster: Add `availability_zone_relocation_enabled` and `availability_zone_relocation_status` attributes. +resource/aws_redshift_cluster: Add `availability_zone_relocation_enabled` attribute and allow `availability_zone` to be changed in-place. ``` ```release-note:enhancement -data_source/aws_redshift_cluster: Add `availability_zone_relocation_enabled` and `availability_zone_relocation_status` attributes. +data_source/aws_redshift_cluster: Add `availability_zone_relocation_enabled` attribute. ``` diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 752d51ec15b..2351d7f71e4 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -65,10 +65,6 @@ func ResourceCluster() *schema.Resource { Type: schema.TypeBool, Optional: true, }, - "availability_zone_relocation_status": { - Type: schema.TypeString, - Computed: true, - }, "cluster_identifier": { Type: schema.TypeString, Required: true, @@ -591,7 +587,6 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", arn) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) - d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) azr, err := clusterAvailabilityZoneRelocationStatus(rsc) if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) diff --git a/internal/service/redshift/cluster_data_source.go b/internal/service/redshift/cluster_data_source.go index f79727205e7..15db32922bf 100644 --- a/internal/service/redshift/cluster_data_source.go +++ b/internal/service/redshift/cluster_data_source.go @@ -42,11 +42,6 @@ func DataSourceCluster() *schema.Resource { Computed: true, }, - "availability_zone_relocation_status": { - Type: schema.TypeString, - Computed: true, - }, - "bucket_name": { Type: schema.TypeString, Computed: true, @@ -206,7 +201,6 @@ func dataSourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) - d.Set("availability_zone_relocation_status", rsc.AvailabilityZoneRelocationStatus) azr, err := clusterAvailabilityZoneRelocationStatus(rsc) if err != nil { return fmt.Errorf("error reading Redshift Cluster (%s): %w", d.Id(), err) diff --git a/internal/service/redshift/cluster_test.go b/internal/service/redshift/cluster_test.go index 86ab5cd9ddf..7758adcd46a 100644 --- a/internal/service/redshift/cluster_test.go +++ b/internal/service/redshift/cluster_test.go @@ -1415,9 +1415,9 @@ resource "aws_redshift_cluster" "test" { allow_version_upgrade = false skip_final_snapshot = true - publicly_accessible = false + publicly_accessible = false availability_zone_relocation_enabled = true - availability_zone = data.aws_availability_zones.available.names[%[2]d] + availability_zone = data.aws_availability_zones.available.names[%[2]d] } `, rName, regionIndex)) } @@ -1436,9 +1436,9 @@ resource "aws_redshift_cluster" "test" { allow_version_upgrade = false skip_final_snapshot = true - publicly_accessible = false + publicly_accessible = false availability_zone_relocation_enabled = false - availability_zone = data.aws_availability_zones.available.names[%[2]d] + availability_zone = data.aws_availability_zones.available.names[%[2]d] } `, rName, regionIndex)) } @@ -1458,7 +1458,7 @@ resource "aws_redshift_cluster" "test" { allow_version_upgrade = false skip_final_snapshot = true - publicly_accessible = false + publicly_accessible = false availability_zone_relocation_enabled = %[2]t } `, rName, enabled)) @@ -1477,7 +1477,7 @@ resource "aws_redshift_cluster" "test" { allow_version_upgrade = false skip_final_snapshot = true - publicly_accessible = true + publicly_accessible = true availability_zone_relocation_enabled = true } `, rName)) diff --git a/website/docs/d/redshift_cluster.html.markdown b/website/docs/d/redshift_cluster.html.markdown index 3bcad79afde..ffd98b3de54 100644 --- a/website/docs/d/redshift_cluster.html.markdown +++ b/website/docs/d/redshift_cluster.html.markdown @@ -55,7 +55,6 @@ In addition to all arguments above, the following attributes are exported: * `automated_snapshot_retention_period` - The backup retention period * `availability_zone` - The availability zone of the cluster * `availability_zone_relocation_enabled` - Indicates whether the cluster is able to be relocated to another availability zone. -* `availability_zone_relocation_status` - The status of the Availability Zone relocation operation (enabled, disabled, pending_enabling, pending_disabling) * `bucket_name` - The name of the S3 bucket where the log files are to be stored * `cluster_identifier` - The cluster identifier * `cluster_parameter_group_name` - The name of the parameter group to be associated with this cluster diff --git a/website/docs/r/redshift_cluster.html.markdown b/website/docs/r/redshift_cluster.html.markdown index 31fc089ab79..c0a1cabebc6 100644 --- a/website/docs/r/redshift_cluster.html.markdown +++ b/website/docs/r/redshift_cluster.html.markdown @@ -45,7 +45,7 @@ The following arguments are supported: * `cluster_security_groups` - (Optional) A list of security groups to be associated with this cluster. * `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster. * `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC). -* `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency. +* `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency. Can only be changed if `availability_zone_relocation_enabled` is `true`. * `availability_zone_relocation_enabled` - (Optional) If true, the cluster can be relocated to another availabity zone, either automatically by AWS or when requested. Default is `false`. Available for use on clusters from the RA3 instance family. * `preferred_maintenance_window` - (Optional) The weekly time range (in UTC) during which automated cluster maintenance can occur. Format: ddd:hh24:mi-ddd:hh24:mi @@ -109,7 +109,6 @@ In addition to all arguments above, the following attributes are exported: * `node_type` - The type of nodes in the cluster * `database_name` - The name of the default database in the Cluster * `availability_zone` - The availability zone of the Cluster -* `availability_zone_relocation_status` - The status of the Availability Zone relocation operation (enabled, disabled, pending_enabling, pending_disabling) * `automated_snapshot_retention_period` - The backup retention period * `preferred_maintenance_window` - The backup window * `endpoint` - The connection endpoint