Skip to content

Commit

Permalink
resource/aws_vpc_endpoint_route_table_association: Handle read-after-…
Browse files Browse the repository at this point in the history
…create eventual consistency (#18465)

Reference: #12449
Reference: #16796

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSVpcEndpointRouteTableAssociation_basic (36.76s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSVpcEndpointRouteTableAssociation_basic (40.65s)
```
  • Loading branch information
bflad authored Apr 2, 2021
1 parent 62266d8 commit d46f920
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .changelog/pending.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_vpc_endpoint_route_table_association: Handle read-after-create eventual consistency
```
55 changes: 55 additions & 0 deletions aws/internal/service/ec2/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,61 @@ func VpcByID(conn *ec2.EC2, id string) (*ec2.Vpc, error) {
return nil, nil
}

// VpcEndpointByID looks up a VpcEndpoint by ID. When not found, returns nil and potentially an API error.
func VpcEndpointByID(conn *ec2.EC2, id string) (*ec2.VpcEndpoint, error) {
input := &ec2.DescribeVpcEndpointsInput{
VpcEndpointIds: aws.StringSlice([]string{id}),
}

output, err := conn.DescribeVpcEndpoints(input)

if err != nil {
return nil, err
}

if output == nil {
return nil, nil
}

for _, vpcEndpoint := range output.VpcEndpoints {
if vpcEndpoint == nil {
continue
}

if aws.StringValue(vpcEndpoint.VpcEndpointId) != id {
continue
}

return vpcEndpoint, nil
}

return nil, nil
}

// VpcEndpointRouteTableAssociation returns the associated Route Table ID if found
func VpcEndpointRouteTableAssociation(conn *ec2.EC2, vpcEndpointID string, routeTableID string) (*string, error) {
var result *string

vpcEndpoint, err := VpcEndpointByID(conn, vpcEndpointID)

if err != nil {
return nil, err
}

if vpcEndpoint == nil {
return nil, nil
}

for _, vpcEndpointRouteTableID := range vpcEndpoint.RouteTableIds {
if aws.StringValue(vpcEndpointRouteTableID) == routeTableID {
result = vpcEndpointRouteTableID
break
}
}

return result, err
}

// VpcPeeringConnectionByID returns the VPC peering connection corresponding to the specified identifier.
// Returns nil and potentially an error if no VPC peering connection is found.
func VpcPeeringConnectionByID(conn *ec2.EC2, id string) (*ec2.VpcPeeringConnection, error) {
Expand Down
62 changes: 47 additions & 15 deletions aws/resource_aws_vpc_endpoint_route_table_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode"
tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsVpcEndpointRouteTableAssociation() *schema.Resource {
Expand Down Expand Up @@ -65,27 +71,53 @@ func resourceAwsVpcEndpointRouteTableAssociationRead(d *schema.ResourceData, met

endpointId := d.Get("vpc_endpoint_id").(string)
rtId := d.Get("route_table_id").(string)
// Human friendly ID for error messages since d.Id() is non-descriptive
id := fmt.Sprintf("%s/%s", endpointId, rtId)

vpce, err := findResourceVpcEndpoint(conn, endpointId)
if err != nil {
if isAWSErr(err, "InvalidVpcEndpointId.NotFound", "") {
log.Printf("[WARN] VPC Endpoint (%s) not found, removing VPC Endpoint/Route Table association (%s) from state", endpointId, d.Id())
d.SetId("")
return nil
var routeTableID *string

err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error

routeTableID, err = finder.VpcEndpointRouteTableAssociation(conn, endpointId, rtId)

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcEndpointIdNotFound) {
return resource.RetryableError(err)
}

return err
}
if err != nil {
return resource.NonRetryableError(err)
}

found := false
for _, id := range vpce.RouteTableIds {
if aws.StringValue(id) == rtId {
found = true
break
if d.IsNewResource() && routeTableID == nil {
return resource.RetryableError(&resource.NotFoundError{
LastError: fmt.Errorf("VPC Endpoint Route Table Association (%s) not found", id),
})
}

return nil
})

if tfresource.TimedOut(err) {
routeTableID, err = finder.VpcEndpointRouteTableAssociation(conn, endpointId, rtId)
}
if !found {
log.Printf("[WARN] VPC Endpoint/Route Table association (%s) not found, removing from state", d.Id())

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcEndpointIdNotFound) {
log.Printf("[WARN] VPC Endpoint Route Table Association (%s) not found, removing from state", id)
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading VPC Endpoint Route Table Association (%s): %w", id, err)
}

if routeTableID == nil {
if d.IsNewResource() {
return fmt.Errorf("error reading VPC Endpoint Route Table Association (%s): not found after creation", id)
}

log.Printf("[WARN] VPC Endpoint Route Table Association (%s) not found, removing from state", id)
d.SetId("")
return nil
}
Expand Down

0 comments on commit d46f920

Please sign in to comment.