Add `name_prefix` to RDS resources (#13232)

Adds `name_prefix` (or, in some cases, `identifier_prefix`) support to all AWS RDS resources.
This commit is contained in:
Joshua Spence 2017-04-01 04:22:57 +11:00 committed by Paul Stack
parent e7c3e8df68
commit d25c310468
23 changed files with 985 additions and 157 deletions

View File

@ -101,11 +101,19 @@ func resourceAwsDbInstance() *schema.Resource {
}, },
"identifier": { "identifier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"identifier_prefix"},
ValidateFunc: validateRdsIdentifier,
},
"identifier_prefix": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
ForceNew: true, ForceNew: true,
ValidateFunc: validateRdsId, ValidateFunc: validateRdsIdentifierPrefix,
}, },
"instance_class": { "instance_class": {
@ -336,10 +344,16 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
conn := meta.(*AWSClient).rdsconn conn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))
identifier := d.Get("identifier").(string) var identifier string
// Generate a unique ID for the user if v, ok := d.GetOk("identifier"); ok {
if identifier == "" { identifier = v.(string)
identifier = resource.PrefixedUniqueId("tf-") } 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 // SQL Server identifier size is max 15 chars, so truncate
if engine := d.Get("engine").(string); engine != "" { if engine := d.Get("engine").(string); engine != "" {
if strings.Contains(strings.ToLower(engine), "sqlserver") { if strings.Contains(strings.ToLower(engine), "sqlserver") {

View File

@ -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) { func TestAccAWSDBInstance_kmsKey(t *testing.T) {
var v rds.DBInstance var v rds.DBInstance
keyRegex := regexp.MustCompile("^arn:aws:kms:") keyRegex := regexp.MustCompile("^arn:aws:kms:")
@ -613,8 +653,8 @@ resource "aws_db_instance" "bar" {
username = "foo" username = "foo"
# Maintenance Window is stored in lower case in the API, though not strictly # 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 # documented. Terraform will downcase this to match (as opposed to throw a
# validation error). # validation error).
maintenance_window = "Fri:09:00-Fri:09:30" maintenance_window = "Fri:09:00-Fri:09:30"
skip_final_snapshot = true skip_final_snapshot = true
@ -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 = ` var testAccAWSDBInstanceConfigKmsKeyId = `
resource "aws_kms_key" "foo" { resource "aws_kms_key" "foo" {
description = "Terraform acc test %s" description = "Terraform acc test %s"
@ -720,7 +793,7 @@ func testAccReplicaInstanceConfig(val int) string {
parameter_group_name = "default.mysql5.6" parameter_group_name = "default.mysql5.6"
} }
resource "aws_db_instance" "replica" { resource "aws_db_instance" "replica" {
identifier = "tf-replica-db-%d" identifier = "tf-replica-db-%d"
backup_retention_period = 0 backup_retention_period = 0

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"regexp"
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -31,10 +30,19 @@ func resourceAwsDbOptionGroup() *schema.Resource {
Computed: true, Computed: true,
}, },
"name": &schema.Schema{ "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, Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
Required: true, ValidateFunc: validateDbOptionGroupNamePrefix,
ValidateFunc: validateDbOptionGroupName,
}, },
"engine_name": &schema.Schema{ "engine_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -48,8 +56,9 @@ func resourceAwsDbOptionGroup() *schema.Resource {
}, },
"option_group_description": &schema.Schema{ "option_group_description": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: "Managed by Terraform",
}, },
"option": &schema.Schema{ "option": &schema.Schema{
@ -107,11 +116,20 @@ func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) er
rdsconn := meta.(*AWSClient).rdsconn rdsconn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 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{ createOpts := &rds.CreateOptionGroupInput{
EngineName: aws.String(d.Get("engine_name").(string)), EngineName: aws.String(d.Get("engine_name").(string)),
MajorEngineVersion: aws.String(d.Get("major_engine_version").(string)), MajorEngineVersion: aws.String(d.Get("major_engine_version").(string)),
OptionGroupDescription: aws.String(d.Get("option_group_description").(string)), OptionGroupDescription: aws.String(d.Get("option_group_description").(string)),
OptionGroupName: aws.String(d.Get("name").(string)), OptionGroupName: aws.String(groupName),
Tags: tags, Tags: tags,
} }
@ -121,7 +139,7 @@ func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error creating DB Option Group: %s", err) 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()) log.Printf("[INFO] DB Option Group ID: %s", d.Id())
return resourceAwsDbOptionGroupUpdate(d, meta) return resourceAwsDbOptionGroupUpdate(d, meta)
@ -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) arn := fmt.Sprintf("arn:%s:rds:%s:%s:og:%s", partition, region, accountid, identifier)
return arn, nil 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
}

View File

@ -2,6 +2,7 @@ package aws
import ( import (
"fmt" "fmt"
"regexp"
"testing" "testing"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -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) { func TestAccAWSDBOptionGroup_basicDestroyWithInstance(t *testing.T) {
rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5)) rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5))
@ -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 { func testAccCheckAWSDBOptionGroupExists(n string, v *rds.OptionGroup) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -387,3 +412,30 @@ resource "aws_db_option_group" "bar" {
} }
`, r) `, 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)
}

View File

@ -32,10 +32,19 @@ func resourceAwsDbParameterGroup() *schema.Resource {
Computed: true, Computed: true,
}, },
"name": &schema.Schema{ "name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateDbParamGroupName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
Required: true, ValidateFunc: validateDbParamGroupNamePrefix,
ValidateFunc: validateDbParamGroupName,
}, },
"family": &schema.Schema{ "family": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -81,8 +90,17 @@ func resourceAwsDbParameterGroupCreate(d *schema.ResourceData, meta interface{})
rdsconn := meta.(*AWSClient).rdsconn rdsconn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 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.CreateDBParameterGroupInput{ createOpts := rds.CreateDBParameterGroupInput{
DBParameterGroupName: aws.String(d.Get("name").(string)), DBParameterGroupName: aws.String(groupName),
DBParameterGroupFamily: aws.String(d.Get("family").(string)), DBParameterGroupFamily: aws.String(d.Get("family").(string)),
Description: aws.String(d.Get("description").(string)), Description: aws.String(d.Get("description").(string)),
Tags: tags, Tags: tags,

View File

@ -3,6 +3,7 @@ package aws
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"regexp"
"testing" "testing"
"time" "time"
@ -290,6 +291,44 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) {
}) })
} }
func TestAccAWSDBParameterGroup_namePrefix(t *testing.T) {
var v rds.DBParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBParameterGroupConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBParameterGroupExists("aws_db_parameter_group.test", &v),
resource.TestMatchResourceAttr(
"aws_db_parameter_group.test", "name", regexp.MustCompile("^tf-test-")),
),
},
},
})
}
func TestAccAWSDBParameterGroup_generatedName(t *testing.T) {
var v rds.DBParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBParameterGroupConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBParameterGroupExists("aws_db_parameter_group.test", &v),
),
},
},
})
}
func TestAccAWSDBParameterGroup_withApplyMethod(t *testing.T) { func TestAccAWSDBParameterGroup_withApplyMethod(t *testing.T) {
var v rds.DBParameterGroup var v rds.DBParameterGroup
@ -671,3 +710,16 @@ resource "aws_db_parameter_group" "large" {
parameter { name = "tx_isolation" value = "REPEATABLE-READ" } parameter { name = "tx_isolation" value = "REPEATABLE-READ" }
}`, n) }`, n)
} }
const testAccDBParameterGroupConfig_namePrefix = `
resource "aws_db_parameter_group" "test" {
name_prefix = "tf-test-"
family = "mysql5.6"
}
`
const testAccDBParameterGroupConfig_generatedName = `
resource "aws_db_parameter_group" "test" {
family = "mysql5.6"
}
`

