diff --git a/aws/resource_aws_db_instance.go b/aws/resource_aws_db_instance.go index ae09cd83e46..f465b3e3ea1 100644 --- a/aws/resource_aws_db_instance.go +++ b/aws/resource_aws_db_instance.go @@ -57,6 +57,11 @@ func resourceAwsDbInstance() *schema.Resource { Sensitive: true, }, + "deletion_protection": { + Type: schema.TypeBool, + Optional: true, + }, + "engine": { Type: schema.TypeString, Optional: true, @@ -466,6 +471,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts := rds.CreateDBInstanceReadReplicaInput{ AutoMinorVersionUpgrade: aws.Bool(d.Get("auto_minor_version_upgrade").(bool)), CopyTagsToSnapshot: aws.Bool(d.Get("copy_tags_to_snapshot").(bool)), + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), DBInstanceClass: aws.String(d.Get("instance_class").(string)), DBInstanceIdentifier: aws.String(identifier), PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), @@ -593,6 +599,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error DBName: aws.String(d.Get("name").(string)), DBInstanceClass: aws.String(d.Get("instance_class").(string)), DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), Engine: aws.String(d.Get("engine").(string)), EngineVersion: aws.String(d.Get("engine_version").(string)), S3BucketName: aws.String(s3_bucket["bucket_name"].(string)), @@ -749,6 +756,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error DBInstanceClass: aws.String(d.Get("instance_class").(string)), DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), DBSnapshotIdentifier: aws.String(d.Get("snapshot_identifier").(string)), + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), Tags: tags, } @@ -921,6 +929,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error DBName: aws.String(d.Get("name").(string)), DBInstanceClass: aws.String(d.Get("instance_class").(string)), DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), MasterUsername: aws.String(d.Get("username").(string)), MasterUserPassword: aws.String(d.Get("password").(string)), Engine: aws.String(d.Get("engine").(string)), @@ -1119,6 +1128,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("identifier", v.DBInstanceIdentifier) d.Set("resource_id", v.DbiResourceId) d.Set("username", v.MasterUsername) + d.Set("deletion_protection", v.DeletionProtection) d.Set("engine", v.Engine) d.Set("engine_version", v.EngineVersion) d.Set("allocated_storage", v.AllocatedStorage) @@ -1335,6 +1345,11 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error req.CopyTagsToSnapshot = aws.Bool(d.Get("copy_tags_to_snapshot").(bool)) requestUpdate = true } + if d.HasChange("deletion_protection") { + d.SetPartial("deletion_protection") + req.DeletionProtection = aws.Bool(d.Get("deletion_protection").(bool)) + requestUpdate = true + } if d.HasChange("instance_class") { d.SetPartial("instance_class") req.DBInstanceClass = aws.String(d.Get("instance_class").(string)) diff --git a/aws/resource_aws_db_instance_test.go b/aws/resource_aws_db_instance_test.go index 7156fbf35b4..fc1bd564cf2 100644 --- a/aws/resource_aws_db_instance_test.go +++ b/aws/resource_aws_db_instance_test.go @@ -123,6 +123,7 @@ func TestAccAWSDBInstance_basic(t *testing.T) { resource.TestCheckResourceAttr( "aws_db_instance.bar", "allocated_storage", "10"), resource.TestMatchResourceAttr("aws_db_instance.bar", "arn", regexp.MustCompile(`^arn:[^:]+:rds:[^:]+:\d{12}:db:.+`)), + resource.TestCheckResourceAttr("aws_db_instance.bar", "deletion_protection", "false"), resource.TestCheckResourceAttr( "aws_db_instance.bar", "engine", "mysql"), resource.TestCheckResourceAttr( @@ -285,6 +286,46 @@ func TestAccAWSDBInstance_iamAuth(t *testing.T) { }) } +func TestAccAWSDBInstance_DeletionProtection(t *testing.T) { + var dbInstance rds.DBInstance + + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_db_instance.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDBInstanceConfig_DeletionProtection(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "apply_immediately", + "final_snapshot_identifier", + "password", + "skip_final_snapshot", + }, + }, + { + Config: testAccAWSDBInstanceConfig_DeletionProtection(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), + ), + }, + }, + }) +} + func TestAccAWSDBInstance_FinalSnapshotIdentifier(t *testing.T) { var snap rds.DBInstance rInt := acctest.RandInt() @@ -509,6 +550,53 @@ func TestAccAWSDBInstance_ReplicateSourceDb_BackupWindow(t *testing.T) { }) } +func TestAccAWSDBInstance_ReplicateSourceDb_DeletionProtection(t *testing.T) { + t.Skip("CreateDBInstanceReadReplica API currently ignores DeletionProtection=true with SourceDBInstanceIdentifier set") + // --- FAIL: TestAccAWSDBInstance_ReplicateSourceDb_DeletionProtection (1624.88s) + // testing.go:527: Step 0 error: Check failed: Check 4/4 error: aws_db_instance.test: Attribute 'deletion_protection' expected "true", got "false" + // + // Action=CreateDBInstanceReadReplica&AutoMinorVersionUpgrade=true&CopyTagsToSnapshot=false&DBInstanceClass=db.t2.micro&DBInstanceIdentifier=tf-acc-test-6591588621809891413&DeletionProtection=true&PubliclyAccessible=false&SourceDBInstanceIdentifier=tf-acc-test-6591588621809891413-source&Tags=&Version=2014-10-31 + // + // + // + // false + // + // AWS Support has confirmed this issue and noted that it will be fixed in the future. + + var dbInstance, sourceDbInstance rds.DBInstance + + rName := acctest.RandomWithPrefix("tf-acc-test") + sourceResourceName := "aws_db_instance.source" + resourceName := "aws_db_instance.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDBInstanceConfig_ReplicateSourceDb_DeletionProtection(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(sourceResourceName, &sourceDbInstance), + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + testAccCheckAWSDBInstanceReplicaAttributes(&sourceDbInstance, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "true"), + ), + }, + // Ensure we disable deletion protection before attempting to delete :) + { + Config: testAccAWSDBInstanceConfig_ReplicateSourceDb_DeletionProtection(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(sourceResourceName, &sourceDbInstance), + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + testAccCheckAWSDBInstanceReplicaAttributes(&sourceDbInstance, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), + ), + }, + }, + }) +} + func TestAccAWSDBInstance_ReplicateSourceDb_IamDatabaseAuthenticationEnabled(t *testing.T) { var dbInstance, sourceDbInstance rds.DBInstance @@ -893,6 +981,43 @@ func TestAccAWSDBInstance_SnapshotIdentifier_BackupWindow(t *testing.T) { }) } +func TestAccAWSDBInstance_SnapshotIdentifier_DeletionProtection(t *testing.T) { + var dbInstance, sourceDbInstance rds.DBInstance + var dbSnapshot rds.DBSnapshot + + rName := acctest.RandomWithPrefix("tf-acc-test") + sourceDbResourceName := "aws_db_instance.source" + snapshotResourceName := "aws_db_snapshot.test" + resourceName := "aws_db_instance.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDBInstanceConfig_SnapshotIdentifier_DeletionProtection(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(sourceDbResourceName, &sourceDbInstance), + testAccCheckDbSnapshotExists(snapshotResourceName, &dbSnapshot), + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "true"), + ), + }, + // Ensure we disable deletion protection before attempting to delete :) + { + Config: testAccAWSDBInstanceConfig_SnapshotIdentifier_DeletionProtection(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists(sourceDbResourceName, &sourceDbInstance), + testAccCheckDbSnapshotExists(snapshotResourceName, &dbSnapshot), + testAccCheckAWSDBInstanceExists(resourceName, &dbInstance), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), + ), + }, + }, + }) +} + func TestAccAWSDBInstance_SnapshotIdentifier_IamDatabaseAuthenticationEnabled(t *testing.T) { var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -3247,6 +3372,21 @@ resource "aws_db_instance" "test" { `, rName) } +func testAccAWSDBInstanceConfig_DeletionProtection(rName string, deletionProtection bool) string { + return fmt.Sprintf(` +resource "aws_db_instance" "test" { + allocated_storage = 5 + deletion_protection = %t + engine = "mysql" + identifier = %q + instance_class = "db.t2.micro" + password = "avoid-plaintext-passwords" + username = "tfacctest" + skip_final_snapshot = true +} +`, deletionProtection, rName) +} + func testAccAWSDBInstanceConfig_EnabledCloudwatchLogsExports_Oracle(rName string) string { return fmt.Sprintf(` resource "aws_db_instance" "test" { @@ -3401,6 +3541,29 @@ resource "aws_db_instance" "test" { `, rName, backupWindow, rName) } +func testAccAWSDBInstanceConfig_ReplicateSourceDb_DeletionProtection(rName string, deletionProtection bool) string { + return fmt.Sprintf(` +resource "aws_db_instance" "source" { + allocated_storage = 5 + backup_retention_period = 1 + engine = "mysql" + identifier = "%s-source" + instance_class = "db.t2.micro" + password = "avoid-plaintext-passwords" + username = "tfacctest" + skip_final_snapshot = true +} + +resource "aws_db_instance" "test" { + deletion_protection = %t + identifier = %q + instance_class = "${aws_db_instance.source.instance_class}" + replicate_source_db = "${aws_db_instance.source.id}" + skip_final_snapshot = true +} +`, rName, deletionProtection, rName) +} + func testAccAWSDBInstanceConfig_ReplicateSourceDb_IamDatabaseAuthenticationEnabled(rName string, iamDatabaseAuthenticationEnabled bool) string { return fmt.Sprintf(` resource "aws_db_instance" "source" { @@ -3800,6 +3963,33 @@ resource "aws_db_instance" "test" { `, rName, rName, backupWindow, rName) } +func testAccAWSDBInstanceConfig_SnapshotIdentifier_DeletionProtection(rName string, deletionProtection bool) string { + return fmt.Sprintf(` +resource "aws_db_instance" "source" { + allocated_storage = 5 + engine = "mysql" + identifier = "%s-source" + instance_class = "db.t2.micro" + password = "avoid-plaintext-passwords" + username = "tfacctest" + skip_final_snapshot = true +} + +resource "aws_db_snapshot" "test" { + db_instance_identifier = "${aws_db_instance.source.id}" + db_snapshot_identifier = %q +} + +resource "aws_db_instance" "test" { + deletion_protection = %t + identifier = %q + instance_class = "${aws_db_instance.source.instance_class}" + snapshot_identifier = "${aws_db_snapshot.test.id}" + skip_final_snapshot = true +} +`, rName, rName, deletionProtection, rName) +} + func testAccAWSDBInstanceConfig_SnapshotIdentifier_IamDatabaseAuthenticationEnabled(rName string, iamDatabaseAuthenticationEnabled bool) string { return fmt.Sprintf(` resource "aws_db_instance" "source" { diff --git a/website/docs/r/db_instance.html.markdown b/website/docs/r/db_instance.html.markdown index d3f70bf08af..acae8e1d7c3 100644 --- a/website/docs/r/db_instance.html.markdown +++ b/website/docs/r/db_instance.html.markdown @@ -91,6 +91,7 @@ with read replicas, it needs to be specified only if the source database specifies an instance in another AWS Region. See [DBSubnetGroupName in API action CreateDBInstanceReadReplica](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBInstanceReadReplica.html) for additional read replica contraints. +* `deletion_protection` - (Optional) If the DB instance should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`. * `domain` - (Optional) The ID of the Directory Service Active Directory domain to create the instance in. * `domain_iam_role_name` - (Optional, but required if domain is provided) The name of the IAM role to be used when making API calls to the Directory Service. * `enabled_cloudwatch_logs_exports` - (Optional) List of log types to enable for exporting to CloudWatch logs. If omitted, no logs will be exported. Valid values (depending on `engine`): `alert`, `audit`, `error`, `general`, `listener`, `slowquery`, `trace`.