Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#507: aws_db_instance: Add support for in-place identifier update #16782

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 82 additions & 40 deletions aws/resource_aws_db_instance.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws

import (
"context"
"fmt"
"log"
"regexp"
Expand All @@ -11,12 +12,15 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

const DbiResourceIdFilterName = "dbi-resource-id"

func resourceAwsDbInstance() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDbInstanceCreate,
Expand All @@ -27,13 +31,18 @@ func resourceAwsDbInstance() *schema.Resource {
State: resourceAwsDbInstanceImport,
},

SchemaVersion: 1,
SchemaVersion: 2,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceAwsDbInstanceResourceV0().CoreConfigSchema().ImpliedType(),
Upgrade: resourceAwsDbInstanceStateUpgradeV0,
Version: 0,
},
{
Type: resourceAwsDbInstanceResourceV1().CoreConfigSchema().ImpliedType(),
Upgrade: resourceAwsDbInstanceStateUpgradeV1,
Version: 1,
},
},

Timeouts: &schema.ResourceTimeout{
Expand Down Expand Up @@ -149,15 +158,13 @@ func resourceAwsDbInstance() *schema.Resource {
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: validateRdsIdentifierPrefix,
},

Expand Down Expand Up @@ -537,6 +544,18 @@ func resourceAwsDbInstance() *schema.Resource {

"tags": tagsSchema(),
},

CustomizeDiff: customdiff.All(
customdiff.ComputedIf("address", func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
return diff.HasChange("identifier")
}),
customdiff.ComputedIf("arn", func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
return diff.HasChange("identifier")
}),
customdiff.ComputedIf("endpoint", func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
return diff.HasChange("identifier")
}),
),
}
}

Expand Down Expand Up @@ -706,10 +725,12 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
}

log.Printf("[DEBUG] DB Instance Replica create configuration: %#v", opts)
_, err := conn.CreateDBInstanceReadReplica(&opts)
createDBInstanceReadReplicaOutput, err := conn.CreateDBInstanceReadReplica(&opts)
if err != nil {
return fmt.Errorf("Error creating DB Instance: %s", err)
}