View File

@ -3,7 +3,6 @@ package aws
import ( import (
"fmt" "fmt"
"log" "log"
"regexp"
"strings" "strings"
"time" "time"
@ -31,10 +30,19 @@ func resourceAwsDbSubnetGroup() *schema.Resource {
}, },
"name": &schema.Schema{ "name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateDbSubnetGroupName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
Required: true, ValidateFunc: validateDbSubnetGroupNamePrefix,
ValidateFunc: validateSubnetGroupName,
}, },
"description": &schema.Schema{ "description": &schema.Schema{
@ -65,8 +73,17 @@ func resourceAwsDbSubnetGroupCreate(d *schema.ResourceData, meta interface{}) er
subnetIds[i] = aws.String(subnetId.(string)) subnetIds[i] = aws.String(subnetId.(string))
} }
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.CreateDBSubnetGroupInput{ createOpts := rds.CreateDBSubnetGroupInput{
DBSubnetGroupName: aws.String(d.Get("name").(string)), DBSubnetGroupName: aws.String(groupName),
DBSubnetGroupDescription: aws.String(d.Get("description").(string)), DBSubnetGroupDescription: aws.String(d.Get("description").(string)),
SubnetIds: subnetIds, SubnetIds: subnetIds,
Tags: tags, Tags: tags,
@ -238,20 +255,3 @@ func buildRDSsubgrpARN(identifier, partition, accountid, region string) (string,
return arn, nil return arn, nil
} }
func validateSubnetGroupName(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, periods, and spaces 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
}

View File

@ -2,6 +2,7 @@ package aws
import ( import (
"fmt" "fmt"
"regexp"
"testing" "testing"
"github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/acctest"
@ -43,6 +44,46 @@ func TestAccAWSDBSubnetGroup_basic(t *testing.T) {
}) })
} }
func TestAccAWSDBSubnetGroup_namePrefix(t *testing.T) {
var v rds.DBSubnetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDBSubnetGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBSubnetGroupConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBSubnetGroupExists(
"aws_db_subnet_group.test", &v),
resource.TestMatchResourceAttr(
"aws_db_subnet_group.test", "name", regexp.MustCompile("^tf_test-")),
),
},
},
})
}
func TestAccAWSDBSubnetGroup_generatedName(t *testing.T) {
var v rds.DBSubnetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDBSubnetGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBSubnetGroupConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBSubnetGroupExists(
"aws_db_subnet_group.test", &v),
),
},
},
})
}
// Regression test for https://github.com/hashicorp/terraform/issues/2603 and // Regression test for https://github.com/hashicorp/terraform/issues/2603 and
// https://github.com/hashicorp/terraform/issues/2664 // https://github.com/hashicorp/terraform/issues/2664
func TestAccAWSDBSubnetGroup_withUndocumentedCharacters(t *testing.T) { func TestAccAWSDBSubnetGroup_withUndocumentedCharacters(t *testing.T) {
@ -105,38 +146,6 @@ func TestAccAWSDBSubnetGroup_updateDescription(t *testing.T) {
}) })
} }
func TestResourceAWSDBSubnetGroupNameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting",
ErrCount: 1,
},
{
Value: "testing?",
ErrCount: 1,
},
{
Value: "default",
ErrCount: 1,
},
{
Value: randomString(300),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateSubnetGroupName(tc.Value, "aws_db_subnet_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the DB Subnet Group name to trigger a validation error")
}
}
}
func testAccCheckDBSubnetGroupDestroy(s *terraform.State) error { func testAccCheckDBSubnetGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).rdsconn conn := testAccProvider.Meta().(*AWSClient).rdsconn
@ -263,6 +272,49 @@ resource "aws_db_subnet_group" "foo" {
}`, rName) }`, rName)
} }
const testAccDBSubnetGroupConfig_namePrefix = `
resource "aws_vpc" "test" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
name_prefix = "tf_test-"
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}`
const testAccDBSubnetGroupConfig_generatedName = `
resource "aws_vpc" "test" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}`
const testAccDBSubnetGroupConfig_withUnderscoresAndPeriodsAndSpaces = ` const testAccDBSubnetGroupConfig_withUnderscoresAndPeriodsAndSpaces = `
resource "aws_vpc" "main" { resource "aws_vpc" "main" {
cidr_block = "192.168.0.0/16" cidr_block = "192.168.0.0/16"

View File

@ -36,10 +36,19 @@ func resourceAwsRDSCluster() *schema.Resource {
}, },
"cluster_identifier": { "cluster_identifier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"cluster_identifier_prefix"},
ValidateFunc: validateRdsIdentifier,
},
"cluster_identifier_prefix": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
ValidateFunc: validateRdsId, ValidateFunc: validateRdsIdentifierPrefix,
}, },
"cluster_members": { "cluster_members": {
@ -225,6 +234,19 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error
conn := meta.(*AWSClient).rdsconn conn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))
var identifier string
if v, ok := d.GetOk("cluster_identifier"); ok {
identifier = v.(string)
} else {
if v, ok := d.GetOk("cluster_identifier_prefix"); ok {
identifier = resource.PrefixedUniqueId(v.(string))
} else {
identifier = resource.PrefixedUniqueId("tf-")
}
d.Set("cluster_identifier", identifier)
}
if _, ok := d.GetOk("snapshot_identifier"); ok { if _, ok := d.GetOk("snapshot_identifier"); ok {
opts := rds.RestoreDBClusterFromSnapshotInput{ opts := rds.RestoreDBClusterFromSnapshotInput{
DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),

View File

@ -24,10 +24,19 @@ func resourceAwsRDSClusterInstance() *schema.Resource {
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"identifier": { "identifier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"identifier_prefix"},
ValidateFunc: validateRdsIdentifier,
},
"identifier_prefix": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
ValidateFunc: validateRdsId, ValidateFunc: validateRdsIdentifierPrefix,
}, },
"db_subnet_group_name": { "db_subnet_group_name": {
@ -162,10 +171,14 @@ func resourceAwsRDSClusterInstanceCreate(d *schema.ResourceData, meta interface{
createOpts.DBParameterGroupName = aws.String(attr.(string)) createOpts.DBParameterGroupName = aws.String(attr.(string))
} }
if v := d.Get("identifier").(string); v != "" { if v, ok := d.GetOk("identifier"); ok {
createOpts.DBInstanceIdentifier = aws.String(v) createOpts.DBInstanceIdentifier = aws.String(v.(string))
} else { } else {
createOpts.DBInstanceIdentifier = aws.String(resource.UniqueId()) if v, ok := d.GetOk("identifier_prefix"); ok {
createOpts.DBInstanceIdentifier = aws.String(resource.PrefixedUniqueId(v.(string)))
} else {
createOpts.DBInstanceIdentifier = aws.String(resource.PrefixedUniqueId("tf-"))
}
} }
if attr, ok := d.GetOk("db_subnet_group_name"); ok { if attr, ok := d.GetOk("db_subnet_group_name"); ok {

View File

@ -46,6 +46,48 @@ func TestAccAWSRDSClusterInstance_basic(t *testing.T) {
}) })
} }
func TestAccAWSRDSClusterInstance_namePrefix(t *testing.T) {
var v rds.DBInstance
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSClusterInstanceConfig_namePrefix(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSClusterInstanceExists("aws_rds_cluster_instance.test", &v),
testAccCheckAWSDBClusterInstanceAttributes(&v),
resource.TestMatchResourceAttr(
"aws_rds_cluster_instance.test", "identifier", regexp.MustCompile("^tf-cluster-instance-")),
),
},
},
})
}
func TestAccAWSRDSClusterInstance_generatedName(t *testing.T) {
var v rds.DBInstance
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSClusterInstanceConfig_generatedName(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSClusterInstanceExists("aws_rds_cluster_instance.test", &v),
testAccCheckAWSDBClusterInstanceAttributes(&v),
resource.TestMatchResourceAttr(
"aws_rds_cluster_instance.test", "identifier", regexp.MustCompile("^tf-")),
),
},
},
})
}
func TestAccAWSRDSClusterInstance_kmsKey(t *testing.T) { func TestAccAWSRDSClusterInstance_kmsKey(t *testing.T) {
var v rds.DBInstance var v rds.DBInstance
keyRegex := regexp.MustCompile("^arn:aws:kms:") keyRegex := regexp.MustCompile("^arn:aws:kms:")
@ -256,6 +298,83 @@ resource "aws_db_parameter_group" "bar" {
`, n, n, n) `, n, n, n)
} }
func testAccAWSClusterInstanceConfig_namePrefix(n int) string {
return fmt.Sprintf(`
resource "aws_rds_cluster_instance" "test" {
identifier_prefix = "tf-cluster-instance-"
cluster_identifier = "${aws_rds_cluster.test.id}"
instance_class = "db.r3.large"
}
resource "aws_rds_cluster" "test" {
cluster_identifier = "tf-aurora-cluster-%d"
master_username = "root"
master_password = "password"
db_subnet_group_name = "${aws_db_subnet_group.test.name}"
skip_final_snapshot = true
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.0.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
name = "tf-test-%d"
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}
`, n, n)
}
func testAccAWSClusterInstanceConfig_generatedName(n int) string {
return fmt.Sprintf(`
resource "aws_rds_cluster_instance" "test" {
cluster_identifier = "${aws_rds_cluster.test.id}"
instance_class = "db.r3.large"
}
resource "aws_rds_cluster" "test" {
cluster_identifier = "tf-aurora-cluster-%d"
master_username = "root"
master_password = "password"
db_subnet_group_name = "${aws_db_subnet_group.test.name}"
skip_final_snapshot = true
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.0.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
name = "tf-test-%d"
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}
`, n, n)
}
func testAccAWSClusterInstanceConfigKmsKey(n int) string { func testAccAWSClusterInstanceConfigKmsKey(n int) string {
return fmt.Sprintf(` return fmt.Sprintf(`

View File

@ -29,10 +29,19 @@ func resourceAwsRDSClusterParameterGroup() *schema.Resource {
Computed: true, Computed: true,
}, },
"name": &schema.Schema{ "name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateDbParamGroupName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
Required: true, ValidateFunc: validateDbParamGroupNamePrefix,
ValidateFunc: validateDbParamGroupName,
}, },
"family": &schema.Schema{ "family": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -86,8 +95,17 @@ func resourceAwsRDSClusterParameterGroupCreate(d *schema.ResourceData, meta inte
rdsconn := meta.(*AWSClient).rdsconn rdsconn := meta.(*AWSClient).rdsconn
tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 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.CreateDBClusterParameterGroupInput{ createOpts := rds.CreateDBClusterParameterGroupInput{
DBClusterParameterGroupName: aws.String(d.Get("name").(string)), DBClusterParameterGroupName: aws.String(groupName),
DBParameterGroupFamily: aws.String(d.Get("family").(string)), DBParameterGroupFamily: aws.String(d.Get("family").(string)),
Description: aws.String(d.Get("description").(string)), Description: aws.String(d.Get("description").(string)),
Tags: tags, Tags: tags,

View File

@ -3,6 +3,7 @@ package aws
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
"testing" "testing"
"time" "time"
@ -90,6 +91,44 @@ func TestAccAWSDBClusterParameterGroup_basic(t *testing.T) {
}) })
} }
func TestAccAWSDBClusterParameterGroup_namePrefix(t *testing.T) {
var v rds.DBClusterParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBClusterParameterGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDBClusterParameterGroupConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBClusterParameterGroupExists("aws_rds_cluster_parameter_group.test", &v),
resource.TestMatchResourceAttr(
"aws_rds_cluster_parameter_group.test", "name", regexp.MustCompile("^tf-test-")),
),
},
},
})
}
func TestAccAWSDBClusterParameterGroup_generatedName(t *testing.T) {
var v rds.DBClusterParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBClusterParameterGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDBClusterParameterGroupConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBClusterParameterGroupExists("aws_rds_cluster_parameter_group.test", &v),
),
},
},
})
}
func TestAccAWSDBClusterParameterGroup_disappears(t *testing.T) { func TestAccAWSDBClusterParameterGroup_disappears(t *testing.T) {
var v rds.DBClusterParameterGroup var v rds.DBClusterParameterGroup
@ -365,3 +404,15 @@ func testAccAWSDBClusterParameterGroupOnlyConfig(name string) string {
family = "aurora5.6" family = "aurora5.6"
}`, name) }`, name)
} }
const testAccAWSDBClusterParameterGroupConfig_namePrefix = `
resource "aws_rds_cluster_parameter_group" "test" {
name_prefix = "tf-test-"
family = "aurora5.6"
}
`
const testAccAWSDBClusterParameterGroupConfig_generatedName = `
resource "aws_rds_cluster_parameter_group" "test" {
family = "aurora5.6"
}
`

View File

@ -40,6 +40,46 @@ func TestAccAWSRDSCluster_basic(t *testing.T) {
}) })
} }
func TestAccAWSRDSCluster_namePrefix(t *testing.T) {
var v rds.DBCluster
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSClusterConfig_namePrefix(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSClusterExists("aws_rds_cluster.test", &v),
resource.TestMatchResourceAttr(
"aws_rds_cluster.test", "cluster_identifier", regexp.MustCompile("^tf-test-")),
),
},
},
})
}
func TestAccAWSRDSCluster_generatedName(t *testing.T) {
var v rds.DBCluster
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSClusterConfig_generatedName(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSClusterExists("aws_rds_cluster.test", &v),
resource.TestMatchResourceAttr(
"aws_rds_cluster.test", "cluster_identifier", regexp.MustCompile("^tf-")),
),
},
},
})
}
func TestAccAWSRDSCluster_takeFinalSnapshot(t *testing.T) { func TestAccAWSRDSCluster_takeFinalSnapshot(t *testing.T) {
var v rds.DBCluster var v rds.DBCluster
rInt := acctest.RandInt() rInt := acctest.RandInt()
@ -322,6 +362,71 @@ resource "aws_rds_cluster" "default" {
}`, n) }`, n)
} }
func testAccAWSClusterConfig_namePrefix(n int) string {
return fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
cluster_identifier_prefix = "tf-test-"
master_username = "root"
master_password = "password"
db_subnet_group_name = "${aws_db_subnet_group.test.name}"
skip_final_snapshot = true
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.0.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
name = "tf-test-%d"
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}
`, n)
}
func testAccAWSClusterConfig_generatedName(n int) string {
return fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
master_username = "root"
master_password = "password"
db_subnet_group_name = "${aws_db_subnet_group.test.name}"
skip_final_snapshot = true
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "a" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.0.0/24"
availability_zone = "us-west-2a"
}
resource "aws_subnet" "b" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2b"
}
resource "aws_db_subnet_group" "test" {
name = "tf-test-%d"
subnet_ids = ["${aws_subnet.a.id}", "${aws_subnet.b.id}"]
}
`, n)
}
func testAccAWSClusterConfigWithFinalSnapshot(n int) string { func testAccAWSClusterConfigWithFinalSnapshot(n int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_rds_cluster" "default" { resource "aws_rds_cluster" "default" {

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
func validateRdsId(v interface{}, k string) (ws []string, errors []error) { func validateRdsIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string) value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf( errors = append(errors, fmt.Errorf(
@ -33,6 +33,23 @@ func validateRdsId(v interface{}, k string) (ws []string, errors []error) {
return return
} }
func validateRdsIdentifierPrefix(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 and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
return
}
func validateElastiCacheClusterId(v interface{}, k string) (ws []string, errors []error) { func validateElastiCacheClusterId(v interface{}, k string) (ws []string, errors []error) {
value := v.(string) value := v.(string)
if (len(value) < 1) || (len(value) > 20) { if (len(value) < 1) || (len(value) > 20) {
@ -103,7 +120,27 @@ func validateDbParamGroupName(v interface{}, k string) (ws []string, errors []er
"%q cannot be greater than 255 characters", k)) "%q cannot be greater than 255 characters", k))
} }
return return
}
func validateDbParamGroupNamePrefix(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 and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be greater than 226 characters", k))
}
return
} }
func validateStreamViewType(v interface{}, k string) (ws []string, errors []error) { func validateStreamViewType(v interface{}, k string) (ws []string, errors []error) {
@ -1041,3 +1078,79 @@ func validateApiGatewayUsagePlanQuotaSettings(v map[string]interface{}) (errors
return return
} }
func validateDbSubnetGroupName(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, periods, and spaces 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
}
func validateDbSubnetGroupNamePrefix(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, periods, and spaces allowed in %q", k))
}
if len(value) > 229 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 229 characters", k))
}
return
}
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
}
func validateDbOptionGroupNamePrefix(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 len(value) > 229 {
errors = append(errors, fmt.Errorf(
"%q cannot be greater than 229 characters", k))
}
return
}

