Skip to content

Commit

Permalink
Add name_prefix to RDS resources (#13232)
Browse files Browse the repository at this point in the history
Adds `name_prefix` (or, in some cases, `identifier_prefix`) support to all AWS RDS resources.
  • Loading branch information
joshuaspence authored and stack72 committed Mar 31, 2017
1 parent e7c3e8d commit d25c310
Show file tree
Hide file tree
Showing 23 changed files with 985 additions and 157 deletions.
24 changes: 19 additions & 5 deletions builtin/providers/aws/resource_aws_db_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,19 @@ func resourceAwsDbInstance() *schema.Resource {
},

"identifier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"identifier_prefix"},
ValidateFunc: validateRdsIdentifier,
},
"identifier_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateRdsId,
ValidateFunc: validateRdsIdentifierPrefix,
},

"instance_class": {
Expand Down Expand Up @@ -336,10 +344,16 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
conn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

identifier := d.Get("identifier").(string)
// Generate a unique ID for the user
if identifier == "" {
identifier = resource.PrefixedUniqueId("tf-")
var identifier string
if v, ok := d.GetOk("identifier"); ok {
identifier = v.(string)
} else {
if v, ok := d.GetOk("identifier_prefix"); ok {
identifier = resource.PrefixedUniqueId(v.(string))
} else {
identifier = resource.UniqueId()
}

// SQL Server identifier size is max 15 chars, so truncate
if engine := d.Get("engine").(string); engine != "" {
if strings.Contains(strings.ToLower(engine), "sqlserver") {
Expand Down
79 changes: 76 additions & 3 deletions builtin/providers/aws/resource_aws_db_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,46 @@ func TestAccAWSDBInstance_basic(t *testing.T) {
})
}

func TestAccAWSDBInstance_namePrefix(t *testing.T) {
var v rds.DBInstance

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDBInstanceConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBInstanceExists("aws_db_instance.test", &v),
testAccCheckAWSDBInstanceAttributes(&v),
resource.TestMatchResourceAttr(
"aws_db_instance.test", "identifier", regexp.MustCompile("^tf-test-")),
),
},
},
})
}

func TestAccAWSDBInstance_generatedName(t *testing.T) {
var v rds.DBInstance

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDBInstanceConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBInstanceExists("aws_db_instance.test", &v),
testAccCheckAWSDBInstanceAttributes(&v),
),
},
},
})
}

func TestAccAWSDBInstance_kmsKey(t *testing.T) {
var v rds.DBInstance
keyRegex := regexp.MustCompile("^arn:aws:kms:")
Expand Down Expand Up @@ -613,8 +653,8 @@ resource "aws_db_instance" "bar" {
username = "foo"
# Maintenance Window is stored in lower case in the API, though not strictly
# documented. Terraform will downcase this to match (as opposed to throw a
# Maintenance Window is stored in lower case in the API, though not strictly
# documented. Terraform will downcase this to match (as opposed to throw a
# validation error).
maintenance_window = "Fri:09:00-Fri:09:30"
skip_final_snapshot = true
Expand All @@ -628,6 +668,39 @@ resource "aws_db_instance" "bar" {
}
}`

const testAccAWSDBInstanceConfig_namePrefix = `
resource "aws_db_instance" "test" {
allocated_storage = 10
engine = "MySQL"
identifier_prefix = "tf-test-"
instance_class = "db.t1.micro"
password = "password"
username = "root"
security_group_names = ["default"]
publicly_accessible = true
skip_final_snapshot = true
timeouts {
create = "30m"
}
}`

const testAccAWSDBInstanceConfig_generatedName = `
resource "aws_db_instance" "test" {
allocated_storage = 10
engine = "MySQL"
instance_class = "db.t1.micro"
password = "password"
username = "root"
security_group_names = ["default"]
publicly_accessible = true
skip_final_snapshot = true
timeouts {
create = "30m"
}
}`

var testAccAWSDBInstanceConfigKmsKeyId = `
resource "aws_kms_key" "foo" {
description = "Terraform acc test %s"
Expand Down Expand Up @@ -720,7 +793,7 @@ func testAccReplicaInstanceConfig(val int) string {
parameter_group_name = "default.mysql5.6"
}
resource "aws_db_instance" "replica" {
identifier = "tf-replica-db-%d"
backup_retention_period = 0
Expand Down
55 changes: 24 additions & 31 deletions builtin/providers/aws/resource_aws_db_option_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"log"
"regexp"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -31,10 +30,19 @@ func resourceAwsDbOptionGroup() *schema.Resource {
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateDbOptionGroupName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Required: true,
ValidateFunc: validateDbOptionGroupName,
ValidateFunc: validateDbOptionGroupNamePrefix,
},
"engine_name": &schema.Schema{
Type: schema.TypeString,
Expand All @@ -48,8 +56,9 @@ func resourceAwsDbOptionGroup() *schema.Resource {
},
"option_group_description": &schema.Schema{
Type: schema.TypeString,
Required: true,
Optional: true,
ForceNew: true,
Default: "Managed by Terraform",
},

"option": &schema.Schema{
Expand Down Expand Up @@ -107,11 +116,20 @@ func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) er
rdsconn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

var groupName string
if v, ok := d.GetOk("name"); ok {
groupName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
groupName = resource.PrefixedUniqueId(v.(string))
} else {
groupName = resource.UniqueId()
}

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)),
OptionGroupName: aws.String(groupName),
Tags: tags,
}

Expand All @@ -121,7 +139,7 @@ func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error creating DB Option Group: %s", err)
}

