Skip to content

Commit

Permalink
Merge pull request #34728 from hashicorp/b-rds-oob-bluegreen
Browse files Browse the repository at this point in the history
resource/aws_db_instance: Allow out-of-band Blue/Green update
  • Loading branch information
gdavison authored Dec 9, 2023
2 parents c47967c + 34a7ff7 commit d22bdb5
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 37 deletions.
3 changes: 3 additions & 0 deletions .changelog/34728.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_db_instance: Fix error where Terraform loses track of resource if Blue/Green Deployment is applied outside of Terraform
```
7 changes: 7 additions & 0 deletions .ci/semgrep/pluginsdk/isnewresource.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ rules:
}
- pattern-not-inside: |
if <... !d.IsNewResource() ...> { ... }
- pattern-not-inside: |
if <... d.IsNewResource() ...> { ... } else {
...
d.SetId("")
...
return nil
}
severity: WARNING
20 changes: 11 additions & 9 deletions internal/service/rds/blue_green.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

type cleanupWaiterFunc func(context.Context, ...tfresource.OptionsFunc) //nolint:unused // WIP

type cleanupWaiterErrFunc func(context.Context, ...tfresource.OptionsFunc) error //nolint:unused // WIP
type cleanupWaiterFunc func(context.Context, *rds_sdkv2.Client, ...tfresource.OptionsFunc)

type blueGreenOrchestrator struct {
conn *rds_sdkv2.Client
cleanupWaiters []cleanupWaiterFunc //nolint:unused // WIP
cleanupWaiters []cleanupWaiterFunc
}

func newBlueGreenOrchestrator(conn *rds_sdkv2.Client) *blueGreenOrchestrator {
Expand All @@ -32,21 +30,21 @@ func newBlueGreenOrchestrator(conn *rds_sdkv2.Client) *blueGreenOrchestrator {
}
}

func (o *blueGreenOrchestrator) cleanUp(ctx context.Context) { //nolint:unused // WIP
func (o *blueGreenOrchestrator) CleanUp(ctx context.Context) {
if len(o.cleanupWaiters) == 0 {
return
}

waiter, waiters := o.cleanupWaiters[0], o.cleanupWaiters[1:]
waiter(ctx)
waiter(ctx, o.conn)
for _, waiter := range waiters {
// Skip the delay for subsequent waiters. Since we're waiting for all of the waiters
// to complete, we don't need to run them concurrently, saving on network traffic.
waiter(ctx, tfresource.WithDelay(0))
waiter(ctx, o.conn, tfresource.WithDelay(0))
}
}

func (o *blueGreenOrchestrator) createDeployment(ctx context.Context, input *rds_sdkv2.CreateBlueGreenDeploymentInput) (*types.BlueGreenDeployment, error) {
func (o *blueGreenOrchestrator) CreateDeployment(ctx context.Context, input *rds_sdkv2.CreateBlueGreenDeploymentInput) (*types.BlueGreenDeployment, error) {
createOut, err := o.conn.CreateBlueGreenDeployment(ctx, input)
if err != nil {
return nil, fmt.Errorf("creating Blue/Green Deployment: %s", err)
Expand All @@ -63,7 +61,7 @@ func (o *blueGreenOrchestrator) waitForDeploymentAvailable(ctx context.Context,
return dep, nil
}

func (o *blueGreenOrchestrator) switchover(ctx context.Context, identifier string, timeout time.Duration) (*types.BlueGreenDeployment, error) {
func (o *blueGreenOrchestrator) Switchover(ctx context.Context, identifier string, timeout time.Duration) (*types.BlueGreenDeployment, error) {
input := &rds_sdkv2.SwitchoverBlueGreenDeploymentInput{
BlueGreenDeploymentIdentifier: aws.String(identifier),
}
Expand All @@ -86,6 +84,10 @@ func (o *blueGreenOrchestrator) switchover(ctx context.Context, identifier strin
return dep, nil
}

func (o *blueGreenOrchestrator) AddCleanupWaiter(f cleanupWaiterFunc) {
o.cleanupWaiters = append(o.cleanupWaiters, f)
}

type instanceHandler struct {
conn *rds_sdkv2.Client
}
Expand Down
15 changes: 15 additions & 0 deletions internal/service/rds/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,19 @@ var (
FindDBInstanceByID = findDBInstanceByIDSDKv1

ListTags = listTags

NewBlueGreenOrchestrator = newBlueGreenOrchestrator

WaitBlueGreenDeploymentDeleted = waitBlueGreenDeploymentDeleted
WaitBlueGreenDeploymentAvailable = waitBlueGreenDeploymentAvailable

ParseDBInstanceARN = parseDBInstanceARN

WaitDBInstanceAvailable = waitDBInstanceAvailableSDKv2
WaitDBInstanceDeleted = waitDBInstanceDeleted
)

const (
ErrCodeInvalidParameterCombination = errCodeInvalidParameterCombination
ErrCodeInvalidParameterValue = errCodeInvalidParameterValue
)
51 changes: 26 additions & 25 deletions internal/service/rds/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -1651,18 +1651,31 @@ func resourceInstanceRead(ctx context.Context, d *schema.ResourceData, meta inte
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).RDSConn(ctx)

v, err := findDBInstanceByIDSDKv1(ctx, conn, d.Id())
var (
v *rds.DBInstance
err error
)

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] RDS DB Instance (%s) not found, removing from state", d.Get("identifier").(string))
d.SetId("")
return nil
if d.IsNewResource() {
v, err = findDBInstanceByIDSDKv1(ctx, conn, d.Id())
} else {
v, err = findDBInstanceByIDSDKv1(ctx, conn, d.Id())
if tfresource.NotFound(err) {
v, err = findDBInstanceByIDSDKv1(ctx, conn, d.Get("identifier").(string))
if tfresource.NotFound(err) {
log.Printf("[WARN] RDS DB Instance (%s) not found, removing from state", d.Get("identifier").(string))
d.SetId("")
return nil
}
}
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "reading RDS DB Instance (%s): %s", d.Get("identifier").(string), err)
}

d.SetId(aws.StringValue(v.DbiResourceId))

d.Set("allocated_storage", v.AllocatedStorage)
d.Set("arn", v.DBInstanceArn)
d.Set("auto_minor_version_upgrade", v.AutoMinorVersionUpgrade)
Expand Down Expand Up @@ -1831,21 +1844,9 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
"password",
) {
orchestrator := newBlueGreenOrchestrator(conn)
handler := newInstanceHandler(conn)
var cleaupWaiters []func(optFns ...tfresource.OptionsFunc)
defer func() {
if len(cleaupWaiters) == 0 {
return
}
defer orchestrator.CleanUp(ctx)

waiter, waiters := cleaupWaiters[0], cleaupWaiters[1:]
waiter()
for _, waiter := range waiters {
// Skip the delay for subsequent waiters. Since we're waiting for all of the waiters
// to complete, we don't need to run them concurrently, saving on network traffic.
waiter(tfresource.WithDelay(0))
}
}()
handler := newInstanceHandler(conn)

err := handler.precondition(ctx, d)
if err != nil {
Expand All @@ -1856,7 +1857,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in

log.Printf("[DEBUG] Updating RDS DB Instance (%s): Creating Blue/Green Deployment", d.Get("identifier").(string))

dep, err := orchestrator.createDeployment(ctx, createIn)
dep, err := orchestrator.CreateDeployment(ctx, createIn)
if err != nil {
return sdkdiag.AppendErrorf(diags, "updating RDS DB Instance (%s): %s", d.Get("identifier").(string), err)
}
Expand All @@ -1883,7 +1884,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
return
}

cleaupWaiters = append(cleaupWaiters, func(optFns ...tfresource.OptionsFunc) {
orchestrator.AddCleanupWaiter(func(ctx context.Context, conn *rds_sdkv2.Client, optFns ...tfresource.OptionsFunc) {
_, err = waitBlueGreenDeploymentDeleted(ctx, conn, aws.StringValue(deploymentIdentifier), deadline.Remaining(), optFns...)
if err != nil {
diags = sdkdiag.AppendErrorf(diags, "updating RDS DB Instance (%s): deleting Blue/Green Deployment: waiting for completion: %s", d.Get("identifier").(string), err)
Expand Down Expand Up @@ -1912,7 +1913,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in

log.Printf("[DEBUG] Updating RDS DB Instance (%s): Switching over Blue/Green Deployment", d.Get("identifier").(string))

dep, err = orchestrator.switchover(ctx, aws.StringValue(dep.BlueGreenDeploymentIdentifier), deadline.Remaining())
dep, err = orchestrator.Switchover(ctx, aws.StringValue(dep.BlueGreenDeploymentIdentifier), deadline.Remaining())
if err != nil {
return sdkdiag.AppendErrorf(diags, "updating RDS DB Instance (%s): %s", d.Get("identifier").(string), err)
}
Expand Down Expand Up @@ -1968,7 +1969,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
return sdkdiag.AppendErrorf(diags, "updating RDS DB Instance (%s): deleting Blue/Green Deployment source: %s", d.Get("identifier").(string), err)
}

cleaupWaiters = append(cleaupWaiters, func(optFns ...tfresource.OptionsFunc) {
orchestrator.AddCleanupWaiter(func(ctx context.Context, conn *rds_sdkv2.Client, optFns ...tfresource.OptionsFunc) {
_, err = waitDBInstanceDeleted(ctx, meta.(*conns.AWSClient).RDSConn(ctx), sourceARN.Identifier, deadline.Remaining(), optFns...)
if err != nil {
diags = sdkdiag.AppendErrorf(diags, "updating RDS DB Instance (%s): deleting Blue/Green Deployment source: waiting for completion: %s", d.Get("identifier").(string), err)
Expand Down Expand Up @@ -2605,7 +2606,7 @@ func waitDBInstanceAvailableSDKv1(ctx context.Context, conn *rds.RDS, id string,
return nil, err
}

func waitDBInstanceAvailableSDKv2(ctx context.Context, conn *rds_sdkv2.Client, id string, timeout time.Duration, optFns ...tfresource.OptionsFunc) (*rds.DBInstance, error) { //nolint:unparam
func waitDBInstanceAvailableSDKv2(ctx context.Context, conn *rds_sdkv2.Client, id string, timeout time.Duration, optFns ...tfresource.OptionsFunc) (*rds.DBInstance, error) {
options := tfresource.Options{
PollInterval: 10 * time.Second,
Delay: 1 * time.Minute,
Expand Down Expand Up @@ -2648,7 +2649,7 @@ func waitDBInstanceAvailableSDKv2(ctx context.Context, conn *rds_sdkv2.Client, i
return nil, err
}

func waitDBInstanceDeleted(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration, optFns ...tfresource.OptionsFunc) (*rds.DBInstance, error) { //nolint:unparam
func waitDBInstanceDeleted(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration, optFns ...tfresource.OptionsFunc) (*rds.DBInstance, error) {
options := tfresource.Options{
PollInterval: 10 * time.Second,
Delay: 1 * time.Minute,
Expand Down
Loading

0 comments on commit d22bdb5

Please sign in to comment.