From c200ad380b94e46f5d06acdda39080a5ee6c61e0 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 9 Nov 2017 11:40:17 +0000 Subject: [PATCH 1/2] r/redshift_cluster: Add support for snapshot_copy --- aws/resource_aws_redshift_cluster.go | 70 ++++++++++++++++++++++- aws/resource_aws_redshift_cluster_test.go | 65 +++++++++++++++++++++ aws/structure.go | 19 ++++++ 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_redshift_cluster.go b/aws/resource_aws_redshift_cluster.go index a4c1e90f2af7..c652b25ae511 100644 --- a/aws/resource_aws_redshift_cluster.go +++ b/aws/resource_aws_redshift_cluster.go @@ -219,6 +219,29 @@ func resourceAwsRedshiftCluster() *schema.Resource { Set: schema.HashString, }, + "snapshot_copy": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination_region": { + Type: schema.TypeString, + Required: true, + }, + "retention_period": { + Type: schema.TypeInt, + Optional: true, + Default: 7, + }, + "grant_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "logging": { Type: schema.TypeList, MaxItems: 1, @@ -471,6 +494,13 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("[WARN] 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 + } + } + logging, ok := d.GetOk("logging.0.enable") _, deprecatedOk := d.GetOk("enable_logging") if (ok && logging.(bool)) || deprecatedOk { @@ -479,7 +509,6 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", err) return loggingErr } - } return resourceAwsRedshiftClusterRead(d, meta) @@ -586,6 +615,8 @@ func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) er d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) d.Set("tags", tagsToMapRedshift(rsc.Tags)) + d.Set("snapshot_copy", flattenRedshiftSnapshotCopy(rsc.ClusterSnapshotCopyStatus)) + // TODO: Deprecated fields - remove in next major version d.Set("bucket_name", loggingStatus.BucketName) d.Set("enable_logging", loggingStatus.LoggingEnabled) @@ -747,6 +778,22 @@ func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) } } + 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) + } + } + } + deprecatedHasChange := (d.HasChange("enable_logging") || d.HasChange("bucket_name") || d.HasChange("s3_key_prefix")) if d.HasChange("logging") || deprecatedHasChange { var loggingErr error @@ -814,6 +861,27 @@ func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshif 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 log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id()) diff --git a/aws/resource_aws_redshift_cluster_test.go b/aws/resource_aws_redshift_cluster_test.go index 70f2ddda881b..64c6c8800d4a 100644 --- a/aws/resource_aws_redshift_cluster_test.go +++ b/aws/resource_aws_redshift_cluster_test.go @@ -221,6 +221,37 @@ func TestAccAWSRedshiftCluster_loggingEnabled(t *testing.T) { }) } +func TestAccAWSRedshiftCluster_snapshotCopy(t *testing.T) { + var v redshift.Cluster + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRedshiftClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRedshiftClusterConfig_snapshotCopyEnabled(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_redshift_cluster.default", "snapshot_copy.0.destination_region", "us-east-1"), + 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 @@ -907,6 +938,40 @@ EOF }`, rInt, rInt, rInt, rInt) } +func testAccAWSRedshiftClusterConfig_snapshotCopyDisabled(rInt int) string { + return fmt.Sprintf(` + resource "aws_redshift_cluster" "default" { + cluster_identifier = "tf-redshift-cluster-%d" + availability_zone = "us-west-2a" + 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_snapshotCopyEnabled(rInt int) string { + return fmt.Sprintf(` + resource "aws_redshift_cluster" "default" { + cluster_identifier = "tf-redshift-cluster-%d" + availability_zone = "us-west-2a" + database_name = "mydb" + master_username = "foo_test" + master_password = "Mustbe8characters" + node_type = "dc1.large" + automated_snapshot_retention_period = 0 + allow_version_upgrade = false + snapshot_copy { + destination_region = "us-east-1" + retention_period = 1 + } + skip_final_snapshot = true + }`, rInt) +} + func testAccAWSRedshiftClusterConfig_tags(rInt int) string { return fmt.Sprintf(` resource "aws_redshift_cluster" "default" { diff --git a/aws/structure.go b/aws/structure.go index 9dae39917d66..30ac0397dc1b 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -2356,3 +2356,22 @@ func flattenRedshiftLogging(ls *redshift.LoggingStatus) []interface{} { } return []interface{}{cfg} } + +func flattenRedshiftSnapshotCopy(scs *redshift.ClusterSnapshotCopyStatus) []interface{} { + if scs == nil { + return []interface{}{} + } + + cfg := make(map[string]interface{}, 0) + if scs.DestinationRegion != nil { + cfg["destination_region"] = *scs.DestinationRegion + } + if scs.RetentionPeriod != nil { + cfg["retention_period"] = *scs.RetentionPeriod + } + if scs.SnapshotCopyGrantName != nil { + cfg["grant_name"] = *scs.SnapshotCopyGrantName + } + + return []interface{}{cfg} +} From f4286a1aee6c8128e6e9729595fb701411edc0ff Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 9 Nov 2017 16:51:57 +0000 Subject: [PATCH 2/2] docs: Add snapshot_copy --- website/docs/r/redshift_cluster.html.markdown | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/docs/r/redshift_cluster.html.markdown b/website/docs/r/redshift_cluster.html.markdown index 7cad3bde6484..940446aaeb96 100644 --- a/website/docs/r/redshift_cluster.html.markdown +++ b/website/docs/r/redshift_cluster.html.markdown @@ -68,6 +68,7 @@ string. * `owner_account` - (Optional) The AWS customer account used to create or copy the snapshot. Required if you are restoring a snapshot you do not own, optional if you own the snapshot. * `iam_roles` - (Optional) A list of IAM Role ARNs to associate with the cluster. A Maximum of 10 can be associated to the cluster at any time. * `logging` - (Optional) Logging, documented below. +* `snapshot_copy` - (Optional) Configuration of automatic copy of snapshots from one region to another. Documented below. * `tags` - (Optional) A mapping of tags to assign to the resource. ### Nested Blocks @@ -79,6 +80,12 @@ string. For more information on the permissions required for the bucket, please read the AWS [documentation](http://docs.aws.amazon.com/redshift/latest/mgmt/db-auditing.html#db-auditing-enable-logging) * `s3_key_prefix` - (Optional) The prefix applied to the log file names. +#### `snapshot_copy` + +* `destination_region` - (Required) The destination region that you want to copy snapshots to. +* `retention_period` - (Optional) The number of days to retain automated snapshots in the destination region after they are copied from the source region. Defaults to `7`. +* `grant_name` - (Optional) The name of the snapshot copy grant to use when snapshots of an AWS KMS-encrypted cluster are copied to the destination region. + ## Attributes Reference The following attributes are exported: