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

r/aws_efs_replication_configuration: support an existing FS as destination #34955

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fda740c
Add file system id attribute to destination block of aws_efs_replicat…
yaronya Dec 11, 2023
a3aea41
Update docs
yaronya Dec 16, 2023
3cacabb
set the destination.file_system_id attribute as computed again
yaronya Dec 16, 2023
3d043a4
Add small note about `destination.file_system_id` attr
yaronya Dec 16, 2023
86cbc1d
Refine docs about `destination.file_system_id`
yaronya Dec 16, 2023
8087b67
Add tests for destination.file_system_id attr usage
yaronya Dec 16, 2023
f4443e3
Fix typo
yaronya Dec 16, 2023
8b05dc2
Fix test to include the region attribute
yaronya Dec 16, 2023
a7d5011
Disable efs replication overwrite protection for existing EFS replica…
yaronya Jan 4, 2024
6915fa5
Format EFS replication test using terrafmt
yaronya Jan 4, 2024
8ba3534
Increase default time out of EFS replication creation to 20m
yaronya Jan 6, 2024
8562c73
Fix EFS replication existing file system test to ignore changes
yaronya Jan 6, 2024
bbbedd8
Run terrafmt to lint test file
yaronya Jan 6, 2024
9df713d
Have the destination file system of the replication created on the al…
yaronya Jan 6, 2024
13b9fa2
Fix file_system_id EFS replication usage docs example
yaronya Jan 6, 2024
e7fc7c3
Run terrafmt to lint html markdown docs
yaronya Jan 7, 2024
435995e
Merge branch 'main' into HEAD
ewbankkit Jan 11, 2024
d9bca16
r/aws_efs_replication_configuration: Update documentation with new de…
ewbankkit Jan 11, 2024
aedbeed
Add CHANGELOG entry.
ewbankkit Jan 11, 2024
f278f36
r/aws_efs_replication_configuration: Cosmetics.
ewbankkit Jan 11, 2024
126e5d3
r/aws_efs_replication_configuration: Tidy up acceptance tests.
ewbankkit Jan 11, 2024
2c75d3a
r/aws_efs_replication_configuration: Tidy up.
ewbankkit Jan 11, 2024
0a7d0e5
r/aws_efs_mount_target: Fix 'MountTargetNotFound' errors on Delete.
ewbankkit Jan 11, 2024
f234faa
Cosmetics.
ewbankkit Jan 11, 2024
ec6180d
Fix 'TestAccEFSReplicationConfiguration_basic' and 'TestAccEFSReplica…
ewbankkit Jan 11, 2024
a27f18e
r/aws_efs_replication_configuration: Delete from both sides.
ewbankkit Jan 11, 2024
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
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
Loading