From d25c3104681c18044895b47ed44a87dc71d1e34e Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Sat, 1 Apr 2017 04:22:57 +1100 Subject: [PATCH] Add `name_prefix` to RDS resources (#13232) Adds `name_prefix` (or, in some cases, `identifier_prefix`) support to all AWS RDS resources. --- .../providers/aws/resource_aws_db_instance.go | 24 +++- .../aws/resource_aws_db_instance_test.go | 79 ++++++++++- .../aws/resource_aws_db_option_group.go | 55 ++++---- .../aws/resource_aws_db_option_group_test.go | 124 ++++++++++++----- .../aws/resource_aws_db_parameter_group.go | 24 +++- .../resource_aws_db_parameter_group_test.go | 52 +++++++ .../aws/resource_aws_db_subnet_group.go | 42 +++--- .../aws/resource_aws_db_subnet_group_test.go | 116 +++++++++++----- .../providers/aws/resource_aws_rds_cluster.go | 26 +++- .../aws/resource_aws_rds_cluster_instance.go | 21 ++- .../resource_aws_rds_cluster_instance_test.go | 119 ++++++++++++++++ ...esource_aws_rds_cluster_parameter_group.go | 24 +++- ...ce_aws_rds_cluster_parameter_group_test.go | 51 +++++++ .../aws/resource_aws_rds_cluster_test.go | 105 ++++++++++++++ builtin/providers/aws/validators.go | 115 +++++++++++++++- builtin/providers/aws/validators_test.go | 128 ++++++++++++++++++ .../providers/aws/r/db_instance.html.markdown | 5 +- .../aws/r/db_option_group.html.markdown | 7 +- .../aws/r/db_parameter_group.html.markdown | 5 +- .../aws/r/db_subnet_group.html.markdown | 5 +- .../providers/aws/r/rds_cluster.html.markdown | 4 +- .../aws/r/rds_cluster_instance.html.markdown | 4 +- .../r/rds_cluster_parameter_group.markdown | 7 +- 23 files changed, 985 insertions(+), 157 deletions(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 192ccab97..00409230d 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -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": { @@ -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") { diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index 56f890532..6c8d451ca 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -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:") @@ -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 @@ -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" @@ -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 diff --git a/builtin/providers/aws/resource_aws_db_option_group.go b/builtin/providers/aws/resource_aws_db_option_group.go index 5c68e7bd3..e8ad1ac99 100644 --- a/builtin/providers/aws/resource_aws_db_option_group.go +++ b/builtin/providers/aws/resource_aws_db_option_group.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "log" - "regexp" "time" "github.com/aws/aws-sdk-go/aws" @@ -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, @@ -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{ @@ -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, } @@ -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) @@ -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 -} diff --git a/builtin/providers/aws/resource_aws_db_option_group_test.go b/builtin/providers/aws/resource_aws_db_option_group_test.go index 5a3215b04..3ee5f8197 100644 --- a/builtin/providers/aws/resource_aws_db_option_group_test.go +++ b/builtin/providers/aws/resource_aws_db_option_group_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "testing" "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) { 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 { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -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) +} diff --git a/builtin/providers/aws/resource_aws_db_parameter_group.go b/builtin/providers/aws/resource_aws_db_parameter_group.go index b18282712..d5e943fd6 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group.go @@ -32,10 +32,19 @@ func resourceAwsDbParameterGroup() *schema.Resource { Computed: true, }, "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, + Optional: true, + Computed: true, ForceNew: true, - Required: true, - ValidateFunc: validateDbParamGroupName, + ValidateFunc: validateDbParamGroupNamePrefix, }, "family": &schema.Schema{ Type: schema.TypeString, @@ -81,8 +90,17 @@ func resourceAwsDbParameterGroupCreate(d *schema.ResourceData, meta interface{}) 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.CreateDBParameterGroupInput{ - DBParameterGroupName: aws.String(d.Get("name").(string)), + DBParameterGroupName: aws.String(groupName), DBParameterGroupFamily: aws.String(d.Get("family").(string)), Description: aws.String(d.Get("description").(string)), Tags: tags, diff --git a/builtin/providers/aws/resource_aws_db_parameter_group_test.go b/builtin/providers/aws/resource_aws_db_parameter_group_test.go index 75db4f77d..b8e4e56c4 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group_test.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "math/rand" + "regexp" "testing" "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) { var v rds.DBParameterGroup @@ -671,3 +710,16 @@ resource "aws_db_parameter_group" "large" { parameter { name = "tx_isolation" value = "REPEATABLE-READ" } }`, 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" +} +` diff --git a/builtin/providers/aws/resource_aws_db_subnet_group.go b/builtin/providers/aws/resource_aws_db_subnet_group.go index 9c1c56199..c4e437bee 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group.go @@ -3,7 +3,6 @@ package aws import ( "fmt" "log" - "regexp" "strings" "time" @@ -31,10 +30,19 @@ func resourceAwsDbSubnetGroup() *schema.Resource { }, "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, + Optional: true, + Computed: true, ForceNew: true, - Required: true, - ValidateFunc: validateSubnetGroupName, + ValidateFunc: validateDbSubnetGroupNamePrefix, }, "description": &schema.Schema{ @@ -65,8 +73,17 @@ func resourceAwsDbSubnetGroupCreate(d *schema.ResourceData, meta interface{}) er 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{ - DBSubnetGroupName: aws.String(d.Get("name").(string)), + DBSubnetGroupName: aws.String(groupName), DBSubnetGroupDescription: aws.String(d.Get("description").(string)), SubnetIds: subnetIds, Tags: tags, @@ -238,20 +255,3 @@ func buildRDSsubgrpARN(identifier, partition, accountid, region string) (string, 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 -} diff --git a/builtin/providers/aws/resource_aws_db_subnet_group_test.go b/builtin/providers/aws/resource_aws_db_subnet_group_test.go index 434ae1728..70d27c5db 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group_test.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "testing" "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 // https://github.com/hashicorp/terraform/issues/2664 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 { conn := testAccProvider.Meta().(*AWSClient).rdsconn @@ -263,6 +272,49 @@ resource "aws_db_subnet_group" "foo" { }`, 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 = ` resource "aws_vpc" "main" { cidr_block = "192.168.0.0/16" diff --git a/builtin/providers/aws/resource_aws_rds_cluster.go b/builtin/providers/aws/resource_aws_rds_cluster.go index 7461b880c..c33129654 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster.go +++ b/builtin/providers/aws/resource_aws_rds_cluster.go @@ -36,10 +36,19 @@ func resourceAwsRDSCluster() *schema.Resource { }, "cluster_identifier": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"cluster_identifier_prefix"}, + ValidateFunc: validateRdsIdentifier, + }, + "cluster_identifier_prefix": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, - ValidateFunc: validateRdsId, + ValidateFunc: validateRdsIdentifierPrefix, }, "cluster_members": { @@ -225,6 +234,19 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error conn := meta.(*AWSClient).rdsconn 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 { opts := rds.RestoreDBClusterFromSnapshotInput{ DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), diff --git a/builtin/providers/aws/resource_aws_rds_cluster_instance.go b/builtin/providers/aws/resource_aws_rds_cluster_instance.go index 36f111628..2caca4573 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_instance.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_instance.go @@ -24,10 +24,19 @@ func resourceAwsRDSClusterInstance() *schema.Resource { Schema: map[string]*schema.Schema{ "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, }, "db_subnet_group_name": { @@ -162,10 +171,14 @@ func resourceAwsRDSClusterInstanceCreate(d *schema.ResourceData, meta interface{ createOpts.DBParameterGroupName = aws.String(attr.(string)) } - if v := d.Get("identifier").(string); v != "" { - createOpts.DBInstanceIdentifier = aws.String(v) + if v, ok := d.GetOk("identifier"); ok { + createOpts.DBInstanceIdentifier = aws.String(v.(string)) } 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 { diff --git a/builtin/providers/aws/resource_aws_rds_cluster_instance_test.go b/builtin/providers/aws/resource_aws_rds_cluster_instance_test.go index b1e66ea7e..df1a5d644 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_instance_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_instance_test.go @@ -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) { var v rds.DBInstance keyRegex := regexp.MustCompile("^arn:aws:kms:") @@ -256,6 +298,83 @@ resource "aws_db_parameter_group" "bar" { `, 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 { return fmt.Sprintf(` diff --git a/builtin/providers/aws/resource_aws_rds_cluster_parameter_group.go b/builtin/providers/aws/resource_aws_rds_cluster_parameter_group.go index 62b0d497b..61cb20f01 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_parameter_group.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_parameter_group.go @@ -29,10 +29,19 @@ func resourceAwsRDSClusterParameterGroup() *schema.Resource { Computed: true, }, "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, + Optional: true, + Computed: true, ForceNew: true, - Required: true, - ValidateFunc: validateDbParamGroupName, + ValidateFunc: validateDbParamGroupNamePrefix, }, "family": &schema.Schema{ Type: schema.TypeString, @@ -86,8 +95,17 @@ func resourceAwsRDSClusterParameterGroupCreate(d *schema.ResourceData, meta inte 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.CreateDBClusterParameterGroupInput{ - DBClusterParameterGroupName: aws.String(d.Get("name").(string)), + DBClusterParameterGroupName: aws.String(groupName), DBParameterGroupFamily: aws.String(d.Get("family").(string)), Description: aws.String(d.Get("description").(string)), Tags: tags, diff --git a/builtin/providers/aws/resource_aws_rds_cluster_parameter_group_test.go b/builtin/providers/aws/resource_aws_rds_cluster_parameter_group_test.go index f2a526d2e..231fdf44c 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_parameter_group_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_parameter_group_test.go @@ -3,6 +3,7 @@ package aws import ( "errors" "fmt" + "regexp" "testing" "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) { var v rds.DBClusterParameterGroup @@ -365,3 +404,15 @@ func testAccAWSDBClusterParameterGroupOnlyConfig(name string) string { family = "aurora5.6" }`, 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" +} +` diff --git a/builtin/providers/aws/resource_aws_rds_cluster_test.go b/builtin/providers/aws/resource_aws_rds_cluster_test.go index c18a6431a..3774385a9 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_test.go @@ -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) { var v rds.DBCluster rInt := acctest.RandInt() @@ -322,6 +362,71 @@ resource "aws_rds_cluster" "default" { }`, 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 { return fmt.Sprintf(` resource "aws_rds_cluster" "default" { diff --git a/builtin/providers/aws/validators.go b/builtin/providers/aws/validators.go index a8f9c66cf..7ff0e6f38 100644 --- a/builtin/providers/aws/validators.go +++ b/builtin/providers/aws/validators.go @@ -12,7 +12,7 @@ import ( "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) if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { errors = append(errors, fmt.Errorf( @@ -33,6 +33,23 @@ func validateRdsId(v interface{}, k string) (ws []string, errors []error) { 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) { value := v.(string) 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)) } 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) { @@ -1041,3 +1078,79 @@ func validateApiGatewayUsagePlanQuotaSettings(v map[string]interface{}) (errors 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 +} diff --git a/builtin/providers/aws/validators_test.go b/builtin/providers/aws/validators_test.go index 0c37308fe..7fe451a87 100644 --- a/builtin/providers/aws/validators_test.go +++ b/builtin/providers/aws/validators_test.go @@ -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") + } + } +} diff --git a/website/source/docs/providers/aws/r/db_instance.html.markdown b/website/source/docs/providers/aws/r/db_instance.html.markdown index e491e3784..7e6044425 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -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. * `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. -* `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. * `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general 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 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 the time required to take snapshots 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 index faf7b351c..ad4c4d5d4 100644 --- a/website/source/docs/providers/aws/r/db_option_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_option_group.html.markdown @@ -38,8 +38,9 @@ resource "aws_db_option_group" "bar" { The following arguments are supported: -* `name` - (Required) The name of the Option group to be created. -* `option_group_description` - (Required) The description of the option group. +* `name` - (Optional, Forces new resource) The name of the option 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`. +* `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.. * `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. @@ -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 -``` \ No newline at end of file +``` diff --git a/website/source/docs/providers/aws/r/db_parameter_group.html.markdown b/website/source/docs/providers/aws/r/db_parameter_group.html.markdown index 271325034..2e4d362d2 100644 --- a/website/source/docs/providers/aws/r/db_parameter_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_parameter_group.html.markdown @@ -31,7 +31,8 @@ resource "aws_db_parameter_group" "default" { 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. * `description` - (Optional) The description of the DB parameter group. Defaults to "Managed by Terraform". * `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 -``` \ No newline at end of file +``` diff --git a/website/source/docs/providers/aws/r/db_subnet_group.html.markdown b/website/source/docs/providers/aws/r/db_subnet_group.html.markdown index a97f22c05..bee5eee52 100644 --- a/website/source/docs/providers/aws/r/db_subnet_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_subnet_group.html.markdown @@ -27,7 +27,8 @@ resource "aws_db_subnet_group" "default" { 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". * `subnet_ids` - (Required) A list of VPC subnet IDs. * `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 -``` \ No newline at end of file +``` diff --git a/website/source/docs/providers/aws/r/rds_cluster.html.markdown b/website/source/docs/providers/aws/r/rds_cluster.html.markdown index b40cbab40..6139824f1 100644 --- a/website/source/docs/providers/aws/r/rds_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster.html.markdown @@ -53,8 +53,8 @@ the [AWS official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/Co The following arguments are supported: -* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case -string. +* `cluster_identifier` - (Optional, Forces new resources) The cluster identifier. If omitted, Terraform will assign a random, unique identifier. +* `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 characters. If you do not provide a name, Amazon RDS will not create a database in the DB cluster you are creating diff --git a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown index d5196d733..031972d4b 100644 --- a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown @@ -47,8 +47,8 @@ the [AWS official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/Co The following arguments are supported: -* `identifier` - (Optional) The Instance Identifier. Must be a lower case -string. If omitted, a unique identifier will be generated. +* `identifier` - (Optional, Forces new resource) The indentifier for 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`. * `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 and memory, see [Scaling Aurora DB Instances][4]. Aurora currently diff --git a/website/source/docs/providers/aws/r/rds_cluster_parameter_group.markdown b/website/source/docs/providers/aws/r/rds_cluster_parameter_group.markdown index 8b465efad..c2fa63d78 100644 --- a/website/source/docs/providers/aws/r/rds_cluster_parameter_group.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster_parameter_group.markdown @@ -32,9 +32,10 @@ resource "aws_rds_cluster_parameter_group" "default" { 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. -* `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. * `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 -``` \ No newline at end of file +```