d.Set("resource_id", createDBInstanceReadReplicaOutput.DBInstance.DbiResourceId)
} else if v, ok := d.GetOk("s3_import"); ok {

if _, ok := d.GetOk("allocated_storage"); !ok {
Expand Down Expand Up @@ -837,10 +858,11 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
}

log.Printf("[DEBUG] DB Instance S3 Restore configuration: %#v", opts)
var restoreDBInstanceFromS3Output *rds.RestoreDBInstanceFromS3Output
var err error
// Retry for IAM eventual consistency
err = resource.Retry(2*time.Minute, func() *resource.RetryError {
_, err = conn.RestoreDBInstanceFromS3(&opts)
restoreDBInstanceFromS3Output, err = conn.RestoreDBInstanceFromS3(&opts)
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "ENHANCED_MONITORING") {
return resource.RetryableError(err)
Expand All @@ -860,23 +882,23 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.RestoreDBInstanceFromS3(&opts)
restoreDBInstanceFromS3Output, err = conn.RestoreDBInstanceFromS3(&opts)
}
if err != nil {
return fmt.Errorf("Error creating DB Instance: %s", err)
}

d.SetId(d.Get("identifier").(string))
d.Set("resource_id", restoreDBInstanceFromS3Output.DBInstance.DbiResourceId)

log.Printf("[INFO] DB Instance ID: %s", d.Id())
log.Printf("[INFO] DB Instance ID: %q; DB Resource ID: %q", identifier, d.Get("resource_id").(string))

log.Println(
"[INFO] Waiting for DB Instance to be available")

stateConf := &resource.StateChangeConf{
Pending: resourceAwsDbInstanceCreatePendingStates,
Target: []string{"available", "storage-optimization"},
Refresh: resourceAwsDbInstanceStateRefreshFunc(d.Id(), conn),
Refresh: resourceAwsDbInstanceStateRefreshFunc(d.Get("resource_id").(string), conn),
Timeout: d.Timeout(schema.TimeoutCreate),
MinTimeout: 10 * time.Second,
Delay: 30 * time.Second, // Wait 30 secs before starting
Expand Down Expand Up @@ -1059,7 +1081,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
}

log.Printf("[DEBUG] DB Instance restore from snapshot configuration: %s", opts)
_, err := conn.RestoreDBInstanceFromDBSnapshot(&opts)
restoreDBInstanceFromDBSnapshotOutput, err := conn.RestoreDBInstanceFromDBSnapshot(&opts)

// When using SQL Server engine with MultiAZ enabled, its not
// possible to immediately enable mirroring since
Expand All @@ -1073,12 +1095,14 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
opts.MultiAZ = aws.Bool(false)
modifyDbInstanceInput.MultiAZ = aws.Bool(true)
requiresModifyDbInstance = true
_, err = conn.RestoreDBInstanceFromDBSnapshot(&opts)
restoreDBInstanceFromDBSnapshotOutput, err = conn.RestoreDBInstanceFromDBSnapshot(&opts)
}

if err != nil {
return fmt.Errorf("Error creating DB Instance: %s", err)
}

d.Set("resource_id", restoreDBInstanceFromDBSnapshotOutput.DBInstance.DbiResourceId)
} else if v, ok := d.GetOk("restore_to_point_in_time"); ok {
if input := expandRestoreToPointInTime(v.([]interface{})); input != nil {
input.AutoMinorVersionUpgrade = aws.Bool(d.Get("auto_minor_version_upgrade").(bool))
Expand Down Expand Up @@ -1163,10 +1187,12 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error

log.Printf("[DEBUG] DB Instance restore to point in time configuration: %s", input)

_, err := conn.RestoreDBInstanceToPointInTime(input)
restoreDBInstanceToPointInTime, err := conn.RestoreDBInstanceToPointInTime(input)
if err != nil {
return fmt.Errorf("error creating DB Instance: %w", err)
}

d.Set("resource_id", restoreDBInstanceToPointInTime.DBInstance.DbiResourceId)
}
} else {
if _, ok := d.GetOk("allocated_storage"); !ok {
Expand Down Expand Up @@ -1319,7 +1345,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.CreateDBInstance(&opts)
createdDBInstanceOutput, err = conn.CreateDBInstance(&opts)
}
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "") {
Expand All @@ -1328,14 +1354,16 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
}
return fmt.Errorf("Error creating DB Instance: %s", err)
}
d.Set("resource_id", createdDBInstanceOutput.DBInstance.DbiResourceId)
// This is added here to avoid unnecessary modification when ca_cert_identifier is the default one
if attr, ok := d.GetOk("ca_cert_identifier"); ok && attr.(string) != aws.StringValue(createdDBInstanceOutput.DBInstance.CACertificateIdentifier) {
modifyDbInstanceInput.CACertificateIdentifier = aws.String(attr.(string))
requiresModifyDbInstance = true
}
}

d.SetId(d.Get("identifier").(string))
d.SetId(d.Get("resource_id").(string))
log.Printf("[DEBUG] DB Instance ID: %q; DB Resource ID: %q", identifier, d.Id())

stateConf := &resource.StateChangeConf{
Pending: resourceAwsDbInstanceCreatePendingStates,
Expand All @@ -1346,43 +1374,43 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
Delay: 30 * time.Second, // Wait 30 secs before starting
}

log.Printf("[INFO] Waiting for DB Instance (%s) to be available", d.Id())
log.Printf("[INFO] Waiting for DB Instance %q (%s) to become available", identifier, d.Id())
_, err := stateConf.WaitForState()
if err != nil {
return err
}

if requiresModifyDbInstance {
modifyDbInstanceInput.DBInstanceIdentifier = aws.String(d.Id())
modifyDbInstanceInput.DBInstanceIdentifier = aws.String(identifier)

log.Printf("[INFO] DB Instance (%s) configuration requires ModifyDBInstance: %s", d.Id(), modifyDbInstanceInput)
log.Printf("[INFO] DB Instance %q (%s) configuration requires ModifyDBInstance: %s", identifier, d.Id(), modifyDbInstanceInput)
_, err := conn.ModifyDBInstance(modifyDbInstanceInput)
if err != nil {
return fmt.Errorf("error modifying DB Instance (%s): %s", d.Id(), err)
return fmt.Errorf("error modifying DB Instance %q (%s): %s", identifier, d.Id(), err)
}

log.Printf("[INFO] Waiting for DB Instance (%s) to be available", d.Id())
log.Printf("[INFO] Waiting for DB Instance %q (%s) to become available", identifier, d.Id())
err = waitUntilAwsDbInstanceIsAvailableAfterUpdate(d.Id(), conn, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return fmt.Errorf("error waiting for DB Instance (%s) to be available: %s", d.Id(), err)
return fmt.Errorf("error waiting for DB Instance %q (%s) to become available: %s", identifier, d.Id(), err)
}
}