View File

@ -1785,3 +1785,131 @@ func TestValidateElbNamePrefix(t *testing.T) {
} }
} }
} }
func TestValidateDbSubnetGroupName(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting",
ErrCount: 1,
},
{
Value: "testing?",
ErrCount: 1,
},
{
Value: "default",
ErrCount: 1,
},
{
Value: randomString(300),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateDbSubnetGroupName(tc.Value, "aws_db_subnet_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the DB Subnet Group name to trigger a validation error")
}
}
}
func TestValidateDbSubnetGroupNamePrefix(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting",
ErrCount: 1,
},
{
Value: "testing?",
ErrCount: 1,
},
{
Value: randomString(230),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateDbSubnetGroupNamePrefix(tc.Value, "aws_db_subnet_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the DB Subnet Group name prefix to trigger a validation error")
}
}
}
func TestValidateDbOptionGroupName(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 TestValidateDbOptionGroupNamePrefix(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "testing123!",
ErrCount: 1,
},
{
Value: "1testing123",
ErrCount: 1,
},
{
Value: "testing--123",
ErrCount: 1,
},
{
Value: randomString(230),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateDbOptionGroupNamePrefix(tc.Value, "aws_db_option_group_name")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the DB Option Group name prefix to trigger a validation error")
}
}
}

