Skip to content

Commit

Permalink
Merge pull request #34955 from yaronya/f/efs-replication-config-desti…
Browse files Browse the repository at this point in the history
…nation-fs-id

r/aws_efs_replication_configuration: support an existing FS as destination
  • Loading branch information
ewbankkit authored Jan 11, 2024
2 parents 11ec582 + a27f18e commit aa8dd58
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 137 deletions.
7 changes: 7 additions & 0 deletions .changelog/34955.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_efs_replication_configuration: Increase Create timeout to 20 minutes
```

```release-note:enhancement
resource/aws_efs_replication_configuration: Mark `destination.file_system_id` as Optional, enabling [EFS replication fallback](https://docs.aws.amazon.com/efs/latest/ug/replication-use-cases.html#replicate-existing-destination)
```
29 changes: 0 additions & 29 deletions internal/service/efs/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,3 @@ func FindFileSystemPolicyByID(ctx context.Context, conn *efs.EFS, id string) (*e

return output, nil
}

func FindReplicationConfigurationByID(ctx context.Context, conn *efs.EFS, id string) (*efs.ReplicationConfigurationDescription, error) {
input := &efs.DescribeReplicationConfigurationsInput{
FileSystemId: aws.String(id),
}

output, err := conn.DescribeReplicationConfigurationsWithContext(ctx, input)

if tfawserr.ErrCodeEquals(err, efs.ErrCodeFileSystemNotFound) || tfawserr.ErrCodeEquals(err, efs.ErrCodeReplicationNotFound) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil ||
len(output.Replications) == 0 ||
output.Replications[0] == nil ||
len(output.Replications[0].Destinations) == 0 ||
output.Replications[0].Destinations[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output.Replications[0], nil
}
46 changes: 35 additions & 11 deletions internal/service/efs/mount_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

// @SDKResource("aws_efs_mount_target")
// @SDKResource("aws_efs_mount_target", name="Mount Target")
func ResourceMountTarget() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceMountTargetCreate,
Expand Down Expand Up @@ -222,6 +222,10 @@ func resourceMountTargetDelete(ctx context.Context, d *schema.ResourceData, meta
MountTargetId: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, efs.ErrCodeMountTargetNotFound) {
return diags
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "deleting EFS Mount Target (%s): %s", d.Id(), err)
}
Expand All @@ -243,12 +247,32 @@ func getAZFromSubnetID(ctx context.Context, conn *ec2.EC2, subnetID string) (str
return aws.StringValue(subnet.AvailabilityZone), nil
}

func FindMountTargetByID(ctx context.Context, conn *efs.EFS, id string) (*efs.MountTargetDescription, error) {
input := &efs.DescribeMountTargetsInput{
MountTargetId: aws.String(id),
func findMountTarget(ctx context.Context, conn *efs.EFS, input *efs.DescribeMountTargetsInput) (*efs.MountTargetDescription, error) {
output, err := findMountTargets(ctx, conn, input)

if err != nil {
return nil, err
}

output, err := conn.DescribeMountTargetsWithContext(ctx, input)
return tfresource.AssertSinglePtrResult(output)
}

func findMountTargets(ctx context.Context, conn *efs.EFS, input *efs.DescribeMountTargetsInput) ([]*efs.MountTargetDescription, error) {
var output []*efs.MountTargetDescription

err := conn.DescribeMountTargetsPagesWithContext(ctx, input, func(page *efs.DescribeMountTargetsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, v := range page.MountTargets {
if v != nil {
output = append(output, v)
}
}

return !lastPage
})

if tfawserr.ErrCodeEquals(err, efs.ErrCodeMountTargetNotFound) {
return nil, &retry.NotFoundError{
Expand All @@ -261,15 +285,15 @@ func FindMountTargetByID(ctx context.Context, conn *efs.EFS, id string) (*efs.Mo
return nil, err
}

if output == nil || len(output.MountTargets) == 0 || output.MountTargets[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}
return output, nil
}

if count := len(output.MountTargets); count > 1 {
return nil, tfresource.NewTooManyResultsError(count, input)
func FindMountTargetByID(ctx context.Context, conn *efs.EFS, id string) (*efs.MountTargetDescription, error) {
input := &efs.DescribeMountTargetsInput{
MountTargetId: aws.String(id),
}

return output.MountTargets[0], nil
return findMountTarget(ctx, conn, input)
}

func statusMountTargetLifeCycleState(ctx context.Context, conn *efs.EFS, id string) retry.StateRefreshFunc {
Expand Down
159 changes: 144 additions & 15 deletions internal/service/efs/replication_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ package efs

import (
"context"
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

// @SDKResource("aws_efs_replication_configuration")
// @SDKResource("aws_efs_replication_configuration", name="Replication Configuration")
func ResourceReplicationConfiguration() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceReplicationConfigurationCreate,
Expand All @@ -31,7 +33,7 @@ func ResourceReplicationConfiguration() *schema.Resource {
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Create: schema.DefaultTimeout(20 * time.Minute),
Delete: schema.DefaultTimeout(20 * time.Minute),
},

Expand All @@ -55,7 +57,9 @@ func ResourceReplicationConfiguration() *schema.Resource {
},
"file_system_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"kms_key_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -170,35 +174,156 @@ func resourceReplicationConfigurationDelete(ctx context.Context, d *schema.Resou
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).EFSConn(ctx)

// Deletion of the replication configuration must be done from the
// Region in which the destination file system is located.
// Deletion of the replication configuration must be done from the Region in which the destination file system is located.
destination := expandDestinationsToCreate(d.Get("destination").([]interface{}))[0]
session, err := conns.NewSessionForRegion(&conn.Config, aws.StringValue(destination.Region), meta.(*conns.AWSClient).TerraformVersion)
region := aws.StringValue(destination.Region)
session, err := conns.NewSessionForRegion(&conn.Config, region, meta.(*conns.AWSClient).TerraformVersion)

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating AWS session: %s", err)
return sdkdiag.AppendErrorf(diags, "creating AWS session (%s): %s", region, err)
}

deleteConn := efs.New(session)

log.Printf("[DEBUG] Deleting EFS Replication Configuration: %s", d.Id())
_, err = deleteConn.DeleteReplicationConfigurationWithContext(ctx, &efs.DeleteReplicationConfigurationInput{
SourceFileSystemId: aws.String(d.Id()),
if err := deleteReplicationConfiguration(ctx, efs.New(session), d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return sdkdiag.AppendFromErr(diags, err)
}

// Delete also in the source Region.
if err := deleteReplicationConfiguration(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return sdkdiag.AppendFromErr(diags, err)
}

return diags
}

func deleteReplicationConfiguration(ctx context.Context, conn *efs.EFS, fsID string, timeout time.Duration) error {
_, err := conn.DeleteReplicationConfigurationWithContext(ctx, &efs.DeleteReplicationConfigurationInput{
SourceFileSystemId: aws.String(fsID),
})

if tfawserr.ErrCodeEquals(err, efs.ErrCodeFileSystemNotFound, efs.ErrCodeReplicationNotFound) {
return diags
return nil
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "deleting EFS Replication Configuration (%s): %s", d.Id(), err)
return fmt.Errorf("deleting EFS Replication Configuration (%s): %w", fsID, err)
}

if _, err := waitReplicationConfigurationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for EFS Replication Configuration (%s) delete: %s", d.Id(), err)
if _, err := waitReplicationConfigurationDeleted(ctx, conn, fsID, timeout); err != nil {
return fmt.Errorf("waiting for EFS Replication Configuration (%s) delete: %w", fsID, err)
}

return diags
return nil
}

func findReplicationConfiguration(ctx context.Context, conn *efs.EFS, input *efs.DescribeReplicationConfigurationsInput) (*efs.ReplicationConfigurationDescription, error) {
output, err := findReplicationConfigurations(ctx, conn, input)

if err != nil {
return nil, err
}

return tfresource.AssertSinglePtrResult(output)
}

func findReplicationConfigurations(ctx context.Context, conn *efs.EFS, input *efs.DescribeReplicationConfigurationsInput) ([]*efs.ReplicationConfigurationDescription, error) {
var output []*efs.ReplicationConfigurationDescription

err := conn.DescribeReplicationConfigurationsPagesWithContext(ctx, input, func(page *efs.DescribeReplicationConfigurationsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, v := range page.Replications {
if v != nil {
output = append(output, v)
}
}

return !lastPage
})

if tfawserr.ErrCodeEquals(err, efs.ErrCodeFileSystemNotFound, efs.ErrCodeReplicationNotFound) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

return output, nil
}

func FindReplicationConfigurationByID(ctx context.Context, conn *efs.EFS, id string) (*efs.ReplicationConfigurationDescription, error) {
input := &efs.DescribeReplicationConfigurationsInput{
FileSystemId: aws.String(id),
}

output, err := findReplicationConfiguration(ctx, conn, input)

if err != nil {
return nil, err
}

if len(output.Destinations) == 0 || output.Destinations[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}

func statusReplicationConfiguration(ctx context.Context, conn *efs.EFS, id string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := FindReplicationConfigurationByID(ctx, conn, id)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

return output, aws.StringValue(output.Destinations[0].Status), nil
}
}

func waitReplicationConfigurationCreated(ctx context.Context, conn *efs.EFS, id string, timeout time.Duration) (*efs.ReplicationConfigurationDescription, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{efs.ReplicationStatusEnabling},
Target: []string{efs.ReplicationStatusEnabled},
Refresh: statusReplicationConfiguration(ctx, conn, id),
Timeout: timeout,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*efs.ReplicationConfigurationDescription); ok {
return output, err
}

return nil, err
}

func waitReplicationConfigurationDeleted(ctx context.Context, conn *efs.EFS, id string, timeout time.Duration) (*efs.ReplicationConfigurationDescription, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{efs.ReplicationStatusDeleting},
Target: []string{},
Refresh: statusReplicationConfiguration(ctx, conn, id),
Timeout: timeout,
ContinuousTargetOccurence: 2,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*efs.ReplicationConfigurationDescription); ok {
return output, err
}

return nil, err
}

func expandDestinationToCreate(tfMap map[string]interface{}) *efs.DestinationToCreate {
Expand All @@ -220,6 +345,10 @@ func expandDestinationToCreate(tfMap map[string]interface{}) *efs.DestinationToC
apiObject.Region = aws.String(v)
}

if v, ok := tfMap["file_system_id"].(string); ok && v != "" {
apiObject.FileSystemId = aws.String(v)
}

return apiObject
}

Expand Down
Loading

0 comments on commit aa8dd58

Please sign in to comment.