From 8dc123fd94bb386de184b28d3b644f942d057d92 Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 21 Dec 2015 09:15:52 +0000 Subject: [PATCH 1/2] Scaffold the AWS DB Option Group resource Change the AWS DB Instance to now include the DB Option Group param. Adds a test to prove that it works Add acceptance tests for the AWS DB Option Group work. This ensures that Options can be added and updated Documentation for the AWS DB Option resource --- builtin/providers/aws/provider.go | 2 +- .../providers/aws/resource_aws_db_instance.go | 23 ++ .../aws/resource_aws_db_instance_test.go | 48 ++- .../aws/resource_aws_db_option_group.go | 299 ++++++++++++++++++ .../aws/resource_aws_db_option_group_test.go | 256 +++++++++++++++ builtin/providers/aws/structure.go | 72 +++++ .../aws/r/db_option_group.html.markdown | 54 ++++ website/source/layouts/aws.erb | 4 + 8 files changed, 756 insertions(+), 2 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_db_option_group.go create mode 100644 builtin/providers/aws/resource_aws_db_option_group_test.go create mode 100644 website/source/docs/providers/aws/r/db_option_group.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 6e9505c2e..4448f6de7 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -147,6 +147,7 @@ func Provider() terraform.ResourceProvider { "aws_customer_gateway": resourceAwsCustomerGateway(), "aws_db_event_subscription": resourceAwsDbEventSubscription(), "aws_db_instance": resourceAwsDbInstance(), + "aws_db_option_group": resourceAwsDbOptionGroup(), "aws_db_parameter_group": resourceAwsDbParameterGroup(), "aws_db_security_group": resourceAwsDbSecurityGroup(), "aws_db_subnet_group": resourceAwsDbSubnetGroup(), @@ -255,7 +256,6 @@ func Provider() terraform.ResourceProvider { "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), "aws_vpn_gateway": resourceAwsVpnGateway(), }, - ConfigureFunc: providerConfigure, } } diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index f5d664c55..bc8cec612 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -283,6 +283,12 @@ func resourceAwsDbInstance() *schema.Resource { Default: 0, }, + "option_group_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -345,6 +351,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.MonitoringInterval = aws.Int64(int64(attr.(int))) } + if attr, ok := d.GetOk("option_group_name"); ok { + opts.OptionGroupName = aws.String(attr.(string)) + } + log.Printf("[DEBUG] DB Instance Replica create configuration: %#v", opts) _, err := conn.CreateDBInstanceReadReplica(&opts) if err != nil { @@ -546,6 +556,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.MonitoringInterval = aws.Int64(int64(attr.(int))) } + if attr, ok := d.GetOk("option_group_name"); ok { + opts.OptionGroupName = aws.String(attr.(string)) + } + log.Printf("[DEBUG] DB Instance create configuration: %#v", opts) var err error err = resource.Retry(5*time.Minute, func() *resource.RetryError { @@ -638,6 +652,9 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("status", v.DBInstanceStatus) d.Set("storage_encrypted", v.StorageEncrypted) + if v.OptionGroupMemberships != nil { + d.Set("option_group_name", v.OptionGroupMemberships[0].OptionGroupName) + } if v.MonitoringInterval != nil { d.Set("monitoring_interval", v.MonitoringInterval) @@ -874,6 +891,12 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error requestUpdate = true } + if d.HasChange("option_group_name") { + d.SetPartial("option_group_name") + req.OptionGroupName = aws.String(d.Get("option_group_name").(string)) + requestUpdate = true + } + log.Printf("[DEBUG] Send DB Instance Modification request: %#v", requestUpdate) if requestUpdate { log.Printf("[DEBUG] DB Instance Modification request: %#v", req) diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index fcff06f65..7421c8701 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -51,6 +51,27 @@ func TestAccAWSDBInstance_basic(t *testing.T) { }) } +func TestAccAWSDBInstance_optionGroup(t *testing.T) { + var v rds.DBInstance + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSDBInstanceConfigWithOptionGroup, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + testAccCheckAWSDBInstanceAttributes(&v), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "option_group_name", "option-group-test-terraform"), + ), + }, + }, + }) +} + func TestAccAWSDBInstanceReplica(t *testing.T) { var s, r rds.DBInstance @@ -160,7 +181,7 @@ func testAccCheckAWSDBInstanceDestroy(s *terraform.State) error { if !ok { return err } - if newerr.Code() != "InvalidDBInstance.NotFound" { + if newerr.Code() != "DBInstanceNotFound" { return err } } @@ -383,6 +404,31 @@ resource "aws_db_instance" "bar" { parameter_group_name = "default.mysql5.6" }` +var testAccAWSDBInstanceConfigWithOptionGroup = fmt.Sprintf(` + +resource "aws_db_option_group" "bar" { + option_group_name = "option-group-test-terraform" + option_group_description = "Test option group for terraform" + engine_name = "mysql" + major_engine_version = "5.6" +} + +resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + allocated_storage = 10 + engine = "MySQL" + instance_class = "db.m1.small" + name = "baz" + password = "barbarbarbar" + username = "foo" + + backup_retention_period = 0 + + parameter_group_name = "default.mysql5.6" + option_group_name = "${aws_db_option_group.bar.option_group_name}" +}`, acctest.RandInt()) + func testAccReplicaInstanceConfig(val int) string { return fmt.Sprintf(` resource "aws_db_instance" "bar" { diff --git a/builtin/providers/aws/resource_aws_db_option_group.go b/builtin/providers/aws/resource_aws_db_option_group.go new file mode 100644 index 000000000..c40f0a164 --- /dev/null +++ b/builtin/providers/aws/resource_aws_db_option_group.go @@ -0,0 +1,299 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/rds" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsDbOptionGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDbOptionGroupCreate, + Read: resourceAwsDbOptionGroupRead, + Update: resourceAwsDbOptionGroupUpdate, + Delete: resourceAwsDbOptionGroupDelete, + + Schema: map[string]*schema.Schema{ + "arn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validateDbOptionGroupName, + }, + "engine_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "major_engine_version": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "option_group_description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "option": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "option_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "db_security_group_memberships": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "vpc_security_group_memberships": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + Set: resourceAwsDbOptionHash, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) error { + rdsconn := meta.(*AWSClient).rdsconn + tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) + + createOpts := &rds.CreateOptionGroupInput{ + EngineName: aws.String(d.Get("engine_name").(string)), + MajorEngineVersion: aws.String(d.Get("major_engine_version").(string)), + OptionGroupDescription: aws.String(d.Get("option_group_description").(string)), + OptionGroupName: aws.String(d.Get("name").(string)), + Tags: tags, + } + + log.Printf("[DEBUG] Create DB Option Group: %#v", createOpts) + _, err := rdsconn.CreateOptionGroup(createOpts) + if err != nil { + return fmt.Errorf("Error creating DB Option Group: %s", err) + } + + d.SetId(d.Get("name").(string)) + log.Printf("[INFO] DB Option Group ID: %s", d.Id()) + + return resourceAwsDbOptionGroupUpdate(d, meta) +} + +func resourceAwsDbOptionGroupRead(d *schema.ResourceData, meta interface{}) error { + rdsconn := meta.(*AWSClient).rdsconn + params := &rds.DescribeOptionGroupsInput{ + OptionGroupName: aws.String(d.Get("name").(string)), + } + + log.Printf("[DEBUG] Describe DB Option Group: %#v", params) + options, err := rdsconn.DescribeOptionGroups(params) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if "OptionGroupNotFoundFault" == awsErr.Code() { + d.SetId("") + log.Printf("[DEBUG] DB Option Group (%s) not found", d.Get("name").(string)) + return nil + } + } + return fmt.Errorf("Error Describing DB Option Group: %s", err) + } + + var option *rds.OptionGroup + for _, ogl := range options.OptionGroupsList { + if *ogl.OptionGroupName == d.Get("name").(string) { + option = ogl + break + } + } + + if option == nil { + return fmt.Errorf("Unable to find Option Group: %#v", options.OptionGroupsList) + } + + d.Set("major_engine_version", option.MajorEngineVersion) + d.Set("engine_name", option.EngineName) + d.Set("option_group_description", option.OptionGroupDescription) + if len(option.Options) != 0 { + d.Set("option", flattenOptions(option.Options)) + } + + optionGroup := options.OptionGroupsList[0] + arn, err := buildRDSOptionGroupARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region) + if err != nil { + name := "" + if optionGroup.OptionGroupName != nil && *optionGroup.OptionGroupName != "" { + name = *optionGroup.OptionGroupName + } + log.Printf("[DEBUG] Error building ARN for DB Option Group, not setting Tags for Option Group %s", name) + } else { + d.Set("arn", arn) + resp, err := rdsconn.ListTagsForResource(&rds.ListTagsForResourceInput{ + ResourceName: aws.String(arn), + }) + + if err != nil { + log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) + } + + var dt []*rds.Tag + if len(resp.TagList) > 0 { + dt = resp.TagList + } + d.Set("tags", tagsToMapRDS(dt)) + } + + return nil +} + +func resourceAwsDbOptionGroupUpdate(d *schema.ResourceData, meta interface{}) error { + rdsconn := meta.(*AWSClient).rdsconn + if d.HasChange("option") { + o, n := d.GetChange("option") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + addOptions, addErr := expandOptionConfiguration(ns.Difference(os).List()) + if addErr != nil { + return addErr + } + + removeOptions, removeErr := flattenOptionConfigurationNames(os.Difference(ns).List()) + if removeErr != nil { + return removeErr + } + + modifyOpts := &rds.ModifyOptionGroupInput{ + OptionGroupName: aws.String(d.Id()), + ApplyImmediately: aws.Bool(true), + } + + if len(addOptions) > 0 { + modifyOpts.OptionsToInclude = addOptions + } + + if len(removeOptions) > 0 { + modifyOpts.OptionsToRemove = removeOptions + } + + log.Printf("[DEBUG] Modify DB Option Group: %s", modifyOpts) + _, err := rdsconn.ModifyOptionGroup(modifyOpts) + if err != nil { + return fmt.Errorf("Error modifying DB Option Group: %s", err) + } + d.SetPartial("option") + + } + + if arn, err := buildRDSOptionGroupARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region); err == nil { + if err := setTagsRDS(rdsconn, d, arn); err != nil { + return err + } else { + d.SetPartial("tags") + } + } + + return resourceAwsDbOptionGroupRead(d, meta) +} + +func resourceAwsDbOptionGroupDelete(d *schema.ResourceData, meta interface{}) error { + rdsconn := meta.(*AWSClient).rdsconn + + deleteOpts := &rds.DeleteOptionGroupInput{ + OptionGroupName: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Delete DB Option Group: %#v", deleteOpts) + _, err := rdsconn.DeleteOptionGroup(deleteOpts) + if err != nil { + return fmt.Errorf("Error Deleting DB Option Group: %s", err) + } + + return nil +} + +func flattenOptionConfigurationNames(configured []interface{}) ([]*string, error) { + var optionNames []*string + for _, pRaw := range configured { + data := pRaw.(map[string]interface{}) + optionNames = append(optionNames, aws.String(data["option_name"].(string))) + } + + return optionNames, nil +} + +func resourceAwsDbOptionHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["option_name"].(string))) + if _, ok := m["port"]; ok { + buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) + } + + return hashcode.String(buf.String()) +} + +func buildRDSOptionGroupARN(identifier, accountid, region string) (string, error) { + if accountid == "" { + return "", fmt.Errorf("[ERROR] No AccountId found") + } + arn := fmt.Sprintf("arn:aws:rds:%s:%s:og:%s", region, accountid, identifier) + return arn, nil +} + +func validateDbOptionGroupName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(`^[a-z]`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "first character of %q must be a letter", k)) + } + if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only alphanumeric characters and hyphens allowed in %q", k)) + } + if regexp.MustCompile(`--`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot contain two consecutive hyphens", k)) + } + if regexp.MustCompile(`-$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot end with a hyphen", k)) + } + if len(value) > 255 { + errors = append(errors, fmt.Errorf( + "%q cannot be greater than 255 characters", k)) + } + return +} diff --git a/builtin/providers/aws/resource_aws_db_option_group_test.go b/builtin/providers/aws/resource_aws_db_option_group_test.go new file mode 100644 index 000000000..e9611f90b --- /dev/null +++ b/builtin/providers/aws/resource_aws_db_option_group_test.go @@ -0,0 +1,256 @@ +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/rds" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSDBOptionGroup_basic(t *testing.T) { + var v rds.OptionGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBOptionGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSDBOptionGroupBasicConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBOptionGroupExists("aws_db_option_group.bar", &v), + testAccCheckAWSDBOptionGroupAttributes(&v), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "name", "option-group-test-terraform"), + ), + }, + }, + }) +} + +func TestAccAWSDBOptionGroup_sqlServerOptionsUpdate(t *testing.T) { + var v rds.OptionGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBOptionGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSDBOptionGroupSqlServerEEOptions, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBOptionGroupExists("aws_db_option_group.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "name", "option-group-test-terraform"), + ), + }, + + resource.TestStep{ + Config: testAccAWSDBOptionGroupSqlServerEEOptions_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBOptionGroupExists("aws_db_option_group.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "name", "option-group-test-terraform"), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "option.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSDBOptionGroup_multipleOptions(t *testing.T) { + var v rds.OptionGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBOptionGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSDBOptionGroupMultipleOptions, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBOptionGroupExists("aws_db_option_group.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "name", "option-group-test-terraform"), + resource.TestCheckResourceAttr( + "aws_db_option_group.bar", "option.#", "2"), + ), + }, + }, + }) +} + +func testAccCheckAWSDBOptionGroupAttributes(v *rds.OptionGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if *v.EngineName != "mysql" { + return fmt.Errorf("bad engine_name: %#v", *v.EngineName) + } + + if *v.MajorEngineVersion != "5.6" { + return fmt.Errorf("bad major_engine_version: %#v", *v.MajorEngineVersion) + } + + if *v.OptionGroupDescription != "Test option group for terraform" { + return fmt.Errorf("bad option_group_description: %#v", *v.OptionGroupDescription) + } + + return nil + } +} + +func TestResourceAWSDBOptionGroupName_validation(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "testing123!", + ErrCount: 1, + }, + { + Value: "1testing123", + ErrCount: 1, + }, + { + Value: "testing--123", + ErrCount: 1, + }, + { + Value: "testing123-", + ErrCount: 1, + }, + { + Value: randomString(256), + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateDbOptionGroupName(tc.Value, "aws_db_option_group_name") + + if len(errors) != tc.ErrCount { + t.Fatalf("Expected the DB Option Group Name to trigger a validation error") + } + } +} + +func testAccCheckAWSDBOptionGroupExists(n string, v *rds.OptionGroup) 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 DB Option Group Name is set") + } + + conn := testAccProvider.Meta().(*AWSClient).rdsconn + + opts := rds.DescribeOptionGroupsInput{ + OptionGroupName: aws.String(rs.Primary.ID), + } + + resp, err := conn.DescribeOptionGroups(&opts) + + if err != nil { + return err + } + + if len(resp.OptionGroupsList) != 1 || + *resp.OptionGroupsList[0].OptionGroupName != rs.Primary.ID { + return fmt.Errorf("DB Option Group not found") + } + + *v = *resp.OptionGroupsList[0] + + return nil + } +} + +func testAccCheckAWSDBOptionGroupDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).rdsconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_db_option_group" { + continue + } + + resp, err := conn.DescribeOptionGroups( + &rds.DescribeOptionGroupsInput{ + OptionGroupName: aws.String(rs.Primary.ID), + }) + + if err == nil { + if len(resp.OptionGroupsList) != 0 && + *resp.OptionGroupsList[0].OptionGroupName == rs.Primary.ID { + return fmt.Errorf("DB Option Group still exists") + } + } + + // Verify the error + newerr, ok := err.(awserr.Error) + if !ok { + return err + } + if newerr.Code() != "OptionGroupNotFoundFault" { + return err + } + } + + return nil +} + +const testAccAWSDBOptionGroupBasicConfig = ` +resource "aws_db_option_group" "bar" { + name = "option-group-test-terraform" + option_group_description = "Test option group for terraform" + engine_name = "mysql" + major_engine_version = "5.6" +} +` + +const testAccAWSDBOptionGroupSqlServerEEOptions = ` +resource "aws_db_option_group" "bar" { + name = "option-group-test-terraform" + option_group_description = "Test option group for terraform" + engine_name = "sqlserver-ee" + major_engine_version = "11.00" +} +` + +const testAccAWSDBOptionGroupSqlServerEEOptions_update = ` +resource "aws_db_option_group" "bar" { + name = "option-group-test-terraform" + option_group_description = "Test option group for terraform" + engine_name = "sqlserver-ee" + major_engine_version = "11.00" + + option { + option_name = "Mirroring" + } +} +` + +const testAccAWSDBOptionGroupMultipleOptions = ` +resource "aws_db_option_group" "bar" { + name = "option-group-test-terraform" + option_group_description = "Test option group for terraform" + engine_name = "oracle-se" + major_engine_version = "11.2" + + option { + option_name = "STATSPACK" + } + + option { + option_name = "XMLDB" + } +} +` diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 9074c6b0b..abff5a7a3 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -262,6 +262,43 @@ func expandRedshiftParameters(configured []interface{}) ([]*redshift.Parameter, return parameters, nil } +func expandOptionConfiguration(configured []interface{}) ([]*rds.OptionConfiguration, error) { + var option []*rds.OptionConfiguration + + for _, pRaw := range configured { + data := pRaw.(map[string]interface{}) + + o := &rds.OptionConfiguration{ + OptionName: aws.String(data["option_name"].(string)), + } + + if raw, ok := data["port"]; ok { + port := raw.(int) + if port != 0 { + o.Port = aws.Int64(int64(port)) + } + } + + if raw, ok := data["db_security_group_memberships"]; ok { + memberships := expandStringList(raw.(*schema.Set).List()) + if len(memberships) > 0 { + o.DBSecurityGroupMemberships = memberships + } + } + + if raw, ok := data["vpc_security_group_memberships"]; ok { + memberships := expandStringList(raw.(*schema.Set).List()) + if len(memberships) > 0 { + o.VpcSecurityGroupMemberships = memberships + } + } + + option = append(option, o) + } + + return option, nil +} + // Takes the result of flatmap.Expand for an array of parameters and // returns Parameter API compatible objects func expandElastiCacheParameters(configured []interface{}) ([]*elasticache.ParameterNameValue, error) { @@ -506,6 +543,41 @@ func flattenEcsContainerDefinitions(definitions []*ecs.ContainerDefinition) (str return string(byteArray[:n]), nil } +func flattenOptions(list []*rds.Option) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + if i.OptionName != nil { + r := make(map[string]interface{}) + r["option_name"] = strings.ToLower(*i.OptionName) + // Default empty string, guard against nil parameter values + r["port"] = "" + if i.Port != nil { + r["port"] = int(*i.Port) + } + if i.VpcSecurityGroupMemberships != nil { + vpcs := make([]string, 0, len(i.VpcSecurityGroupMemberships)) + for _, vpc := range i.VpcSecurityGroupMemberships { + id := vpc.VpcSecurityGroupId + vpcs = append(vpcs, *id) + } + + r["vpc_security_group_memberships"] = vpcs + } + if i.DBSecurityGroupMemberships != nil { + dbs := make([]string, 0, len(i.DBSecurityGroupMemberships)) + for _, db := range i.DBSecurityGroupMemberships { + id := db.DBSecurityGroupName + dbs = append(dbs, *id) + } + + r["db_security_group_memberships"] = dbs + } + result = append(result, r) + } + } + return result +} + // Flattens an array of Parameters into a []map[string]interface{} func flattenParameters(list []*rds.Parameter) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) diff --git a/website/source/docs/providers/aws/r/db_option_group.html.markdown b/website/source/docs/providers/aws/r/db_option_group.html.markdown new file mode 100644 index 000000000..1d78f2fbd --- /dev/null +++ b/website/source/docs/providers/aws/r/db_option_group.html.markdown @@ -0,0 +1,54 @@ +--- +layout: "aws" +page_title: "AWS: aws_db_option_group" +sidebar_current: "docs-aws-resource-db-option-group" +--- + +# aws\_db\_option\_group + +Provides an RDS DB option group resource. + +## Example Usage + +``` +resource "aws_db_option_group" "bar" { + option_group_name = "option-group-test-terraform" + option_group_description = "Terraform Option Group" + engine_name = "sqlserver-ee" + major_engine_version = "11.00" + + option { + option_name = "mirroring" + } + + option { + option_name = "TDE" + } + + apply_immediately = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `option_group_name` - (Required) The name of the Option group to be created. +* `option_group_description` - (Required) The description of the option group. +* `engine_name` - (Required) Specifies the name of the engine that this option group should be associated with.. +* `major_engine_version` - (Required) Specifies the major version of the engine that this option group should be associated with. +* `option` - (Optional) A list of Options to apply. +* `tags` - (Optional) A mapping of tags to assign to the resource. + +Option blocks support the following: + +* `option_name` - (Required) The Name of the Option (e.g. MEMCACHED). +* `port` - (Optional) The Port number when connecting to the Option (e.g. 11211). +* `db_security_group_memberships` - (Optional) A list of DB Security Groups for which the option is enabled. +* `vpc_security_group_memberships` - (Optional) A list of VPC Security Groups for which the option is enabled. + +## Attributes Reference + +The following attributes are exported: + +* `arn` - The ARN of the db option group. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 7aa965519..49b4f638f 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -543,6 +543,10 @@ aws_db_event_subscription + > + aws_db_option_group + + > aws_db_parameter_group From 526aadd0ea31c337f3b84eb4d4b2c15a8888f753 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Mon, 9 May 2016 00:33:48 +0100 Subject: [PATCH 2/2] Update resource_aws_db_option_group.go --- builtin/providers/aws/resource_aws_db_option_group.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_option_group.go b/builtin/providers/aws/resource_aws_db_option_group.go index c40f0a164..202542f40 100644 --- a/builtin/providers/aws/resource_aws_db_option_group.go +++ b/builtin/providers/aws/resource_aws_db_option_group.go @@ -267,7 +267,7 @@ func resourceAwsDbOptionHash(v interface{}) int { func buildRDSOptionGroupARN(identifier, accountid, region string) (string, error) { if accountid == "" { - return "", fmt.Errorf("[ERROR] No AccountId found") + return "", fmt.Errorf("Unable to construct RDS Option Group ARN because of missing AWS Account ID") } arn := fmt.Sprintf("arn:aws:rds:%s:%s:og:%s", region, accountid, identifier) return arn, nil