View File

@ -55,7 +55,8 @@ The following arguments are supported:
* `allocated_storage` - (Required unless a `snapshot_identifier` or `replicate_source_db` is provided) The allocated storage in gigabytes. * `allocated_storage` - (Required unless a `snapshot_identifier` or `replicate_source_db` is provided) The allocated storage in gigabytes.
* `engine` - (Required unless a `snapshot_identifier` or `replicate_source_db` is provided) The database engine to use. * `engine` - (Required unless a `snapshot_identifier` or `replicate_source_db` is provided) The database engine to use.
* `engine_version` - (Optional) The engine version to use. * `engine_version` - (Optional) The engine version to use.
* `identifier` - (Optional) The name of the RDS instance, if omitted, Terraform will assign a random, unique name * `identifier` - (Optional, Forces new resource) The name of the RDS instance, if omitted, Terraform will assign a random, unique identifier.
* `identifier_prefix` - (Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Conflicts with `identifer`.
* `instance_class` - (Required) The instance type of the RDS instance. * `instance_class` - (Required) The instance type of the RDS instance.
* `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general * `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general
purpose SSD), or "io1" (provisioned IOPS SSD). The default is "io1" if purpose SSD), or "io1" (provisioned IOPS SSD). The default is "io1" if
@ -156,7 +157,7 @@ On Oracle instances the following is exported additionally:
- `create` - (Default `40 minutes`) Used for Creating Instances, Replicas, and - `create` - (Default `40 minutes`) Used for Creating Instances, Replicas, and
restoring from Snapshots restoring from Snapshots
- `update` - (Default `80 minutes`) Used for Database modifications - `update` - (Default `80 minutes`) Used for Database modifications
- `delete` - (Default `40 minutes`) Used for destroying databases. This includes - `delete` - (Default `40 minutes`) Used for destroying databases. This includes
the time required to take snapshots the time required to take snapshots