if requiresRebootDbInstance {
rebootDbInstanceInput := &rds.RebootDBInstanceInput{
DBInstanceIdentifier: aws.String(d.Id()),
DBInstanceIdentifier: aws.String(identifier),
}

log.Printf("[INFO] DB Instance (%s) configuration requires RebootDBInstance: %s", d.Id(), rebootDbInstanceInput)
log.Printf("[INFO] DB Instance %q (%s) configuration requires RebootDBInstance: %s", identifier, d.Id(), rebootDbInstanceInput)
_, err := conn.RebootDBInstance(rebootDbInstanceInput)
if err != nil {
return fmt.Errorf("error rebooting DB Instance (%s): %s", d.Id(), err)
return fmt.Errorf("error rebooting DB Instance %q (%s): %s", identifier, d.Id(), err)
}

log.Printf("[INFO] Waiting for DB Instance (%s) to be available", d.Id())
log.Printf("[INFO] Waiting for DB Instance %q (%s) to become available", identifier, d.Id())
err = waitUntilAwsDbInstanceIsAvailableAfterUpdate(d.Id(), conn, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return fmt.Errorf("error waiting for DB Instance (%s) to be available: %s", d.Id(), err)
return fmt.Errorf("error waiting for DB Instance %q (%s) to become available: %s", identifier, d.Id(), err)
}
}

Expand All @@ -1403,9 +1431,9 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

d.Set("name", v.DBName)
d.Set("identifier", v.DBInstanceIdentifier)
d.Set("resource_id", v.DbiResourceId)
d.Set("identifier", v.DBInstanceIdentifier)
d.Set("name", v.DBName)
d.Set("username", v.MasterUsername)
d.Set("deletion_protection", v.DeletionProtection)
d.Set("engine", v.Engine)
Expand Down Expand Up @@ -1481,7 +1509,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
tags, err := keyvaluetags.RdsListTags(conn, d.Get("arn").(string))

if err != nil {
return fmt.Errorf("error listing tags for RDS DB Instance (%s): %s", d.Get("arn").(string), err)
return fmt.Errorf("error listing tags for RDS DB Instance %q (%s): %s", d.Get("arn").(string), d.Id(), err)
}

if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
Expand Down Expand Up @@ -1525,9 +1553,9 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).rdsconn

log.Printf("[DEBUG] DB Instance destroy: %v", d.Id())
log.Printf("[DEBUG] DB Instance destroy: %q (%s)", d.Get("arn"), d.Id())

opts := rds.DeleteDBInstanceInput{DBInstanceIdentifier: aws.String(d.Id())}
opts := rds.DeleteDBInstanceInput{DBInstanceIdentifier: aws.String(d.Get("identifier").(string))}

skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
opts.SkipFinalSnapshot = aws.Bool(skipFinalSnapshot)
Expand All @@ -1552,10 +1580,10 @@ func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error

// InvalidDBInstanceState: Instance XXX is already being deleted.
if err != nil && !isAWSErr(err, rds.ErrCodeInvalidDBInstanceStateFault, "is already being deleted") {
return fmt.Errorf("error deleting Database Instance %q: %s", d.Id(), err)
return fmt.Errorf("error deleting Database Instance %q (%s): %s", d.Get("identifier").(string), d.Id(), err)
}

log.Println("[INFO] Waiting for DB Instance to be destroyed")
log.Printf("[INFO] Waiting for DB Instance %q (%s) to be destroyed", d.Get("identifier").(string), d.Id())
return waitUntilAwsDbInstanceIsDeleted(d.Id(), conn, d.Timeout(schema.TimeoutDelete))
}

Expand Down Expand Up @@ -1588,16 +1616,24 @@ func waitUntilAwsDbInstanceIsDeleted(id string, conn *rds.RDS, timeout time.Dura
func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).rdsconn

oldIdentifierPtr, newIdentifierPtr := d.GetChange("identifier")
oldIdentifier := oldIdentifierPtr.(string)
newIdentifier := newIdentifierPtr.(string)

req := &rds.ModifyDBInstanceInput{
ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)),
DBInstanceIdentifier: aws.String(d.Id()),
DBInstanceIdentifier: aws.String(oldIdentifier),
}

if !aws.BoolValue(req.ApplyImmediately) {
log.Println("[INFO] Only settings updating, instance changes will be applied in next maintenance window")
}

requestUpdate := false
if d.HasChanges("identifier") {
req.NewDBInstanceIdentifier = aws.String(newIdentifier)
requestUpdate = true
}
if d.HasChanges("allocated_storage", "iops") {
req.Iops = aws.Int64(int64(d.Get("iops").(int)))
req.AllocatedStorage = aws.Int64(int64(d.Get("allocated_storage").(int)))
Expand Down Expand Up @@ -1785,13 +1821,13 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error
}

if err != nil {
return fmt.Errorf("Error modifying DB Instance %s: %s", d.Id(), err)
return fmt.Errorf("Error modifying DB Instance %q (%s): %s", d.Get("identifier").(string), d.Id(), err)
}

log.Printf("[DEBUG] Waiting for DB Instance (%s) to be available", d.Id())
log.Printf("[DEBUG] Waiting for DB Instance %q (%s) to become available", d.Get("identifier").(string), d.Id())
err = waitUntilAwsDbInstanceIsAvailableAfterUpdate(d.Id(), conn, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return fmt.Errorf("error waiting for DB Instance (%s) to be available: %s", d.Id(), err)
return fmt.Errorf("error waiting for DB Instance %q (%s) to become available: %s", d.Get("identifier").(string), d.Id(), err)
}
}

Expand All @@ -1800,7 +1836,7 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error
if d.Get("replicate_source_db").(string) == "" {
// promote
opts := rds.PromoteReadReplicaInput{
DBInstanceIdentifier: aws.String(d.Id()),
DBInstanceIdentifier: aws.String(d.Get("identifier").(string)),
}
attr := d.Get("backup_retention_period")
opts.BackupRetentionPeriod = aws.Int64(int64(attr.(int)))
Expand Down Expand Up @@ -1834,8 +1870,14 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error
// error with AWS. When the DBInstance is not found, it returns no error and a
// nil pointer.
func resourceAwsDbInstanceRetrieve(id string, conn *rds.RDS) (*rds.DBInstance, error) {
resourceIdFilterName := DbiResourceIdFilterName
opts := rds.DescribeDBInstancesInput{
DBInstanceIdentifier: aws.String(id),
Filters: []*rds.Filter{
{
Name: &resourceIdFilterName,
Values: []*string{&id},
},
},
}

log.Printf("[DEBUG] DB Instance describe configuration: %#v", opts)
Expand All @@ -1848,7 +1890,7 @@ func resourceAwsDbInstanceRetrieve(id string, conn *rds.RDS) (*rds.DBInstance, e
return nil, fmt.Errorf("Error retrieving DB Instances: %s", err)
}

if len(resp.DBInstances) != 1 || resp.DBInstances[0] == nil || aws.StringValue(resp.DBInstances[0].DBInstanceIdentifier) != id {
if len(resp.DBInstances) != 1 || resp.DBInstances[0] == nil || aws.StringValue(resp.DBInstances[0].DbiResourceId) != id {
return nil, nil
}

Expand Down Expand Up @@ -1879,7 +1921,7 @@ func resourceAwsDbInstanceStateRefreshFunc(id string, conn *rds.RDS) resource.St
}

if v.DBInstanceStatus != nil {
log.Printf("[DEBUG] DB Instance status for instance %s: %s", id, *v.DBInstanceStatus)
log.Printf("[DEBUG] DB Instance status for %q (%s): %s", *v.DBInstanceIdentifier, id, *v.DBInstanceStatus)
}

return v, *v.DBInstanceStatus, nil
Expand Down
Loading