d.SetId(d.Get("name").(string))
d.SetId(groupName)
log.Printf("[INFO] DB Option Group ID: %s", d.Id())

return resourceAwsDbOptionGroupUpdate(d, meta)
Expand Down Expand Up @@ -343,28 +361,3 @@ func buildRDSOptionGroupARN(identifier, partition, accountid, region string) (st
arn := fmt.Sprintf("arn:%s:rds:%s:%s:og:%s", partition, 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
}
124 changes: 88 additions & 36 deletions builtin/providers/aws/resource_aws_db_option_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"fmt"
"regexp"
"testing"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -34,6 +35,66 @@ func TestAccAWSDBOptionGroup_basic(t *testing.T) {
})
}

func TestAccAWSDBOptionGroup_namePrefix(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: testAccAWSDBOptionGroup_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBOptionGroupExists("aws_db_option_group.test", &v),
testAccCheckAWSDBOptionGroupAttributes(&v),
resource.TestMatchResourceAttr(
"aws_db_option_group.test", "name", regexp.MustCompile("^tf-test-")),
),
},
},
})
}

func TestAccAWSDBOptionGroup_generatedName(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: testAccAWSDBOptionGroup_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBOptionGroupExists("aws_db_option_group.test", &v),
testAccCheckAWSDBOptionGroupAttributes(&v),
),
},
},
})
}

func TestAccAWSDBOptionGroup_defaultDescription(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: testAccAWSDBOptionGroup_defaultDescription(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBOptionGroupExists("aws_db_option_group.test", &v),
resource.TestCheckResourceAttr(
"aws_db_option_group.test", "option_group_description", "Managed by Terraform"),
),
},
},
})
}

func TestAccAWSDBOptionGroup_basicDestroyWithInstance(t *testing.T) {
rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5))

Expand Down Expand Up @@ -160,42 +221,6 @@ func testAccCheckAWSDBOptionGroupAttributes(v *rds.OptionGroup) resource.TestChe
}
}

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]
Expand Down Expand Up @@ -387,3 +412,30 @@ resource "aws_db_option_group" "bar" {
}
`, r)
}

const testAccAWSDBOptionGroup_namePrefix = `
resource "aws_db_option_group" "test" {
name_prefix = "tf-test-"
option_group_description = "Test option group for terraform"
engine_name = "mysql"
major_engine_version = "5.6"
}
`

const testAccAWSDBOptionGroup_generatedName = `
resource "aws_db_option_group" "test" {
option_group_description = "Test option group for terraform"
engine_name = "mysql"
major_engine_version = "5.6"
}
`

func testAccAWSDBOptionGroup_defaultDescription(n int) string {
return fmt.Sprintf(`
resource "aws_db_option_group" "test" {
name = "tf-test-%d"
engine_name = "mysql"
major_engine_version = "5.6"
}
`, n)
}
Loading

0 comments on commit d25c310

Please sign in to comment.