View File

@ -38,8 +38,9 @@ resource "aws_db_option_group" "bar" {
The following arguments are supported: The following arguments are supported:
* `name` - (Required) The name of the Option group to be created. * `name` - (Optional, Forces new resource) The name of the option group. If omitted, Terraform will assign a random, unique name.
* `option_group_description` - (Required) The description of the option group. * `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `option_group_description` - (Optional) The description of the option group. Defaults to "Managed by Terraform".
* `engine_name` - (Required) Specifies the name of the engine that this option group should be associated with.. * `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. * `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. * `option` - (Optional) A list of Options to apply.
@ -70,4 +71,4 @@ DB Option groups can be imported using the `name`, e.g.
``` ```
$ terraform import aws_db_option_group.bar mysql-option-group $ terraform import aws_db_option_group.bar mysql-option-group
``` ```

View File

@ -31,7 +31,8 @@ resource "aws_db_parameter_group" "default" {
The following arguments are supported: The following arguments are supported:
* `name` - (Required) The name of the DB parameter group. * `name` - (Optional, Forces new resource) The name of the DB parameter group. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `family` - (Required) The family of the DB parameter group. * `family` - (Required) The family of the DB parameter group.
* `description` - (Optional) The description of the DB parameter group. Defaults to "Managed by Terraform". * `description` - (Optional) The description of the DB parameter group. Defaults to "Managed by Terraform".
* `parameter` - (Optional) A list of DB parameters to apply. * `parameter` - (Optional) A list of DB parameters to apply.
@ -58,4 +59,4 @@ DB Parameter groups can be imported using the `name`, e.g.
``` ```
$ terraform import aws_db_parameter_group.rds_pg rds-pg $ terraform import aws_db_parameter_group.rds_pg rds-pg
``` ```

View File

@ -27,7 +27,8 @@ resource "aws_db_subnet_group" "default" {
The following arguments are supported: The following arguments are supported:
* `name` - (Required) The name of the DB subnet group. * `name` - (Optional, Forces new resource) The name of the DB subnet group. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `description` - (Optional) The description of the DB subnet group. Defaults to "Managed by Terraform". * `description` - (Optional) The description of the DB subnet group. Defaults to "Managed by Terraform".
* `subnet_ids` - (Required) A list of VPC subnet IDs. * `subnet_ids` - (Required) A list of VPC subnet IDs.
* `tags` - (Optional) A mapping of tags to assign to the resource. * `tags` - (Optional) A mapping of tags to assign to the resource.
@ -46,4 +47,4 @@ DB Subnet groups can be imported using the `name`, e.g.
``` ```
$ terraform import aws_db_subnet_group.default production-subnet-group $ terraform import aws_db_subnet_group.default production-subnet-group
``` ```

View File

@ -53,8 +53,8 @@ the [AWS official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/Co
The following arguments are supported: The following arguments are supported:
* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case * `cluster_identifier` - (Optional, Forces new resources) The cluster identifier. If omitted, Terraform will assign a random, unique identifier.
string. * `cluster_identifier_prefix` - (Optional, Forces new resource) Creates a unique cluster identifier beginning with the specified prefix. Conflicts with `cluster_identifer`.
* `database_name` - (Optional) The name for your database of up to 8 alpha-numeric * `database_name` - (Optional) The name for your database of up to 8 alpha-numeric
characters. If you do not provide a name, Amazon RDS will not create a characters. If you do not provide a name, Amazon RDS will not create a
database in the DB cluster you are creating database in the DB cluster you are creating

View File

@ -47,8 +47,8 @@ the [AWS official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/Co
The following arguments are supported: The following arguments are supported:
* `identifier` - (Optional) The Instance Identifier. Must be a lower case * `identifier` - (Optional, Forces new resource) The indentifier for the RDS instance, if omitted, Terraform will assign a random, unique identifier.
string. If omitted, a unique identifier will be generated. * `identifier_prefix` - (Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Conflicts with `identifer`.
* `cluster_identifier` - (Required) The identifier of the [`aws_rds_cluster`](/docs/providers/aws/r/rds_cluster.html) in which to launch this instance. * `cluster_identifier` - (Required) The identifier of the [`aws_rds_cluster`](/docs/providers/aws/r/rds_cluster.html) in which to launch this instance.
* `instance_class` - (Required) The instance class to use. For details on CPU * `instance_class` - (Required) The instance class to use. For details on CPU
and memory, see [Scaling Aurora DB Instances][4]. Aurora currently and memory, see [Scaling Aurora DB Instances][4]. Aurora currently

View File

@ -32,9 +32,10 @@ resource "aws_rds_cluster_parameter_group" "default" {
The following arguments are supported: The following arguments are supported:
* `name` - (Required) The name of the DB cluster parameter group. * `name` - (Optional, Forces new resource) The name of the DB cluster parameter group. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `family` - (Required) The family of the DB cluster parameter group. * `family` - (Required) The family of the DB cluster parameter group.
* `description` - (Required) The description of the DB cluster parameter group. * `description` - (Optional) The description of the DB cluster parameter group. Defaults to "Managed by Terraform".
* `parameter` - (Optional) A list of DB parameters to apply. * `parameter` - (Optional) A list of DB parameters to apply.
* `tags` - (Optional) A mapping of tags to assign to the resource. * `tags` - (Optional) A mapping of tags to assign to the resource.
@ -60,4 +61,4 @@ RDS Cluster Parameter Groups can be imported using the `name`, e.g.
``` ```
$ terraform import aws_rds_cluster_parameter_group.cluster_pg production-pg-1 $ terraform import aws_rds_cluster_parameter_group.cluster_pg production-pg-1
``` ```