diff --git a/builtin/providers/aws/resource_aws_redshift_cluster.go b/builtin/providers/aws/resource_aws_redshift_cluster.go index 8af4f3fdf..7f056aa00 100644 --- a/builtin/providers/aws/resource_aws_redshift_cluster.go +++ b/builtin/providers/aws/resource_aws_redshift_cluster.go @@ -196,6 +196,14 @@ func resourceAwsRedshiftCluster() *schema.Resource { Computed: true, }, + "iam_roles": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "tags": tagsSchema(), }, } @@ -263,6 +271,10 @@ func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) createOpts.ElasticIp = aws.String(v.(string)) } + if v, ok := d.GetOk("iam_roles"); ok { + createOpts.IamRoles = expandStringList(v.(*schema.Set).List()) + } + log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) resp, err := conn.CreateCluster(createOpts) if err != nil { @@ -359,6 +371,14 @@ func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err) } + var iamRoles []string + for _, i := range rsc.IamRoles { + iamRoles = append(iamRoles, *i.IamRoleArn) + } + if err := d.Set("iam_roles", iamRoles); err != nil { + return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err) + } + d.Set("cluster_public_key", rsc.ClusterPublicKey) d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) d.Set("tags", tagsToMapRedshift(rsc.Tags)) @@ -461,6 +481,41 @@ func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) if err != nil { return fmt.Errorf("[WARN] 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).List() + addIams := ns.Difference(os).List() + + log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") + req := &redshift.ModifyClusterIamRolesInput{ + ClusterIdentifier: aws.String(d.Id()), + AddIamRoles: expandStringList(addIams), + RemoveIamRoles: expandStringList(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("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) + } + + d.SetPartial("iam_roles") + } + + if requestUpdate || d.HasChange("iam_roles") { stateConf := &resource.StateChangeConf{ Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"}, @@ -471,7 +526,7 @@ func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) } // Wait, catching any errors - _, err = stateConf.WaitForState() + _, err := stateConf.WaitForState() if err != nil { return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err) } diff --git a/builtin/providers/aws/resource_aws_redshift_cluster_test.go b/builtin/providers/aws/resource_aws_redshift_cluster_test.go index c0c435b8f..307f19a0b 100644 --- a/builtin/providers/aws/resource_aws_redshift_cluster_test.go +++ b/builtin/providers/aws/resource_aws_redshift_cluster_test.go @@ -38,6 +38,39 @@ func TestAccAWSRedshiftCluster_basic(t *testing.T) { }) } +func TestAccAWSRedshiftCluster_iamRoles(t *testing.T) { + var v redshift.Cluster + + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + preConfig := fmt.Sprintf(testAccAWSRedshiftClusterConfig_iamRoles, ri, ri, ri) + postConfig := fmt.Sprintf(testAccAWSRedshiftClusterConfig_updateIamRoles, ri, ri, 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", "iam_roles.#", "2"), + ), + }, + + resource.TestStep{ + 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 @@ -376,7 +409,6 @@ resource "aws_redshift_cluster" "default" { node_type = "dc1.large" automated_snapshot_retention_period = 7 allow_version_upgrade = false - tags { environment = "Production" cluster = "reader" @@ -394,7 +426,6 @@ resource "aws_redshift_cluster" "default" { node_type = "dc1.large" automated_snapshot_retention_period = 7 allow_version_upgrade = false - tags { environment = "Production" } @@ -404,14 +435,12 @@ var testAccAWSRedshiftClusterConfig_notPubliclyAccessible = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" } - resource "aws_internet_gateway" "foo" { vpc_id = "${aws_vpc.foo.id}" tags { foo = "bar" } } - resource "aws_subnet" "foo" { cidr_block = "10.1.1.0/24" availability_zone = "us-west-2a" @@ -420,7 +449,6 @@ resource "aws_subnet" "foo" { Name = "tf-dbsubnet-test-1" } } - resource "aws_subnet" "bar" { cidr_block = "10.1.2.0/24" availability_zone = "us-west-2b" @@ -429,7 +457,6 @@ resource "aws_subnet" "bar" { Name = "tf-dbsubnet-test-2" } } - resource "aws_subnet" "foobar" { cidr_block = "10.1.3.0/24" availability_zone = "us-west-2c" @@ -438,13 +465,11 @@ resource "aws_subnet" "foobar" { Name = "tf-dbsubnet-test-3" } } - resource "aws_redshift_subnet_group" "foo" { name = "foo" description = "foo description" subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}", "${aws_subnet.foobar.id}"] } - resource "aws_redshift_cluster" "default" { cluster_identifier = "tf-redshift-cluster-%d" availability_zone = "us-west-2a" @@ -462,14 +487,12 @@ var testAccAWSRedshiftClusterConfig_updatePubliclyAccessible = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" } - resource "aws_internet_gateway" "foo" { vpc_id = "${aws_vpc.foo.id}" tags { foo = "bar" } } - resource "aws_subnet" "foo" { cidr_block = "10.1.1.0/24" availability_zone = "us-west-2a" @@ -478,7 +501,6 @@ resource "aws_subnet" "foo" { Name = "tf-dbsubnet-test-1" } } - resource "aws_subnet" "bar" { cidr_block = "10.1.2.0/24" availability_zone = "us-west-2b" @@ -487,7 +509,6 @@ resource "aws_subnet" "bar" { Name = "tf-dbsubnet-test-2" } } - resource "aws_subnet" "foobar" { cidr_block = "10.1.3.0/24" availability_zone = "us-west-2c" @@ -496,13 +517,11 @@ resource "aws_subnet" "foobar" { Name = "tf-dbsubnet-test-3" } } - resource "aws_redshift_subnet_group" "foo" { name = "foo" description = "foo description" subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}", "${aws_subnet.foobar.id}"] } - resource "aws_redshift_cluster" "default" { cluster_identifier = "tf-redshift-cluster-%d" availability_zone = "us-west-2a" @@ -515,3 +534,53 @@ resource "aws_redshift_cluster" "default" { cluster_subnet_group_name = "${aws_redshift_subnet_group.foo.name}" publicly_accessible = true }` + +var testAccAWSRedshiftClusterConfig_iamRoles = ` +resource "aws_iam_role" "ec2-role" { + name = "test-role-ec2-%d" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" +} + +resource "aws_iam_role" "lambda-role" { + name = "test-role-lambda-%d" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"lambda.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" +} + +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 + iam_roles = ["${aws_iam_role.ec2-role.arn}", "${aws_iam_role.lambda-role.arn}"] +}` + +var testAccAWSRedshiftClusterConfig_updateIamRoles = ` +resource "aws_iam_role" "ec2-role" { + name = "test-role-ec2-%d" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" + } + + resource "aws_iam_role" "lambda-role" { + name = "test-role-lambda-%d" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"lambda.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" + } + + 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 + iam_roles = ["${aws_iam_role.ec2-role.arn}"] + }` diff --git a/website/source/docs/providers/aws/r/redshift_cluster.html.markdown b/website/source/docs/providers/aws/r/redshift_cluster.html.markdown index 983f5cb67..fd57c192d 100644 --- a/website/source/docs/providers/aws/r/redshift_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/redshift_cluster.html.markdown @@ -56,8 +56,10 @@ string. * `elastic_ip` - (Optional) The Elastic IP (EIP) address for the cluster. * `skip_final_snapshot` - (Optional) Determines whether a final snapshot of the cluster is created before Amazon Redshift deletes the cluster. If true , a final cluster snapshot is not created. If false , a final cluster snapshot is created before the cluster is deleted. Default is true. * `final_snapshot_identifier` - (Optional) The identifier of the final snapshot that is to be created immediately before deleting the cluster. If this parameter is provided, `skip_final_snapshot` must be false. +* `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. * `tags` - (Optional) A mapping of tags to assign to the resource. + ## Attributes Reference The following attributes are exported: