Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Support tags for AWS redshift cluster #5356

Merged
merged 1 commit into from
May 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions builtin/providers/aws/resource_aws_redshift_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ func resourceAwsRedshiftCluster() *schema.Resource {
Optional: true,
Computed: true,
},

"tags": tagsSchema(),
},
}
}
Expand All @@ -203,6 +205,7 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{})
conn := meta.(*AWSClient).redshiftconn

log.Printf("[INFO] Building Redshift Cluster Options")
tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))
createOpts := &redshift.CreateClusterInput{
ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
Expand All @@ -214,6 +217,7 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{})
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,
}

if v := d.Get("number_of_nodes").(int); v > 1 {
Expand Down Expand Up @@ -357,24 +361,40 @@ func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) er

d.Set("cluster_public_key", rsc.ClusterPublicKey)
d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
d.Set("tags", tagsToMapRedshift(rsc.Tags))

return nil
}

func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
d.Partial(true)

arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region)
if tagErr != nil {
return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id())
} else {
if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil {
return tagErr
} else {
d.SetPartial("tags")
}
}

requestUpdate := false
log.Printf("[INFO] Building Redshift Modify Cluster Options")
req := &redshift.ModifyClusterInput{
ClusterIdentifier: aws.String(d.Id()),
}

if d.HasChange("cluster_type") {
req.ClusterType = aws.String(d.Get("cluster_type").(string))
requestUpdate = true
}

if d.HasChange("node_type") {
req.NodeType = aws.String(d.Get("node_type").(string))
requestUpdate = true
}

if d.HasChange("number_of_nodes") {
Expand All @@ -384,66 +404,81 @@ func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{})
} else {
req.ClusterType = aws.String("single-node")
}

req.NodeType = aws.String(d.Get("node_type").(string))
requestUpdate = true
}

if d.HasChange("cluster_security_groups") {
req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
requestUpdate = true
}

if d.HasChange("vpc_security_group_ips") {
req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List())
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
}

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("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
}
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("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"},
Target: []string{"available"},
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 40 * time.Minute,
MinTimeout: 10 * time.Second,
}
stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"},
Target: []string{"available"},
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 40 * time.Minute,
MinTimeout: 10 * time.Second,
}

// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
}
}

d.Partial(false)

return resourceAwsRedshiftClusterRead(d, meta)
}

Expand Down Expand Up @@ -602,3 +637,12 @@ func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string
}
return
}

func buildRedshiftARN(identifier, accountid, region string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the reduction of the interface to 3 strings! 👍

if accountid == "" {
return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID")
}
arn := fmt.Sprintf("arn:aws:redshift:%s:%s:cluster:%s", region, accountid, identifier)
return arn, nil

}
85 changes: 69 additions & 16 deletions builtin/providers/aws/resource_aws_redshift_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,41 @@ func TestAccAWSRedshiftCluster_updateNodeCount(t *testing.T) {
})
}

func TestAccAWSRedshiftCluster_tags(t *testing.T) {
var v redshift.Cluster

ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
preConfig := fmt.Sprintf(testAccAWSRedshiftClusterConfig_tags, ri)
postConfig := fmt.Sprintf(testAccAWSRedshiftClusterConfig_updatedTags, ri)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftClusterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: preConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v),
resource.TestCheckResourceAttr(
"aws_redshift_cluster.default", "tags.#", "3"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a nitpick, but it would be good to also test the actual tags - e.g. resource.TestCheckResourceAttr("aws_redshift_cluster.default", "tags.environment", "Production")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 seems like an easy improvement

resource.TestCheckResourceAttr("aws_redshift_cluster.default", "tags.environment", "Production"),
),
},

