diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index d4d44ee7cd..df1b773786 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -172,6 +172,7 @@ func Provider() terraform.ResourceProvider { "aws_rds_cluster_instance": resourceAwsRDSClusterInstance(), "aws_redshift_security_group": resourceAwsRedshiftSecurityGroup(), "aws_redshift_parameter_group": resourceAwsRedshiftParameterGroup(), + "aws_redshift_subnet_group": resourceAwsRedshiftSubnetGroup(), "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(), diff --git a/builtin/providers/aws/resource_aws_redshift_parameter_group.go b/builtin/providers/aws/resource_aws_redshift_parameter_group.go index 4bebdd3141..54336d4395 100644 --- a/builtin/providers/aws/resource_aws_redshift_parameter_group.go +++ b/builtin/providers/aws/resource_aws_redshift_parameter_group.go @@ -61,8 +61,6 @@ func resourceAwsRedshiftParameterGroup() *schema.Resource { }, Set: resourceAwsRedshiftParameterHash, }, - - "tags": tagsSchema(), }, } } @@ -74,7 +72,6 @@ func resourceAwsRedshiftParameterGroupCreate(d *schema.ResourceData, meta interf ParameterGroupName: aws.String(d.Get("name").(string)), ParameterGroupFamily: aws.String(d.Get("family").(string)), Description: aws.String(d.Get("description").(string)), - Tags: tagsFromMapRedshift(d.Get("tags").(map[string]interface{})), } log.Printf("[DEBUG] Create Redshift Parameter Group: %#v", createOpts) @@ -103,13 +100,13 @@ func resourceAwsRedshiftParameterGroupRead(d *schema.ResourceData, meta interfac if len(describeResp.ParameterGroups) != 1 || *describeResp.ParameterGroups[0].ParameterGroupName != d.Id() { + d.SetId("") return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.ParameterGroups) } d.Set("name", describeResp.ParameterGroups[0].ParameterGroupName) d.Set("family", describeResp.ParameterGroups[0].ParameterGroupFamily) d.Set("description", describeResp.ParameterGroups[0].Description) - d.Set("tags", tagsToMapRedshift(describeResp.ParameterGroups[0].Tags)) describeParametersOpts := redshift.DescribeClusterParametersInput{ ParameterGroupName: aws.String(d.Id()), diff --git a/builtin/providers/aws/resource_aws_redshift_security_group.go b/builtin/providers/aws/resource_aws_redshift_security_group.go index 0e09eb7c46..e21ccc5df1 100644 --- a/builtin/providers/aws/resource_aws_redshift_security_group.go +++ b/builtin/providers/aws/resource_aws_redshift_security_group.go @@ -62,12 +62,6 @@ func resourceAwsRedshiftSecurityGroup() *schema.Resource { }, Set: resourceAwsRedshiftSecurityGroupIngressHash, }, - - "tags": &schema.Schema{ - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - }, }, } } @@ -80,11 +74,9 @@ func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interfa name := d.Get("name").(string) desc := d.Get("description").(string) - tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{})) sgInput := &redshift.CreateClusterSecurityGroupInput{ ClusterSecurityGroupName: aws.String(name), Description: aws.String(desc), - Tags: tags, } log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc) _, err = conn.CreateClusterSecurityGroup(sgInput) @@ -154,7 +146,6 @@ func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface d.Set("ingress", rules) d.Set("name", *sg.ClusterSecurityGroupName) d.Set("description", *sg.Description) - d.Set("tags", tagsToMapRedshift(sg.Tags)) return nil } diff --git a/builtin/providers/aws/resource_aws_redshift_subnet_group.go b/builtin/providers/aws/resource_aws_redshift_subnet_group.go new file mode 100644 index 0000000000..878cd727ed --- /dev/null +++ b/builtin/providers/aws/resource_aws_redshift_subnet_group.go @@ -0,0 +1,186 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsRedshiftSubnetGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsRedshiftSubnetGroupCreate, + Read: resourceAwsRedshiftSubnetGroupRead, + Update: resourceAwsRedshiftSubnetGroupUpdate, + Delete: resourceAwsRedshiftSubnetGroupDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validateRedshiftSubnetGroupName, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "subnet_ids": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func resourceAwsRedshiftSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + subnetIdsSet := d.Get("subnet_ids").(*schema.Set) + subnetIds := make([]*string, subnetIdsSet.Len()) + for i, subnetId := range subnetIdsSet.List() { + subnetIds[i] = aws.String(subnetId.(string)) + } + + createOpts := redshift.CreateClusterSubnetGroupInput{ + ClusterSubnetGroupName: aws.String(d.Get("name").(string)), + Description: aws.String(d.Get("description").(string)), + SubnetIds: subnetIds, + } + + log.Printf("[DEBUG] Create Redshift Subnet Group: %#v", createOpts) + _, err := conn.CreateClusterSubnetGroup(&createOpts) + if err != nil { + return fmt.Errorf("Error creating Redshift Subnet Group: %s", err) + } + + d.SetId(*createOpts.ClusterSubnetGroupName) + log.Printf("[INFO] Redshift Subnet Group ID: %s", d.Id()) + return resourceAwsRedshiftSubnetGroupRead(d, meta) +} + +func resourceAwsRedshiftSubnetGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + + describeOpts := redshift.DescribeClusterSubnetGroupsInput{ + ClusterSubnetGroupName: aws.String(d.Id()), + } + + describeResp, err := conn.DescribeClusterSubnetGroups(&describeOpts) + if err != nil { + if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "ClusterSubnetGroupNotFoundFault" { + log.Printf("[INFO] Redshift Subnet Group: %s was not found", d.Id()) + d.SetId("") + return nil + } + return err + } + + if len(describeResp.ClusterSubnetGroups) == 0 { + return fmt.Errorf("Unable to find Redshift Subnet Group: %#v", describeResp.ClusterSubnetGroups) + } + + d.Set("name", d.Id()) + d.Set("description", describeResp.ClusterSubnetGroups[0].Description) + d.Set("subnet_ids", subnetIdsToSlice(describeResp.ClusterSubnetGroups[0].Subnets)) + + return nil +} + +func resourceAwsRedshiftSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).redshiftconn + if d.HasChange("subnet_ids") { + _, n := d.GetChange("subnet_ids") + if n == nil { + n = new(schema.Set) + } + ns := n.(*schema.Set) + + var sIds []*string + for _, s := range ns.List() { + sIds = append(sIds, aws.String(s.(string))) + } + + _, err := conn.ModifyClusterSubnetGroup(&redshift.ModifyClusterSubnetGroupInput{ + ClusterSubnetGroupName: aws.String(d.Id()), + SubnetIds: sIds, + }) + + if err != nil { + return err + } + } + + return nil +} + +func resourceAwsRedshiftSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: "destroyed", + Refresh: resourceAwsRedshiftSubnetGroupDeleteRefreshFunc(d, meta), + Timeout: 3 * time.Minute, + MinTimeout: 1 * time.Second, + } + _, err := stateConf.WaitForState() + return err +} + +func resourceAwsRedshiftSubnetGroupDeleteRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + conn := meta.(*AWSClient).redshiftconn + + return func() (interface{}, string, error) { + + deleteOpts := redshift.DeleteClusterSubnetGroupInput{ + ClusterSubnetGroupName: aws.String(d.Id()), + } + + if _, err := conn.DeleteClusterSubnetGroup(&deleteOpts); err != nil { + redshiftErr, ok := err.(awserr.Error) + if !ok { + return d, "error", err + } + + if redshiftErr.Code() != "ClusterSubnetGroupNotFoundFault" { + return d, "error", err + } + } + + return d, "destroyed", nil + } +} + +func subnetIdsToSlice(subnetIds []*redshift.Subnet) []string { + subnetsSlice := make([]string, 0, len(subnetIds)) + for _, s := range subnetIds { + subnetsSlice = append(subnetsSlice, *s.SubnetIdentifier) + } + return subnetsSlice +} + +func validateRedshiftSubnetGroupName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(`^[0-9a-z-_]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only lowercase alphanumeric characters, hyphens, underscores, and periods allowed in %q", k)) + } + if len(value) > 255 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 255 characters", k)) + } + if regexp.MustCompile(`(?i)^default$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q is not allowed as %q", "Default", k)) + } + return +} diff --git a/builtin/providers/aws/resource_aws_redshift_subnet_group_test.go b/builtin/providers/aws/resource_aws_redshift_subnet_group_test.go new file mode 100644 index 0000000000..ba69c4f409 --- /dev/null +++ b/builtin/providers/aws/resource_aws_redshift_subnet_group_test.go @@ -0,0 +1,220 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSRedshiftSubnetGroup_basic(t *testing.T) { + var v redshift.ClusterSubnetGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRedshiftSubnetGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRedshiftSubnetGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v), + resource.TestCheckResourceAttr( + "aws_redshift_subnet_group.foo", "subnet_ids.#", "2"), + ), + }, + }, + }) +} + +func TestAccAWSRedshiftSubnetGroup_updateSubnetIds(t *testing.T) { + var v redshift.ClusterSubnetGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRedshiftSubnetGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRedshiftSubnetGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v), + resource.TestCheckResourceAttr( + "aws_redshift_subnet_group.foo", "subnet_ids.#", "2"), + ), + }, + + resource.TestStep{ + Config: testAccRedshiftSubnetGroupConfig_updateSubnetIds, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v), + resource.TestCheckResourceAttr( + "aws_redshift_subnet_group.foo", "subnet_ids.#", "3"), + ), + }, + }, + }) +} + +func TestResourceAWSRedshiftSubnetGroupName_validation(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "default", + ErrCount: 1, + }, + { + Value: "testing123%%", + ErrCount: 1, + }, + { + Value: "TestingSG", + ErrCount: 1, + }, + { + Value: randomString(256), + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateRedshiftSubnetGroupName(tc.Value, "aws_redshift_subnet_group_name") + + if len(errors) != tc.ErrCount { + t.Fatalf("Expected the Redshift Subnet Group Name to trigger a validation error") + } + } +} + +func testAccCheckRedshiftSubnetGroupDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).redshiftconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_redshift_subnet_group" { + continue + } + + resp, err := conn.DescribeClusterSubnetGroups( + &redshift.DescribeClusterSubnetGroupsInput{ + ClusterSubnetGroupName: aws.String(rs.Primary.ID)}) + if err == nil { + if len(resp.ClusterSubnetGroups) > 0 { + return fmt.Errorf("still exist.") + } + + return nil + } + + redshiftErr, ok := err.(awserr.Error) + if !ok { + return err + } + if redshiftErr.Code() != "ClusterSubnetGroupNotFoundFault" { + return err + } + } + + return nil +} + +func testAccCheckRedshiftSubnetGroupExists(n string, v *redshift.ClusterSubnetGroup) 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 ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).redshiftconn + resp, err := conn.DescribeClusterSubnetGroups( + &redshift.DescribeClusterSubnetGroupsInput{ClusterSubnetGroupName: aws.String(rs.Primary.ID)}) + if err != nil { + return err + } + if len(resp.ClusterSubnetGroups) == 0 { + return fmt.Errorf("ClusterSubnetGroup not found") + } + + *v = *resp.ClusterSubnetGroups[0] + + return nil + } +} + +const testAccRedshiftSubnetGroupConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + availability_zone = "us-west-2a" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-dbsubnet-test-1" + } +} + +resource "aws_subnet" "bar" { + cidr_block = "10.1.2.0/24" + availability_zone = "us-west-2b" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-dbsubnet-test-2" + } +} + +resource "aws_redshift_subnet_group" "foo" { + name = "foo" + description = "foo description" + subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] +} +` + +const testAccRedshiftSubnetGroupConfig_updateSubnetIds = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + availability_zone = "us-west-2a" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-dbsubnet-test-1" + } +} + +resource "aws_subnet" "bar" { + cidr_block = "10.1.2.0/24" + availability_zone = "us-west-2b" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-dbsubnet-test-2" + } +} + +resource "aws_subnet" "foobar" { + cidr_block = "10.1.3.0/24" + availability_zone = "us-west-2c" + vpc_id = "${aws_vpc.foo.id}" + tags { + 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}"] +} +` diff --git a/website/source/docs/providers/aws/r/redshift_parameter_group.html.markdown b/website/source/docs/providers/aws/r/redshift_parameter_group.html.markdown new file mode 100644 index 0000000000..0974ee6f46 --- /dev/null +++ b/website/source/docs/providers/aws/r/redshift_parameter_group.html.markdown @@ -0,0 +1,57 @@ +--- +layout: "aws" +page_title: "AWS: aws_redshift_parameter_group" +sidebar_current: "docs-aws-resource-redshift-parameter-group" +--- + +# aws\_redshift\_parameter\_group + +Provides an Redshift Cluster parameter group resource. + +## Example Usage + +``` +resource "aws_redshift_parameter_group" "bar" { + name = "parameter-group-test-terraform" + family = "redshift-1.0" + description = "Test parameter group for terraform" + parameter { + name = "require_ssl" + value = "true" + } + parameter { + name = "query_group" + value = "example" + } + parameter{ + name = "enable_user_activity_logging" + value = "true" + } + + tags { + Environment = "test" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Redshift parameter group. +* `family` - (Required) The family of the Redshift parameter group. +* `description` - (Required) The description of the Redshift parameter group. +* `parameter` - (Optional) A list of Redshift parameters to apply. + +Parameter blocks support the following: + +* `name` - (Required) The name of the Redshift parameter. +* `value` - (Required) The value of the Redshift parameter. + +You can read more about the parameters that Redshift supports in the [documentation](http://docs.aws.amazon.com/redshift/latest/mgmt/working-with-parameter-groups.html) + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Redshift parameter group name. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 499b2148db..81d2d0f629 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -437,6 +437,10 @@ Redshift Resources