resource.TestStep{
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 testAccCheckAWSRedshiftClusterDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_redshift_cluster" {
Expand Down Expand Up @@ -306,10 +341,6 @@ func TestResourceAWSRedshiftClusterMasterUsernameValidation(t *testing.T) {
}

var testAccAWSRedshiftClusterConfig_updateNodeCount = `
provider "aws" {
region = "us-west-2"
}

resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster-%d"
availability_zone = "us-west-2a"
Expand All @@ -324,10 +355,6 @@ resource "aws_redshift_cluster" "default" {
`

var testAccAWSRedshiftClusterConfig_basic = `
provider "aws" {
region = "us-west-2"
}

resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster-%d"
availability_zone = "us-west-2a"
Expand All @@ -339,11 +366,41 @@ resource "aws_redshift_cluster" "default" {
allow_version_upgrade = false
}`

var testAccAWSRedshiftClusterConfig_notPubliclyAccessible = `
provider "aws" {
region = "us-west-2"
}
var testAccAWSRedshiftClusterConfig_tags = `
resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster-%d"
availability_zone = "us-west-2a"
database_name = "mydb"
master_username = "foo"
master_password = "Mustbe8characters"
node_type = "dc1.large"
automated_snapshot_retention_period = 7
allow_version_upgrade = false

tags {
environment = "Production"
cluster = "reader"
Type = "master"
}
}`

var testAccAWSRedshiftClusterConfig_updatedTags = `
resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster-%d"
availability_zone = "us-west-2a"
database_name = "mydb"
master_username = "foo"
master_password = "Mustbe8characters"
node_type = "dc1.large"
automated_snapshot_retention_period = 7
allow_version_upgrade = false

tags {
environment = "Production"
}
}`

var testAccAWSRedshiftClusterConfig_notPubliclyAccessible = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
Expand Down Expand Up @@ -402,10 +459,6 @@ resource "aws_redshift_cluster" "default" {
}`

var testAccAWSRedshiftClusterConfig_updatePubliclyAccessible = `
provider "aws" {
region = "us-west-2"
}

resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
Expand Down
61 changes: 61 additions & 0 deletions builtin/providers/aws/tagsRedshift.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,71 @@
package aws

import (
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/schema"
)

func setTagsRedshift(conn *redshift.Redshift, d *schema.ResourceData, arn string) error {
if d.HasChange("tags") {
oraw, nraw := d.GetChange("tags")
o := oraw.(map[string]interface{})
n := nraw.(map[string]interface{})
create, remove := diffTagsRedshift(tagsFromMapRedshift(o), tagsFromMapRedshift(n))

// Set tags
if len(remove) > 0 {
log.Printf("[DEBUG] Removing tags: %#v", remove)
k := make([]*string, len(remove), len(remove))
for i, t := range remove {
k[i] = t.Key
}

_, err := conn.DeleteTags(&redshift.DeleteTagsInput{
ResourceName: aws.String(arn),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this API call fail if you passed name instead of ARN?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure actually TBH - looking now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will fail. We need to pass the ARN here

TagKeys: k,
})
if err != nil {
return err
}
}
if len(create) > 0 {
log.Printf("[DEBUG] Creating tags: %#v", create)
_, err := conn.CreateTags(&redshift.CreateTagsInput{
ResourceName: aws.String(arn),
Tags: create,
})
if err != nil {
return err
}
}
}

return nil
}

func diffTagsRedshift(oldTags, newTags []*redshift.Tag) ([]*redshift.Tag, []*redshift.Tag) {
// First, we're creating everything we have
create := make(map[string]interface{})
for _, t := range newTags {
create[*t.Key] = *t.Value
}

// Build the list of what to remove
var remove []*redshift.Tag
for _, t := range oldTags {
old, ok := create[*t.Key]
if !ok || old != *t.Value {
// Delete it!
remove = append(remove, t)
}
}

return tagsFromMapRedshift(create), remove
}

func tagsFromMapRedshift(m map[string]interface{}) []*redshift.Tag {
result := make([]*redshift.Tag, 0, len(m))
for k, v := range m {
Expand Down
Loading